/* $Id: hawki_science_postprocess.c,v 1.37 2015/11/27 12:22:21 jim Exp $
 *
 * This file is part of the HAWKI Pipeline
 * Copyright (C) 2015 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jim $
 * $Date: 2015/11/27 12:22:21 $
 * $Revision: 1.37 $
 * $Name:  $
 */

/* Includes */

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

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <cpl.h>
#include <math.h>

#include "hawki_utils.h"
#include "hawki_pfits.h"
#include "hawki_fwhm.h"
#include "hawki_dfs.h"
#include "hawki_var.h"
#include "casu_utils.h"
#include "casu_mask.h"
#include "casu_mods.h"
#include "casu_fits.h"
#include "casu_tfits.h"
#include "casu_stats.h"
#include "casu_wcsutils.h"

/* Structure for user supplied command line options */

typedef struct {

    /* General parameters */

    int         nebulise;
    int         minphotom;
    int         prettynames;
    int         cdssearch_astrom;
    int         cdssearch_photom;
    int         stk_fast;
    int         stk_nfst;
    int         savemstd;
    char        *cacheloc;
    float       magerrcut;

    /* Nebuliser parameters */

    int         neb_medfilt;
    int         neb_linfilt;

    /* Source catalogue extraction parameters */

    int         cat_ipix;
    float       cat_thresh;
    int         cat_icrowd;
    float       cat_rcore;
    int         cat_nbsize;
} configstruct;

/* Structure to associate the various files together */

typedef struct {
    cpl_frame     *image;
    cpl_frame     *var;
} assoctype;

/* Memory structure */

typedef struct {
    cpl_size      *labels;
    int           npaw;
    cpl_frameset  *paw;
    cpl_frameset  *var;
    assoctype     *asc;

    cpl_frame     *master_conf;
    cpl_frame     *master_mstd_phot;
    cpl_frame     *phottab;
    cpl_table     *tphottab;
    cpl_frame     *catindex_a;
    char          *catpath_a;
    char          *catname_a;
    cpl_frame     *catindex_p;
    char          *catpath_p;
    char          *catname_p;
    cpl_frame     *schlf_n;
    cpl_frame     *schlf_s;

    casu_fits     *outmos;
    casu_fits     *outmosconf;
    casu_fits     *outmosvar;
    casu_tfits    *outmoscat;
} memstruct;

/* Recipe name for product headers */

#define HAWKI_RECIPENAME "hawki_science_postprocess"

static char hawki_science_postprocess_description[] =
"hawki_science_postprocess -- HAWKI science post processing recipe.\n\n"
"Postprocess a stacked pawprint for HAWKI data. Form a filled tile and\n"
"photometrically and astrometrically calibrate the tiled image\n"
"Optionally nebulise the stacked images before source detection\n\n"
"The program accepts the following files in the SOF:\n\n"
"    Tag                   Description\n"
"    -----------------------------------------------------------------------\n"
"    %-21s A set of processed images from a jitter sequence\n"
"    %-21s A variance map for each sequence image\n"
"    %-21s A master confidence map for the jitter sequence\n"
"    %-21s A photometric calibration table\n"
"    %-21s A master 2MASS index for astrometry or\n"
"    %-21s A master PPMXL index for astrometry or\n"
"    %-21s A master local astrometric FITS file\n"
"    %-21s A master 2MASS index for photometry or\n"
"    %-21s A master PPMXL index photometry or\n"
"    %-21s A master local photometric FITS file\n"
"    %-21s A master photometric matched stds catalogue (optional)\n"
"    %-21s Northern Schlegel Map\n"
"    %-21s Southern Schlegel Map\n"
"All of the above are required unless specified otherwise. The astrometric"
"and photometric files are not required if these can be obtained from"
"the CDS using the --cdssearch options\n"
"\n";

/* Required routines for CPL/esorex */

static int hawki_science_postprocess_create(cpl_plugin *plugin);
static int hawki_science_postprocess_exec(cpl_plugin *plugin);
static int hawki_science_postprocess_destroy(cpl_plugin *plugin);
static int hawki_science_postprocess(cpl_parameterlist *parlist,
                                     cpl_frameset *framelist);

/* Routines related to saving products */

static int hawki_sci_postproc_save_image(casu_fits *outim, 
                                         cpl_frameset *framelist,
                                         cpl_parameterlist *parlist,
                                         cpl_frame *template, int imtype,
                                         int prettyname, char *assoc[]);
static int hawki_sci_postproc_save_cat(casu_tfits *outcat, 
                                       cpl_frameset *framelist,
                                       cpl_parameterlist *parlist, int ptype,
                                       cpl_frame *template, int prettyname);
static void hawki_sci_product_name(const char *template, int producttype, 
                                   int nametype, int fnumber, char *outfname);
static int hawki_sci_assoc_frames(memstruct *ps);

/* Garbage collection routines */

static void hawki_sci_postprocess_init(memstruct *ps);
static void hawki_sci_postprocess_tidy(memstruct *ps);

/**
    \ingroup recipelist
    \defgroup hawki_science_postprocess hawki_science_postprocess
    \brief Create a tile from a HAWKI pawprint

    \par Name: 
        hawki_science_postprocess
    \par Purpose: 
        Create a tile from a HAWKI pawprint
    \par Description: 
        A single pawprint of HAWKI data in a single filter that has been
        processed using hawki_science_process is combined into a tile.
        The output tile is calibrated astrometrically and photometrically.
        The user has the option to remove variable background that might
        exist due to variable sky that has not been removed completely 
        during background correction, or nebulosity in the astronomical
        source (nebuliser option). If the nebuliser is used, then nebulised
        images are created and tiled. The catalogue is generated from the
        nebulised tile and the tile is then discarded. A tile from the
        un-nebulised images is the output image product.
    \par Language:
        C
    \par Parameters:
        - \b nebulise (bool): If set then the nebuliser will be used
        - \b neb_medfilt (int): Size of filter for median filtering
        - \b neb_linfilt (int): Size of filter for linear filtering
        - \b minphotom (int): The minimum number of standards for photometry
        - \b prettynames (bool): True if we're using nice file names
        - \b cdssearch_astrom (string): Use CDS for astrometric standards?
            - none: Use no CDS catalogues. Use local catalogues instead
            - 2mass: 2MASS PSC
            - usnob: USNOB catalogue
            - ppmxl: PPMXL catalogue
            - wise: WISE catalogue
        - \b cdssearch_photom (string): Use CDS for photometric standards?
            - none: Use no CDS catalogues. Use local catalogues instead
            - 2mass: 2MASS PSC
            - ppmxl: PPMXL catalogue
        - \b cacheloc (string): A directory where we can put the standard
             star cache
        - \b magerrcut (float): A cut in the magnitude error for photometric
             standards.
        - \b stk_fast (string): flag for fast or slow stacking algorithm.
             The fast algorithm is much greedier in memory.
            - auto: Let the recipe decide which we use
            - slow: Use the slow algorithm
            - fast: Use the fast algorithm
        - \b stk_nfst (int): The number of frames to stack, above which we
             revert to the slow algorithm
        - \b savemstd (int): If set, then matched standard catalogues are saved
        - \b cat_ipix (int): The minimum allowable size of an object
        - \b cat_thresh (float): The detection threshold in sigma above sky
        - \b cat_icrowd (int): If set then the deblending software will be used
        - \b cat_rcore (float): The core radius in pixels
        - \b cat_nbsize (int): The smoothing box size for background map estimation
    \par Input File Types:
        The following list gives the file types that can appear in the SOF
        file. The word in bold is the DO category value.
        - \b BASIC_CALIBRATED_SCI (required): The list of input calibrated 
             science images.
        - \b BASIC_VAR_MAP (required): The list of input calibrated science 
             image variance maps
        - \b MASTER_CONF (required): A master confidence map 
             for the filter used in the science images.
        - \b PHOTCAL_TAB (required): A photometric calibration table
        - \b MASTER_MSTD_PHOT (optional): A master photometric matched stds
             catalogue. If this is supplied then all the photometric calibration
             is done using this.
        - \b SCHLEGEL_MAP_NORTH (required): The Northern Schlegel dust map
        - \b SCHLEGEL_MAP_SOUTH (required): The Southern Schlegel dust map
        - \b MASTER_2MASS_CATLAOGUE_ASTROM or \b MASTER_PPMXL_CATALOGUE_ASTROM
             or \b MASTER_LOCAL_CATALOGUE_ASTROM (required): A master standard 
             star catalogue for astrometry. This is only required if the
             --cdssearch_astrom command line parameter is not being used.
        - \b MASTER_2MASS_CATLAOGUE_PHOTOM or \b MASTER_PPMXL_CATALOGUE_PHOTOM
             or \b MASTER_LOCAL_CATALOGUE_PHOTOM (required): A master standard 
             star catalogue for photometry. This is only required if the
             --cdssearch_photom command line parameter is not being used.
    \par Output Products:
        The following list gives the output data products that are generated
        by this recipe. The word in bold gives the DPR CATG keyword value for
        each product:
        - A tiled image of the input calibrated images, it's corresponding
          variance map and it's corresponding confidence map.
          (\b TILED_IMAGE, \b TILED_VAR_MAP and \b TILED_CONFIDENCE_MAP)
        - An object catalogue extracted from the tile. If the nebuliser
          was used, then this will have been derived from the nebulised tile.
          (\b TILED_OBJECT_CATALOGUE)
        - A matched standards catalogue for astrometry, if desired
          (\b MATCHSTD_ASTROM)
        - A matched standards catalogue for photometry, if desired
          (\b MATCHSTD_PHOTOM)
    \par Output QC Parameters:
        - \b SATURATION
             The saturation level in ADUs.
        - \b MEAN_SKY
             The mean level of the background sky over the image in ADUs.
        - \b SKY_NOISE
             The RMS of the background sky over the image in ADUs
        - \b IMAGE_SIZE
             The average size of stellar images on the image in pixels
        - \b ELLIPTICITY
             The average ellipticity of stellar images on the image
        - \b POSANG
             The average position angle in degrees from North towards East.
             NB: this value only makes sense if the ellipticity is significant
        - \b APERTURE_CORR
             The aperture correction for an aperture of radius rcore.
        - \b NOISE_OBJ
             The number of noise objects found in the image
        - \b MAGZPT
             The photometric zero point
        - \b MAGZERR
             The internal error in the photometric zero point
        - \b MAGNZPT
             The number of stars used to determine the magnitude zero point
        - \b MAGNCUT
             The number of stars cut from magnitude zero point calculation
        - \b SKYBRIGHT
             The sky brightness in mag/arcsec**2
        - \b LIMITING_MAG
             The limiting magnitude for this image for a 5 sigma detection.
        - \b WCS_DCRVAL1
             The offset of the equatorial coordinate represented by CRVAL1 
             from the raw frame to the reduced one (degrees).
        - \b WCS_DCRVAL2
             The offset of the equatorial coordinate represented by CRVAL2
             from the raw frame to the reduced one (degrees).
        - \b WCS_DTHETA
             The change in the WCS coordinate system rotation from the raw
             to the reduced frame (degrees)
        - \b WCS_SCALE
             The scale of the pixels in the reduced frames in arcseconds per
             pixel
        - \b WCS_SHEAR
             The shear of the astrometric solution in the form of the 
             difference between the rotation of the x solution and the rotation
             of the y solution (abs(xrot) - abs(yrot) in degrees)
        - \b WCS_RMS
             The average error in the WCS fit (arcsec)
    \par Notes
        None.
    \par Fatal Error Conditions:
        - NULL input frameset
        - Input frameset headers incorrect meaning that RAW and CALIB frame
          can't be distinguished
        - No science frames in the input frameset
        - Required master calibration images and tables are missing
        - Unable to save data products
    \par Non-Fatal Error Conditions:
        - None
    \par Conditions Leading To Dummy Products:
        - Master calibration images either won't load or are flagged as dummy
        - The detector for the current image extension is flagged dead in
          all science frames
        - Various processing routines fail.
    \par Author:
        Jim Lewis, CASU
    \par Code Reference: 
        hawki_science_postprocess.c
*/

/* Function code */

/*---------------------------------------------------------------------------*/
/**
  @brief    Build the list of available plugins, for this module.
  @param    list    the plugin list
  @return   0 if everything is ok

  This function is exported.
 */
/*---------------------------------------------------------------------------*/

int cpl_plugin_get_info(cpl_pluginlist *list) {
    cpl_recipe  *recipe = cpl_calloc(1,sizeof(*recipe));
    cpl_plugin  *plugin = &recipe->interface;
    char alldesc[SZ_ALLDESC];
    (void)snprintf(alldesc,SZ_ALLDESC,
                   hawki_science_postprocess_description,
                   HAWKI_PRO_SIMPLE_SCI,HAWKI_PRO_VAR_SCI,
                   HAWKI_PRO_CONF,HAWKI_CAL_PHOTTAB,
                   HAWKI_CAL_2MASS_A,HAWKI_CAL_PPMXL_A,HAWKI_CAL_LOCCAT_A,
                   HAWKI_CAL_2MASS_P,HAWKI_CAL_PPMXL_P,HAWKI_CAL_LOCCAT_P,
                   HAWKI_CAL_MSTD_PHOT,HAWKI_CAL_SCHL_N,HAWKI_CAL_SCHL_S);

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_science_postprocess",
                    "HAWKI post processing recipe",
                    alldesc,
                    "Jim Lewis",
                    "jrl@ast.cam.ac.uk",
                    hawki_get_license(),
                    hawki_science_postprocess_create,
                    hawki_science_postprocess_exec,
                    hawki_science_postprocess_destroy);

    cpl_pluginlist_append(list,plugin);

    return(0);
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options
  @param    plugin  the plugin
  @return   0 if everything is ok

  Create the recipe instance and make it available to the application using the
  interface.
 */
/*---------------------------------------------------------------------------*/

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

    /* Get the recipe out of the plugin */

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

    /* Create the parameters list in the cpl_recipe object */

    recipe->parameters = cpl_parameterlist_new();

    /* Fill in flag to use the nebuliser */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.nebulise",
                                CPL_TYPE_BOOL,
                                "Nebulise the stacks before object detection?",
                                "hawki.hawki_science_postprocess",FALSE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"nebulise");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in the minimum number of stars for photometry */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.minphotom",
                                CPL_TYPE_INT,
                                "Minimum number of stars for photometry solution",
                                "hawki.hawki_science_postprocess",20,1,100000);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"minphotom");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in flag to decide whether use predictable names or nice
       names based on input file names */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.prettynames",
                                CPL_TYPE_BOOL,"Use pretty product names?",
                                "hawki.hawki_science_postprocess",FALSE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"prettynames");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Flag to decide how we get the astrometric  standard star information. 
       If zero, then use the local catalogues specified in the sof. If not 
       zero, then use one of the selection of catalogues available from CDS */

    p = cpl_parameter_new_enum("hawki.hawki_science_postprocess.cdssearch_astrom",
                               CPL_TYPE_STRING,
                               "CDS astrometric catalogue",
                               "hawki.hawki_science_postprocess",
                               "none",5,"none","2mass","usnob","ppmxl","wise");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cdssearch_astrom");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Same for photometric standards */

    p = cpl_parameter_new_enum("hawki.hawki_science_postprocess.cdssearch_photom",
                               CPL_TYPE_STRING,
                               "CDS photometric catalogue",
                               "hawki.hawki_science_postprocess",
                               "none",3,"none","2mass","ppmxl");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cdssearch_photom");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Stacking speed method */

    p = cpl_parameter_new_enum("hawki.hawki_science_postprocess.stk_fast",
                               CPL_TYPE_STRING,
                               "Use fast stacking?",
                               "hawki.hawki_science_postprocess",
                               "auto",3,"fast","slow","auto");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_fast");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Limit to the number of frames we stack before reverting to the
       slow algorithm in auto mode */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.stk_nfst",
                                CPL_TYPE_INT,
                                "Nframes before we switch to slow stack algorithm",
                                "hawki.hawki_science_postprocess",30);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_nfst");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in flag to use save the matched standard catalogues */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.savemstd",
                                CPL_TYPE_BOOL,
                                "Save matched standard catalogues?",
                                "hawki.hawki_science_postprocess",FALSE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"savemstd");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in the median filter size for nebuliser */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.neb_medfilt",
                                CPL_TYPE_INT,
                                "Median filter size for nebuliser",
                                "hawki.hawki_science_postprocess",101,11,2047);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"neb_medfilt");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in the linear filter size for nebuliser */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.neb_linfilt",
                                CPL_TYPE_INT,
                                "Linear filter size for nebuliser",
                                "hawki.hawki_science_postprocess",33,3,2047);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"neb_linfilt");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in the minimum object size */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.cat_ipix",
                                CPL_TYPE_INT,
                                "Minimum pixel area for each detected object",
                                "hawki.hawki_science_postprocess",4,1,100000);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cat_ipix");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in the detection threshold parameter */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.cat_thresh",
                                CPL_TYPE_DOUBLE,
                                "Detection threshold in sigma above sky",
                                "hawki.hawki_science_postprocess",2.5,1.0e-6,
                                1.0e10);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cat_thresh");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in flag to use deblending software or not */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.cat_icrowd",
                                CPL_TYPE_BOOL,"Use deblending?",
                                "hawki.hawki_science_postprocess",TRUE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cat_icrowd");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in core radius */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.cat_rcore",
                                CPL_TYPE_DOUBLE,"Value of Rcore in pixels",
                                "hawki.hawki_science_postprocess",10.0,1.0e-6,
                                1024.0);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cat_rcore");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in background smoothing box size */

    p = cpl_parameter_new_range("hawki.hawki_science_postprocess.cat_nbsize",
                                CPL_TYPE_INT,"Background smoothing box size",
                                "hawki.hawki_science_postprocess",64,1,100000);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cat_nbsize");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Location for standard star cache */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.cacheloc",
                                CPL_TYPE_STRING,
                                "Location for standard star cache",
                                "hawki.hawki_science_postprocess",".");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cacheloc");
    cpl_parameterlist_append(recipe->parameters,p);        
        
    /* Magnitude error cut */

    p = cpl_parameter_new_value("hawki.hawki_science_postprocess.magerrcut",
                                CPL_TYPE_DOUBLE,
                                "Cut in magnitude error",
                                "hawki.hawki_science_postprocess",100.0);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"magerrcut");
    cpl_parameterlist_append(recipe->parameters,p);        
        
    /* Get out of here */

    return(0);
}

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

static int hawki_science_postprocess_exec(cpl_plugin *plugin) {
    cpl_recipe  *recipe;

    /* Get the recipe out of the plugin */

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

    return(hawki_science_postprocess(recipe->parameters,recipe->frames));
}
                                
/*---------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*---------------------------------------------------------------------------*/

static int hawki_science_postprocess_destroy(cpl_plugin *plugin) {
    cpl_recipe *recipe ;

    /* Get the recipe out of the plugin */

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

    cpl_parameterlist_delete(recipe->parameters);
    return(0);
}

/*---------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*---------------------------------------------------------------------------*/

static int hawki_science_postprocess(cpl_parameterlist *parlist,
                                     cpl_frameset *framelist) {
    const char *fctid="hawki_science_postprocess";
    configstruct cs;
    memstruct ps;
    casu_fits **inlist,**invars,*inf,*infc,*backmap,*tmpoutmos,*tmpoutmosconf;
    casu_fits **inconfs,*tmpoutmosvar;
    casu_tfits *matchstds,*matchstds_p;
    cpl_parameter *p;
    int nfail,j,status,ngood,k,fastslow,n,i,match,ndit;
    cpl_size nlab;
    cpl_table *stdscat,*tab,*t;
    char filt[16],*junk1,*junk2,pcat[32],*assoc[2],fname[BUFSIZ];
    cpl_propertylist *pp,*epp;
    float *x,*y,*ra,*dec,dit,gain;
    double r,d,*ra_d,*dec_d,*rac,*dra,*decc,*ddec,sum,mjd,mjdmax,tmax,mjdmin;
    double ra1,ra2,dec1,dec2;
    cpl_wcs *wcs;

    /* Check validity of the input frameset */

    if (framelist == NULL || cpl_frameset_get_size(framelist) <= 0) {
        cpl_msg_error(fctid,"Input framelist NULL or has no input data");
        return(-1);
    }

    /* Initialise some things */

    hawki_sci_postprocess_init(&ps);

    /* Get the command line parameters */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.nebulise");
    cs.nebulise = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.minphotom");
    cs.minphotom = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.prettynames");
    cs.prettynames = (cpl_parameter_get_bool(p) ? 1 : 0);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cdssearch_astrom");
    if (! strcmp(cpl_parameter_get_string(p),"none")) 
        cs.cdssearch_astrom = 0;
    else if (! strcmp(cpl_parameter_get_string(p),"2mass")) 
        cs.cdssearch_astrom = 1;
    else if (! strcmp(cpl_parameter_get_string(p),"usnob")) 
        cs.cdssearch_astrom = 2;
    else if (! strcmp(cpl_parameter_get_string(p),"ppmxl")) 
        cs.cdssearch_astrom = 3;
    else if (! strcmp(cpl_parameter_get_string(p),"wise")) 
        cs.cdssearch_astrom = 5;
    else
        cs.cdssearch_astrom = 0; /* Fallback is to use none */
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cdssearch_photom");
    if (! strcmp(cpl_parameter_get_string(p),"none")) 
        cs.cdssearch_photom = 0;
    else if (! strcmp(cpl_parameter_get_string(p),"2mass")) 
        cs.cdssearch_photom = 1;
    else if (! strcmp(cpl_parameter_get_string(p),"ppmxl")) 
        cs.cdssearch_photom = 3;
    else
        cs.cdssearch_photom = 0; /* Fallback is to use none */
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.stk_fast");
    if (! strcmp(cpl_parameter_get_string(p),"auto"))
        cs.stk_fast = -1;
    else if (! strcmp(cpl_parameter_get_string(p),"slow"))
        cs.stk_fast = 0;
    else if (! strcmp(cpl_parameter_get_string(p),"fast"))
        cs.stk_fast = 1;
    else
        cs.stk_fast = -1; /* Fallback is to auto */
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.stk_nfst");
    cs.stk_nfst = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.savemstd");
    cs.savemstd = cpl_parameter_get_bool(p);

    /* Nebuliser filter lengths */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.neb_medfilt");
    cs.neb_medfilt = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.neb_linfilt");
    cs.neb_linfilt = cpl_parameter_get_int(p);

    /* Object detection special parameters */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cat_ipix");
    cs.cat_ipix = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cat_thresh");
    cs.cat_thresh = (float)cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cat_icrowd");
    cs.cat_icrowd = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cat_rcore");
    cs.cat_rcore = (float)cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cat_nbsize");
    cs.cat_nbsize = cpl_parameter_get_int(p);

    /* Cache location */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.cacheloc");
    cs.cacheloc = (char *)cpl_parameter_get_string(p);

    /* Cut in magnitude error */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_postprocess.magerrcut");
    cs.magerrcut = (float)cpl_parameter_get_double(p);

    /* Sort out raw from calib frames */

    if (hawki_dfs_set_groups(framelist) != CASU_OK) {
        cpl_msg_error(fctid,"Cannot identify RAW and CALIB frames");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }

    /* Label the input frames */

    if ((ps.labels = cpl_frameset_labelise(framelist,casu_compare_tags,
                                           &nlab)) == NULL) {
        cpl_msg_error(fctid,"Cannot labelise the input frames");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }

    /* Get the input science frames */

    nfail = 0;
    if ((ps.paw = casu_frameset_subgroup(framelist,ps.labels,nlab,
                                         HAWKI_PRO_SIMPLE_SCI)) == NULL) {
        cpl_msg_error(fctid,"No science stack to process!");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    nfail += hawki_testfrms(ps.paw,HAWKI_NEXTN,1,1);
    ps.npaw = cpl_frameset_get_size(ps.paw);

    /* and variance maps */

    if ((ps.var = casu_frameset_subgroup(framelist,ps.labels,nlab,
                                         HAWKI_PRO_VAR_SCI)) == NULL) {
        cpl_msg_error(fctid,"No science stack variance maps");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    nfail += hawki_testfrms(ps.var,HAWKI_NEXTN,1,1);
    if (cpl_frameset_get_size(ps.var) != ps.npaw) {
        cpl_msg_error(fctid,
                      "Must have the same as the number of images as variance arrays");
        nfail++;
    }

    /* Check to see if there is a master confidence map. */

    if ((ps.master_conf = 
        casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                 HAWKI_CAL_CONF)) == NULL) {
        cpl_msg_error(fctid,"No master confidence map file found");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    nfail += hawki_testfrm_1(ps.master_conf,HAWKI_NEXTN,1,0);

    /* Check to see if there is a master matched standards table for photom */

    ps.master_mstd_phot = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                   HAWKI_CAL_MSTD_PHOT);
    if (ps.master_mstd_phot != NULL) 
        nfail += hawki_testfrm_1(ps.master_mstd_phot,1,0,0);

    /* Associate the input images, variance maps and catalogues together */

    if (hawki_sci_assoc_frames(&ps) != 0) {
        cpl_msg_error(fctid,"Data association problem -- cannot continue");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
        
    /* Check to see if there is a photometric table */

    if ((ps.phottab = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                               HAWKI_CAL_PHOTTAB)) == NULL) {
        cpl_msg_error(fctid,"No photometric table found");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    n = cpl_frame_get_nextensions(ps.phottab);
    nfail += hawki_testfrm_1(ps.phottab,n,0,0);
 
    /* Is the 2mass/ppmxl index file specified for astrom? */

    if (cs.cdssearch_astrom == 0) {
        if ((ps.catindex_a = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                      HAWKI_CAL_2MASS_A)) == NULL) {
            if ((ps.catindex_a = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                          HAWKI_CAL_PPMXL_A)) == NULL) {
                if ((ps.catindex_a = casu_frameset_subgroup_1(framelist,ps.labels,
                                                              nlab,HAWKI_CAL_LOCCAT_A)) == NULL) {
        
                    cpl_msg_error(fctid,
                                  "No astrometric standard catalogue found -- cannot continue");
                    hawki_sci_postprocess_tidy(&ps);
                    return(-1);
                }
            }
        }
        nfail += hawki_testfrm_1(ps.catindex_a,1,0,0);
    } else {
        status = CASU_OK;
        (void)casu_getstds_cdslist(cs.cdssearch_astrom,&junk1,&junk2,&status);
        freespace(junk1);
        freespace(junk2);
        if (status != CASU_OK) {
            status = CASU_OK;
            nfail++;
        }
    }

    /* Is the 2mass/ppmxl index file specified for photom? */

    if (cs.cdssearch_photom == 0 && ps.master_mstd_phot == NULL) {
        if ((ps.catindex_p = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                      HAWKI_CAL_2MASS_P)) == NULL) {
            if ((ps.catindex_p = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                          HAWKI_CAL_PPMXL_P)) == NULL) {
                if ((ps.catindex_p = casu_frameset_subgroup_1(framelist,ps.labels,
                                                              nlab,HAWKI_CAL_LOCCAT_P)) == NULL) {
        
                    cpl_msg_error(fctid,
                                  "No photometric standard catalogue found -- cannot continue");
                    hawki_sci_postprocess_tidy(&ps);
                    return(-1);
                }
            }
        }
        nfail += hawki_testfrm_1(ps.catindex_p,1,0,0);
        pp = cpl_propertylist_load(cpl_frame_get_filename(ps.catindex_p),1);
        if (cpl_propertylist_has(pp,"EXTNAME")) {
            strncpy(pcat,cpl_propertylist_get_string(pp,"EXTNAME"),32-1);
            pcat[32-1] = '\0';
        } else {
            cpl_msg_error(fctid,"No extension name in %s",
                          cpl_frame_get_filename(ps.catindex_p));
            nfail++;
        }
        cpl_propertylist_delete(pp);
    } else if (ps.master_mstd_phot != NULL) {
        pp = cpl_propertylist_load(cpl_frame_get_filename(ps.master_mstd_phot),0);
        strncpy(pcat,cpl_propertylist_get_string(pp,"PHOTCAT"),32-1);
        pcat[32-1] = '\0';
        cpl_propertylist_delete(pp);
    } else {
        (void)casu_getstds_cdslist(cs.cdssearch_photom,&junk1,&junk2,&status);
        strncpy(pcat,junk1,32-1);
        pcat[32-1] = '\0';
        freespace(junk1);
        freespace(junk2);
        if (status != CASU_OK) {
            status = CASU_OK;
            nfail++;
        }
    }

    /* Check the photometric table to see if it contains information about the
       photometric source we want to use */

    match = 0;
    n = cpl_frame_get_nextensions(ps.phottab);
    for (i = 1; i <= n; i++) {
        pp = cpl_propertylist_load(cpl_frame_get_filename(ps.phottab),i);
        if (cpl_propertylist_has(pp,"EXTNAME") &&
            strcmp(pcat,cpl_propertylist_get_string(pp,"EXTNAME")) == 0) {
            match = 1;
            ps.tphottab = cpl_table_load(cpl_frame_get_filename(ps.phottab),
                                         i,0);
            cpl_propertylist_delete(pp);
            break;
        }
        cpl_propertylist_delete(pp);
    }
    if (! match) {
        cpl_msg_error(fctid,"Photcal table has no information on %s",pcat);
        nfail++;
    }        

    /* Is the Northern Schlegel file specified? */

    if ((ps.schlf_n = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                               HAWKI_CAL_SCHL_N)) == NULL) {
        cpl_msg_error(fctid,"Schlegel North map not found -- cannot continue");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    nfail += hawki_testfrm_1(ps.schlf_n,0,1,0);

    /* Is the Southern Schlegel file specified? */

    if ((ps.schlf_s = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                               HAWKI_CAL_SCHL_S)) == NULL) {
        cpl_msg_error(fctid,"Schlegel South map not found -- cannot continue");
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    nfail += hawki_testfrm_1(ps.schlf_s,0,1,0);

    /* Check the cache location is writable */

    if (access(cs.cacheloc,R_OK+W_OK+X_OK) != 0) {
        cpl_msg_error(fctid,"Cache location %s inacessible",cs.cacheloc);
        nfail++;
    }

    /* Ok if any of this failed, then get out of here. This makes it a bit 
       simpler later on since we now know that all of the specified files 
       have the correct number of extensions and will load properly */

    if (nfail > 0) {
        cpl_msg_error(fctid,
                      "There are %" CPL_SIZE_FORMAT " input file errors -- cannot continue",
                      (cpl_size)nfail);
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    
    /* Get catalogue parameters */

    if (cs.cdssearch_astrom == 0) {
        if (casu_catpars(ps.catindex_a,&(ps.catpath_a),
                         &(ps.catname_a)) == CASU_FATAL) {
            hawki_sci_postprocess_tidy(&ps);
            return(-1);
        }
        freeframe(ps.catindex_a);
    } else {
        ps.catpath_a = NULL;
        ps.catname_a = NULL;
    }
    if (cs.cdssearch_photom == 0 && ps.master_mstd_phot == NULL) {
        if (casu_catpars(ps.catindex_p,&(ps.catpath_p),
                         &(ps.catname_p)) == CASU_FATAL) {
            hawki_sci_postprocess_tidy(&ps);
            return(-1);
        }
        freeframe(ps.catindex_p);
    } else {
        ps.catpath_p = NULL;
        ps.catname_p = NULL;
    }

    /* Using the nebuliser before doing the catalogue, then create a 
       nebulised pawprint */

    status = CASU_OK;
    if (cs.nebulise) {
        cpl_msg_info(fctid,"Nebulising pawprint");
        cpl_msg_indent_more();
        inlist = cpl_malloc(ps.npaw*HAWKI_NEXTN*sizeof(casu_fits *));
        inconfs = cpl_malloc(ps.npaw*HAWKI_NEXTN*sizeof(casu_fits *));
        ngood = 0;
        for (k = 0; k < ps.npaw; k++) {
            for (j = 1; j <= HAWKI_NEXTN; j++) {
                inf = casu_fits_load(ps.asc[k].image,CPL_TYPE_FLOAT,j);
                if (! casu_is_dummy(casu_fits_get_ehu(inf))) {
                    cpl_msg_info(fctid,"Doing extension %s",
                                 casu_fits_get_fullname(inf));
                    infc = casu_fits_load(ps.master_conf,CPL_TYPE_INT,j);
                    (void)casu_nebuliser(inf,infc,cs.neb_medfilt,cs.neb_linfilt,
                                         3,1,0,0,0,0,10.0,3.0,&backmap,&status);
                    if (status != CASU_OK) {
                        freespace(inlist);
                        freespace(inconfs);
                        hawki_sci_postprocess_tidy(&ps);
                        freefits(inf);
                        freefits(infc);
                        return(-1);
                    }
                    inlist[ngood] = inf;
                    inconfs[ngood] = infc;
                    ngood++;
                } else {
                    cpl_msg_warning(fctid,
                                    "Extension %s is a dummy and will be excluded",
                                    casu_fits_get_fullname(inf));
                    freefits(inf);
                }
            }
        }
        cpl_msg_indent_less();

        /* Were there any good images? */

        if (ngood == 0) {
            cpl_msg_error(fctid,"No good images in input list. Quitting");
            freespace(inlist);
            freespace(inconfs);
            hawki_sci_postprocess_tidy(&ps);
            return(-1);
        }
            
        /* Mosaic the nebulised files */

        cpl_msg_info(fctid,"Creating temporary nebulised mosaic");
        if (cs.stk_fast == -1) 
            fastslow = (ngood <= cs.stk_nfst);
        else
            fastslow = cs.stk_fast;
        (void)casu_imstack(inlist,inconfs,NULL,NULL,ngood,ngood,5.0,5.0,1,0,
                           fastslow,1,"ESO DET DIT",&tmpoutmos,&tmpoutmosconf,
                           &tmpoutmosvar,&status);
        if (status != CASU_OK) {
            hawki_sci_postprocess_tidy(&ps);
            freefitslist(inlist,ngood);
            freefitslist(inconfs,ngood);
            freefits(tmpoutmos);
            freefits(tmpoutmosconf);
            freefits(tmpoutmosvar);
            return(-1);
        }

        /* Chuck nebulised pawprint */

        for (j = 0; j < ngood; j++) {
            freefits(inlist[j]);
            freefits(inconfs[j]);
        }

        /* Now do an imcore to create a catalogue from the nebulised mosaic. */

        cpl_msg_info(fctid,"Creating catalogue from nebulised mosaic");
        (void)hawki_pfits_get_gain(casu_fits_get_ehu(tmpoutmos),&gain);
        (void)casu_imcore(tmpoutmos,tmpoutmosconf,cs.cat_ipix,cs.cat_thresh,
                          cs.cat_icrowd,cs.cat_rcore,cs.cat_nbsize,6,
                          2.0,&(ps.outmoscat),gain,&status);
        if (status != CASU_OK) {
            hawki_sci_postprocess_tidy(&ps);
            freespace(inlist);
            freespace(inconfs);
            freefits(tmpoutmos);
            freefits(tmpoutmosconf);
            freefits(tmpoutmosvar);
            return(-1);
        }

        /* Chuck the nebulised tile, confidence and variance maps */

        freefits(tmpoutmos);
        freefits(tmpoutmosvar);
        freefits(tmpoutmosconf);

        /* Load up the input images */

        invars = cpl_malloc(ps.npaw*HAWKI_NEXTN*sizeof(casu_fits *));
        ngood = 0;
        for (k = 0; k < ps.npaw; k++) {
            for (j = 0; j < HAWKI_NEXTN; j++) {
                inf = casu_fits_load(ps.asc[k].image,CPL_TYPE_FLOAT,j+1);
                if (! casu_is_dummy(casu_fits_get_ehu(inf))) {
                    inlist[ngood] = inf;
                    inconfs[ngood] = casu_fits_load(ps.master_conf,
                                                    CPL_TYPE_INT,j+1);
                    invars[ngood] = casu_fits_load(ps.asc[k].var,CPL_TYPE_FLOAT,
                                                   j+1);
                    ngood++;
                }
            }
        }

        /* Do the real mosaic */
        
        cpl_msg_info(fctid,"Creating the real mosaic");
        if (cs.stk_fast == -1) 
            fastslow = (ngood <= 10);
        else
            fastslow = cs.stk_fast;
        (void)casu_imstack(inlist,inconfs,invars,NULL,ngood,ngood,5.0,5.0,1,
                           0,fastslow,1,"EXPTIME",&(ps.outmos),&(ps.outmosconf),
                           &(ps.outmosvar),&status);
        if (status != CASU_OK) {
            hawki_sci_postprocess_tidy(&ps);
            freefitslist(inlist,ngood);
            freefitslist(inconfs,ngood);
            freefitslist(invars,ngood);
            return(-1);
        }
        freefitslist(inlist,ngood);
        freefitslist(inconfs,ngood);
        freefitslist(invars,ngood);

        /* Add provenance info into the headers */

        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmos),"NCOMBINE",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"NCOMBINE",
                                     "Number of input raw files");
        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmos),"NSTACK",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"NSTACK",
                                     "Number of images in stack");
        inlist = cpl_malloc(ps.npaw*sizeof(casu_fits *));
        for (k = 0; k < ps.npaw; k++) 
            inlist[k] = casu_fits_load(ps.asc[k].image,CPL_TYPE_FLOAT,1);
        casu_prov(casu_fits_get_phu(ps.outmos),inlist,ps.npaw,0);
        freefitslist(inlist,ps.npaw);

    /* Not using the nebuliser, so just do an imcore to create a catalogue 
       from the real mosaic */

    } else {
    
        /* Get memory for input file lists */

        inlist = cpl_malloc(ps.npaw*HAWKI_NEXTN*sizeof(casu_fits *));
        inconfs = cpl_malloc(ps.npaw*HAWKI_NEXTN*sizeof(casu_fits *));
        invars = cpl_malloc(ps.npaw*HAWKI_NEXTN*sizeof(casu_fits *));

        /* Load up the input images */

        ngood = 0;
        for (k = 0; k < ps.npaw; k++) {
            for (j = 0; j < HAWKI_NEXTN; j++) {
                inf = casu_fits_load(ps.asc[k].image,CPL_TYPE_FLOAT,j+1);
                casu_fits_unload_im(inf);
                if (! casu_is_dummy(casu_fits_get_ehu(inf))) {
                    inlist[ngood] = inf;
                    inconfs[ngood] = casu_fits_load(ps.master_conf,
                                                    CPL_TYPE_INT,j+1);
                    invars[ngood] = casu_fits_load(ps.asc[k].var,
                                                   CPL_TYPE_FLOAT,j+1);
                    casu_fits_unload_im(inconfs[ngood]);
                    ngood++;
                } else {
                    cpl_msg_warning(fctid,
                                    "Extension %s is a dummy and will be excluded",
                                    casu_fits_get_fullname(inf));
                    freefits(inf);
                }
            }
        }

        /* Were there any good images? */

        if (ngood == 0) {
            cpl_msg_error(fctid,"No good images in input list. Quitting");
            freespace(inlist);
            freespace(inconfs);
            freespace(invars);
            hawki_sci_postprocess_tidy(&ps);
            return(-1);
        }
            
        /* Do the real mosaic */
        
        cpl_msg_info(fctid,"Creating the real mosaic");
        if (cs.stk_fast == -1) 
            fastslow = (ngood <= 10);
        else
            fastslow = cs.stk_fast;
            
        (void)casu_imstack(inlist,inconfs,invars,NULL,ngood,ngood,5.0,5.0,
                           1,0,fastslow,1,"EXPTIME",&(ps.outmos),&(ps.outmosconf),
                           &(ps.outmosvar),&status);
        if (status != CASU_OK) {
            hawki_sci_postprocess_tidy(&ps);
            freefitslist(inlist,ngood);
            freefitslist(inconfs,ngood);
            freefitslist(invars,ngood);
            return(-1);
        }
        freefitslist(inlist,ngood);
        freefitslist(inconfs,ngood);
        freefitslist(invars,ngood);

        /* Add provenance info into the header */

        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmos),"NCOMBINE",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"NCOMBINE",
                                     "Number of input images");
        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmos),"NSTACK",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"NSTACK",
                                     "Number of images in stack");
        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmosconf),"NCOMBINE",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosconf),"NCOMBINE",
                                     "Number of input images");
        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmosconf),"NSTACK",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosconf),"NSTACK",
                                     "Number of images in stack");
        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmosvar),"NCOMBINE",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosvar),"NCOMBINE",
                                     "Number of input images");
        cpl_propertylist_update_int(casu_fits_get_phu(ps.outmosvar),"NSTACK",
                                    ps.npaw);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosvar),"NSTACK",
                                     "Number of images in stack");
        inlist = cpl_malloc(ps.npaw*sizeof(casu_fits *));
        for (k = 0; k < ps.npaw; k++) 
            inlist[k] = casu_fits_load(ps.asc[k].image,CPL_TYPE_FLOAT,1);
        casu_prov(casu_fits_get_phu(ps.outmos),inlist,ps.npaw,0);
        
        /* Get the mjd of each input image. Average it and write that to the
           output stack header */

        sum = 0.0;
        mjdmax = -1.0e15;
        mjdmin = 1.0e15;
        tmax = 0;
        for (i = 0; i < ps.npaw; i++) {
            hawki_pfits_get_mjd(casu_fits_get_phu(inlist[i]),&mjd);
            sum += mjd;
            if (mjd > mjdmax) {
                mjdmax = mjd;
                hawki_pfits_get_ndit(casu_fits_get_phu(inlist[i]),&ndit);
                hawki_pfits_get_dit(casu_fits_get_phu(inlist[i]),&dit);
                tmax = (double)(ndit*dit);
            }
            mjdmin = min(mjd,mjdmin);
        }
        sum /= (double)(ps.npaw);
        cpl_propertylist_update_double(casu_fits_get_ehu(ps.outmos),"MJD_MEAN",
                                       sum);
        cpl_propertylist_set_comment(casu_fits_get_ehu(ps.outmos),"MJD_MEAN",
                                     "Mean MJD of the input images");
        cpl_propertylist_update_double(casu_fits_get_ehu(ps.outmosconf),
                                       "MJD_MEAN",sum);
        cpl_propertylist_set_comment(casu_fits_get_ehu(ps.outmosconf),
                                     "MJD_MEAN","Mean MJD of the input images");
        cpl_propertylist_update_double(casu_fits_get_ehu(ps.outmosvar),
                                       "MJD_MEAN",sum);
        cpl_propertylist_set_comment(casu_fits_get_ehu(ps.outmosvar),"MJD_MEAN",
                                     "Mean MJD of the input images");
        cpl_propertylist_update_double(casu_fits_get_phu(ps.outmos),"MJD-END",
                                       mjdmax+tmax/86400.0);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"MJD-END",
                                     "End of observations");
        cpl_propertylist_update_double(casu_fits_get_phu(ps.outmosconf),
                                       "MJD-END",mjdmax+tmax/86400.0);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosconf),"MJD-END",
                                     "End of observations");
        cpl_propertylist_update_double(casu_fits_get_phu(ps.outmosvar),
                                       "MJD-END",mjdmax+tmax/86400.0);
        cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosvar),"MJD-END",
                                     "End of observations");
        cpl_propertylist_update_double(casu_fits_get_ehu(ps.outmos),"MJD-OBS",
                                       mjdmin);
        cpl_propertylist_update_double(casu_fits_get_ehu(ps.outmosconf),
                                       "MJD-OBS",mjdmin);
        cpl_propertylist_update_double(casu_fits_get_ehu(ps.outmosvar),
                                       "MJD-OBS",mjdmin);
        freefitslist(inlist,ps.npaw);

        /* Now the imcore */

        cpl_msg_info(fctid,"Creating catalogue from mosaic");
        (void)hawki_pfits_get_gain(casu_fits_get_ehu(ps.outmos),&gain);
        (void)casu_imcore(ps.outmos,ps.outmosconf,cs.cat_ipix,cs.cat_thresh,
                          cs.cat_icrowd,cs.cat_rcore,cs.cat_nbsize,6,
                          2.0,&(ps.outmoscat),gain,&status);
    }

    /* Get some standard stars and match it against the catalogue. Do
       the WCS fit */

    cpl_msg_info(fctid,"Doing WCS on mosaic");
    (void)casu_getstds(casu_fits_get_ehu(ps.outmos),1,
                       ps.catpath_a,ps.catname_a,cs.cdssearch_astrom,
                       cs.cacheloc,&stdscat,&status);
    (void)casu_matchstds(casu_tfits_get_table(ps.outmoscat),stdscat,300.0,
                         &tab,&status);
    matchstds = casu_tfits_wrap(tab,ps.outmoscat,NULL,NULL);
    (void)casu_platesol(casu_fits_get_ehu(ps.outmos),
                        casu_tfits_get_ehu(ps.outmoscat),tab,6,1,
                        &status);
    if (status != CASU_OK) {
        freetable(stdscat);
        freetfits(matchstds);
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }
    hawki_copywcs(casu_fits_get_ehu(ps.outmos),
                  casu_fits_get_ehu(ps.outmosconf));
    hawki_copywcs(casu_fits_get_ehu(ps.outmos),
                  casu_fits_get_ehu(ps.outmosvar));
    wcs = cpl_wcs_new_from_propertylist(casu_tfits_get_ehu(ps.outmoscat));
    n = cpl_table_get_nrow(casu_tfits_get_table(ps.outmoscat));
    x = cpl_table_get_data_float(casu_tfits_get_table(ps.outmoscat),
                                 "X_coordinate");
    y = cpl_table_get_data_float(casu_tfits_get_table(ps.outmoscat),
                                 "Y_coordinate");
    ra = cpl_table_get_data_float(casu_tfits_get_table(ps.outmoscat),"RA");
    dec = cpl_table_get_data_float(casu_tfits_get_table(ps.outmoscat),"DEC");
    for (j = 0; j < n; j++) {
        casu_xytoradec(wcs,(double)x[j],(double)y[j],&r,&d);
        ra[j] = (float)r;
        dec[j] = (float)d;
    }

    /* If saving the matchstds table, then add some information */

    if (cs.savemstd) {
        cpl_table_duplicate_column(tab,"RA_calc",tab,"RA");
        rac = cpl_table_get_data_double(tab,"RA_calc");
        cpl_table_duplicate_column(tab,"diffRA",tab,"RA");
        dra = cpl_table_get_data_double(tab,"diffRA");
        cpl_table_duplicate_column(tab,"Dec_calc",tab,"Dec");
        decc = cpl_table_get_data_double(tab,"Dec_calc");
        cpl_table_duplicate_column(tab,"diffDec",tab,"Dec");
        ddec = cpl_table_get_data_double(tab,"diffDec");

        /* Now compute the equatorial coordinates and compare with the 
           standard values */
        
        n = (int)cpl_table_get_nrow(tab);
        x = cpl_table_get_data_float(tab,"X_coordinate");
        y = cpl_table_get_data_float(tab,"Y_coordinate");
        ra_d = cpl_table_get_data_double(tab,"RA");
        dec_d = cpl_table_get_data_double(tab,"Dec");
        for (i = 0; i < n; i++) {
            casu_xytoradec(wcs,(double)x[i],(double)y[i],&r,&d);
            rac[i] = r;
            decc[i] = d;
            dra[i] = rac[i] - ra_d[i];
            ddec[i] = decc[i] - dec_d[i];
        }
    } else {
        freetfits(matchstds);
    }
    cpl_wcs_delete(wcs);
    freetable(stdscat);
    
    /* Now do the photometry */

    cpl_msg_info(fctid,"Doing photometry on mosaic");
    if (ps.master_mstd_phot == NULL) {
        (void)casu_getstds(casu_fits_get_ehu(ps.outmos),1,
                           ps.catpath_p,ps.catname_p,cs.cdssearch_photom,
                           cs.cacheloc,&stdscat,&status);
        (void)casu_matchstds(casu_tfits_get_table(ps.outmoscat),stdscat,300.0,
                             &tab,&status);
        matchstds_p = casu_tfits_wrap(tab,ps.outmoscat,NULL,NULL);
        cpl_propertylist_update_string(casu_tfits_get_phu(matchstds_p),
                                       "PHOTCAT",pcat);
        cpl_propertylist_set_comment(casu_tfits_get_phu(matchstds_p),"PHOTCAT",
                                     "Originating photometry source");
        freetable(stdscat);
    } else {
        t = cpl_table_load(cpl_frame_get_filename(ps.master_mstd_phot),j,1);
        pp = cpl_propertylist_load(cpl_frame_get_filename(ps.master_mstd_phot),0);
        epp = cpl_propertylist_load(cpl_frame_get_filename(ps.master_mstd_phot),j);
        matchstds_p = casu_tfits_wrap(t,NULL,pp,epp);
    }
    hawki_pfits_get_filter(casu_fits_get_phu(ps.outmos),filt);
    casu_photcal_extinct(&(ps.outmos),&matchstds_p,&(ps.outmoscat),1,
                         filt,ps.tphottab,cs.minphotom,ps.schlf_n,ps.schlf_s,
                         "EXPTIME","ESO TEL AIRM START",cs.magerrcut,&status);
    if (status != CASU_OK) {
        freetfits(matchstds);
        freetfits(matchstds_p);
        hawki_sci_postprocess_tidy(&ps);
        return(-1);
    }

    /* Work out the central RA, Dec of the output map and update 
       the product headers */

    cpl_propertylist_update_int(casu_fits_get_ehu(ps.outmos),"NAXIS1",
                                cpl_image_get_size_x(casu_fits_get_image(ps.outmos)));
    cpl_propertylist_update_int(casu_fits_get_ehu(ps.outmos),"NAXIS2",
                                cpl_image_get_size_y(casu_fits_get_image(ps.outmos)));
    cpl_propertylist_update_int(casu_fits_get_ehu(ps.outmos),"ZNAXIS1",
                                cpl_image_get_size_x(casu_fits_get_image(ps.outmos)));
    cpl_propertylist_update_int(casu_fits_get_ehu(ps.outmos),"ZNAXIS2",
                                cpl_image_get_size_y(casu_fits_get_image(ps.outmos)));
    (void)casu_coverage(casu_fits_get_ehu(ps.outmos),0,&ra1,&ra2,&dec1,&dec2,
                        &status);
    ra1 = 0.5*(ra1 + ra2);
    if (ra1 < 0.0)
        ra1 += 360.0;
    dec1 = 0.5*(dec1 + dec2);
    cpl_propertylist_update_double(casu_fits_get_phu(ps.outmos),"RA",ra1);
    cpl_propertylist_update_double(casu_fits_get_phu(ps.outmos),"DEC",dec1);
    cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"RA",
                                 "RA of field centre");
    cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmos),"DEC",
                                 "Dec of field centre");
    cpl_propertylist_update_double(casu_fits_get_phu(ps.outmosconf),"RA",ra1);
    cpl_propertylist_update_double(casu_fits_get_phu(ps.outmosconf),"DEC",dec1);
    cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosconf),"RA",
                                 "RA of field centre");
    cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosconf),"DEC",
                                 "Dec of field centre");
    cpl_propertylist_update_double(casu_fits_get_phu(ps.outmosvar),"RA",ra1);
    cpl_propertylist_update_double(casu_fits_get_phu(ps.outmosvar),"DEC",dec1);
    cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosvar),"RA",
                                 "RA of field centre");
    cpl_propertylist_set_comment(casu_fits_get_phu(ps.outmosvar),"DEC",
                                 "Dec of field centre");
    cpl_propertylist_update_double(casu_tfits_get_phu(ps.outmoscat),"RA",ra1);
    cpl_propertylist_update_double(casu_tfits_get_phu(ps.outmoscat),"DEC",dec1);
    cpl_propertylist_set_comment(casu_tfits_get_phu(ps.outmoscat),"RA",
                                 "RA of field centre");
    cpl_propertylist_set_comment(casu_tfits_get_phu(ps.outmoscat),"DEC",
                                 "Dec of field centre");

    /* Update the PSF from the DIMM in cases where not enough stars exist */
    double psf_fwhm;
    psf_fwhm = cpl_propertylist_get_float(casu_tfits_get_ehu(ps.outmoscat),
                                          "PSF_FWHM");
    if(psf_fwhm == -1)
    {
        double psf_fwhm_dimm;
        psf_fwhm_dimm = hawki_get_dimm_fwhm(casu_tfits_get_phu(ps.outmoscat));
        if(psf_fwhm_dimm > 0)
        {
            cpl_propertylist_update_float(casu_fits_get_ehu(ps.outmos),
                                          "PSF_FWHM", psf_fwhm_dimm);
            cpl_propertylist_set_comment(casu_fits_get_ehu(ps.outmos),
                                         "PSF_FWHM",
                             "spatial res. computed from DIMM seeing [arcsec]");
            cpl_propertylist_update_float(casu_tfits_get_ehu(ps.outmoscat),
                                          "PSF_FWHM", psf_fwhm_dimm);
            cpl_propertylist_set_comment(casu_tfits_get_ehu(ps.outmoscat),
                                         "PSF_FWHM",
                             "spatial res. computed from DIMM seeing [arcsec]");
        }
        else
        {
            cpl_propertylist_update_float(casu_tfits_get_ehu(ps.outmoscat),
                                          "PSF_FWHM", -1);
            cpl_propertylist_set_comment(casu_tfits_get_ehu(ps.outmoscat),
                                         "PSF_FWHM",
                                         "no spatial res. could be computed");
            cpl_propertylist_update_float(casu_fits_get_ehu(ps.outmos),
                                          "PSF_FWHM", -1);
            cpl_propertylist_set_comment(casu_fits_get_ehu(ps.outmos),
                                         "PSF_FWHM",
                                         "no spatial res. could be computed");
        }
        if (cpl_error_get_code() != CPL_ERROR_NONE)
        {
            cpl_msg_error(fctid,"Cannot compute the FWHM from the DIMM: %s",
                          cpl_error_get_message());
            freetfits(matchstds);
            freetfits(matchstds_p);
            hawki_sci_postprocess_tidy(&ps);
            return(-1);
        }
    }

    /* Save the mosaic and its confidence map */

    hawki_sci_product_name(cpl_frame_get_filename(ps.asc[0].image),1,
                           cs.prettynames,1,fname);
    assoc[0] = cpl_strdup(fname);
    hawki_sci_product_name(cpl_frame_get_filename(ps.asc[0].image),2,
                           cs.prettynames,1,fname);
    assoc[1] = cpl_strdup(fname);    
    cpl_msg_info(fctid,"Saving mosaic, confidence and variance maps");
    (void)hawki_sci_postproc_save_image(ps.outmos,framelist,parlist,
                                        ps.asc[0].image,0,cs.prettynames,
                                        assoc);
    freespace(assoc[0]);
    freespace(assoc[1]);
    (void)hawki_sci_postproc_save_image(ps.outmosconf,framelist,parlist,
                                        ps.asc[0].image,1,cs.prettynames,
                                        assoc); 
    (void)hawki_sci_postproc_save_image(ps.outmosvar,framelist,parlist,
                                        ps.asc[0].image,2,cs.prettynames,
                                        assoc);
    cpl_msg_info(fctid,"Saving catalogue");
    (void)hawki_sci_postproc_save_cat(ps.outmoscat,framelist,parlist,3,
                                      ps.asc[0].image,cs.prettynames);
    if (cs.savemstd) {
        (void)hawki_sci_postproc_save_cat(matchstds,framelist,parlist,4,
                                          ps.asc[0].image,cs.prettynames);
        (void)hawki_sci_postproc_save_cat(matchstds_p,framelist,parlist,5,
                                          ps.asc[0].image,cs.prettynames);
    }
    freetfits(matchstds);
    freetfits(matchstds_p);

    /* Tidy up */

    hawki_sci_postprocess_tidy(&ps);
    return(0);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_postproc_save_image
    \par Purpose:
        Save an image from the recipe
    \par Description:
        Save an image from the recipe. This works for tiles, confidence
        maps and variance maps
    \par Language:
        C
    \param outim
        The image to be saved
    \param framelist
        The input frameset from the recipe
    \param parlist
        The input parameter list from the recipe
    \param template
        A frame to be used as a template for the saved file.
    \param imtype
        A flag for the type of image: 0 -> image, 1 -> confidence map,
        2 -> variance map
    \param prettyname
        A flag to define whether pretty names are being used
    \param assoc
        A list of associated files to be used for PhaseIII headers.
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_postproc_save_image(casu_fits *outim, 
                                         cpl_frameset *framelist,
                                         cpl_parameterlist *parlist,
                                         cpl_frame *template, int imtype,
                                         int prettyname, char *assoc[]) {
    char fname[BUFSIZ],filt[16];
    cpl_frame *product_frame;
    cpl_propertylist *plist,*plist2;
    cpl_type dtype;
    int n,ndit;
    float dit,texp;
    const char *fctid="hawki_sci_postproc_save_image";

    /* Get some information so we can set a file name */

    hawki_sci_product_name(cpl_frame_get_filename(template),imtype,prettyname,1,
                           fname);

    /* Set up the file */

    if (access(fname,F_OK))
        remove(fname);
    product_frame = cpl_frame_new();
    cpl_frame_set_filename(product_frame,fname);
    if (imtype == 0) 
        cpl_frame_set_tag(product_frame,HAWKI_PRO_MOSAIC);
    else if (imtype == 1)
        cpl_frame_set_tag(product_frame,HAWKI_PRO_MOSAIC_CONF);
    else
        cpl_frame_set_tag(product_frame,HAWKI_PRO_MOSAIC_VAR);
    cpl_frame_set_type(product_frame,CPL_FRAME_TYPE_IMAGE);
    cpl_frame_set_group(product_frame,CPL_FRAME_GROUP_PRODUCT);
    cpl_frame_set_level(product_frame,CPL_FRAME_LEVEL_FINAL);

    /* Set up the PHU header */

    plist = cpl_propertylist_duplicate(casu_fits_get_phu(outim));
    plist2 = cpl_propertylist_duplicate(casu_fits_get_ehu(outim));
    casu_merge_propertylists(plist,plist2);
    freepropertylist(plist2);
    hawki_dfs_set_product_primary_header(plist,product_frame,framelist,parlist,
                                         HAWKI_RECIPENAME,"PRO-1.15",
                                         template,1);

    /* Set some PhaseIII parameters */

    cpl_propertylist_update_string(plist,"ORIGIN","ESO-PARANAL");
    cpl_propertylist_set_comment(plist,"ORIGIN",
                                 "European Southern Observatory");
    cpl_propertylist_update_string(plist,"TELESCOP","ESO-VLT-U4");
    cpl_propertylist_set_comment(plist,"TELESCOP","ESO telescope name");
    cpl_propertylist_update_string(plist,"INSTRUME","HAWKI");
    cpl_propertylist_set_comment(plist,"INSTRUME","Instrument used");
    cpl_propertylist_update_string(plist,"OBSTECH","IMAGE");
    cpl_propertylist_set_comment(plist,"OBSTECH","Observation Technique");
    cpl_propertylist_update_string(plist,"IMATYPE","TILE");
    cpl_propertylist_update_bool(plist,"ISAMP",0);
    cpl_propertylist_set_comment(plist,"ISAMP",
                                 "TRUE if image represents partially sampled sky");

    if (imtype == 0) {
        cpl_propertylist_update_string(plist,"PRODCATG","SCIENCE.IMAGE");
        cpl_propertylist_set_comment(plist,"PRODCATG","Data product category");
        cpl_propertylist_update_string(plist,"ASSON1",assoc[0]);
        cpl_propertylist_set_comment(plist,"ASSON1","Associated file");
        cpl_propertylist_update_string(plist,"ASSON2",assoc[1]);
        cpl_propertylist_set_comment(plist,"ASSON2","Associated file");
        cpl_propertylist_update_bool(plist,"SINGLEXP",0);
        cpl_propertylist_set_comment(plist,"SINGLEXP",
                                     "TRUE if resulting from a single exposure");
        n = cpl_propertylist_get_int(plist,"NSTACK");
        if (cpl_propertylist_has(plist,"ZPFUDGED")) {
            if (cpl_propertylist_get_bool(plist,"ZPFUDGED")) {
                cpl_propertylist_update_string(plist,"FLUXCAL","UNCALIBRATED");
            } else {
                cpl_propertylist_update_string(plist,"FLUXCAL","ABSOLUTE");
            }
        } else {
            cpl_propertylist_update_string(plist,"FLUXCAL","UNCALIBRATED");
        }
    cpl_propertylist_set_comment(plist,"FLUXCAL",
                                 "Certifies the validity of PHOTZP");
        cpl_propertylist_erase(plist,"ASSOC1");
        cpl_propertylist_erase(plist,"ASSOC2");
    } else {
        if (cpl_propertylist_has(plist, "PHOTZP"))
            cpl_propertylist_erase(plist,"PHOTZP");
        if (cpl_propertylist_has(plist, "PSF_FWHM"))
            cpl_propertylist_erase(plist,"PSF_FWHM");
        cpl_propertylist_erase(plist,"PRODCATG");
        cpl_propertylist_erase(plist,"ASSON1");
        cpl_propertylist_erase(plist,"ASSON2");
        cpl_propertylist_erase(plist,"ASSOC1");
        cpl_propertylist_erase(plist,"ASSOC2");
        cpl_propertylist_erase(plist,"SINGLEXP");
        n = cpl_propertylist_get_int(plist,"NCOMBINE");
        cpl_propertylist_erase(plist,"NCOMBINE");
        if (imtype == 1)
        {
            cpl_propertylist_update_string(plist,"PRODCATG",
                                           "ANCILLARY.WEIGHTMAP");
            cpl_propertylist_set_comment(plist,"PRODCATG",
                                         "Data product category");
        }
        else
        {
            cpl_propertylist_update_string(plist,"PRODCATG",
                                           "ANCILLARY.VARMAP");
            cpl_propertylist_set_comment(plist,"PRODCATG",
                                         "Data product category");
        }
    }
    hawki_pfits_get_filter(plist,filt);
    cpl_propertylist_update_string(plist,"FILTER",filt);
    cpl_propertylist_set_comment(plist,"FILTER","Filter used in observation"); 
    if (cpl_propertylist_has(plist,"FILTER1")) 
        cpl_propertylist_erase(plist,"FILTER1");
    if (cpl_propertylist_has(plist,"FILTER2")) 
        cpl_propertylist_erase(plist,"FILTER2");
    hawki_pfits_get_ndit(plist,&ndit);
    hawki_pfits_get_dit(plist,&dit);
    texp = (float)(ndit)*dit;
    cpl_propertylist_update_double(plist,"EFF_EXPT",(double)texp);
    texp = (float)(n*ndit)*dit;
    cpl_propertylist_update_double(plist,"EXPTIME",(double)texp);
    cpl_propertylist_update_double(plist,"TEXPTIME",texp);
    cpl_propertylist_update_int(plist,"OBID1",
                                cpl_propertylist_get_int(plist,"ESO OBS ID"));
    cpl_propertylist_set_comment(plist,"OBID1","Observation block ID");
    cpl_propertylist_update_bool(plist,"M_EPOCH",0);
    cpl_propertylist_set_comment(plist,"M_EPOCH",
                                 "TRUE if resulting from multiple epochs");
    cpl_propertylist_update_string(plist,"REFERENC","");
    cpl_propertylist_set_comment(plist,"REFERENC","Bibliographic Reference");
    cpl_propertylist_update_double(plist,"DIT",
                                   cpl_propertylist_get_double(plist,"ESO DET DIT"));
    cpl_propertylist_set_comment(plist,"DIT","Detector integration time");
    cpl_propertylist_update_string(plist,"BUNIT","ADU");
    cpl_propertylist_set_comment(plist,"BUNIT","Physical unit of array values");

    if (cpl_propertylist_has(plist,"ESO QC MAGZPT") && ndit > 0) {
        double magzpt = cpl_propertylist_get_double(plist,"ESO QC MAGZPT");
        cpl_propertylist_update_double(plist,"ESO QC MAGZPT TEL",
                                       magzpt + 2.5 * log10((double)ndit));
        cpl_propertylist_set_comment(plist,"ESO QC MAGZPT TEL",
                                             "[mag] photometric tel zeropoint");
    }
       
    /* Now save the data */

    dtype = (imtype == 1 ? CPL_TYPE_INT : CPL_TYPE_FLOAT);
    if (cpl_image_save(casu_fits_get_image(outim),fname,dtype,
                       plist,CPL_IO_DEFAULT) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product image extension -- %s",
                      cpl_error_get_message());
        freepropertylist(plist);
        freepropertylist(plist2);
        return(CASU_FATAL);
    }
    cpl_frameset_insert(framelist,product_frame);
    freepropertylist(plist);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_postproc_save_cat
    \par Purpose:
        Save an catalogue from the recipe
    \par Description:
        Save an catalogue from the recipe. 
    \par Language:
        C
    \param outcat
        The catalogue to be saved
    \param framelist
        The input frameset from the recipe
    \param parlist
        The input parameter list from the recipe
    \param ptype
        The catalogue type: 3 = source catalogue, 4 = astrom matched, 
        5 = photom matched
    \param template
        A frame to be used as a template for the saved file.
    \param prettyname
        A flag to define whether pretty names are being used
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_postproc_save_cat(casu_tfits *outcat, 
                                       cpl_frameset *framelist,
                                       cpl_parameterlist *parlist, int ptype,
                                       cpl_frame *template, int prettyname) {
    char fname[BUFSIZ],imgfname[BUFSIZ];
    cpl_frame *product_frame;
    cpl_propertylist *plist;
    const char *fctid = "hawki_sci_postproc_save_cat";
    int n,ndit;
    float dit,texp;

    /* Get some information so we can set a file name */

    hawki_sci_product_name(cpl_frame_get_filename(template),ptype,prettyname,1,
                           fname);

    /* Set up the file */

    if (access(fname,F_OK))
        remove(fname);
    product_frame = cpl_frame_new();
    cpl_frame_set_filename(product_frame,fname);
    switch (ptype) {
    case 3:
        cpl_frame_set_tag(product_frame,HAWKI_PRO_MOSAIC_CAT);
        break;
    case 4:
        cpl_frame_set_tag(product_frame,HAWKI_PRO_MATCHSTD_ASTROM);
        break;
    case 5:
        cpl_frame_set_tag(product_frame,HAWKI_PRO_MATCHSTD_PHOTOM);
        break;
    }
    cpl_frame_set_type(product_frame,CPL_FRAME_TYPE_TABLE);
    cpl_frame_set_group(product_frame,CPL_FRAME_GROUP_PRODUCT);
    cpl_frame_set_level(product_frame,CPL_FRAME_LEVEL_FINAL);

    /* Set up the PHU header */

    plist = casu_tfits_get_phu(outcat);
    hawki_dfs_set_product_primary_header(plist,product_frame,framelist,parlist,
                                         HAWKI_RECIPENAME,"PRO-1.15",NULL,1);
    hawki_sci_product_name(cpl_frame_get_filename(template),0,
                           prettyname,1,imgfname);
    cpl_propertylist_erase_regexp(plist,"PROV[0-9]*",0);
    cpl_propertylist_update_string(plist,"PROV1",imgfname);

    /* Set some PhaseIII parameters */

    if (ptype == 3) {
        cpl_propertylist_update_string(plist,"ORIGIN","ESO-PARANAL");
        cpl_propertylist_set_comment(plist,"ORIGIN",
                                     "European Southern Observatory");
        cpl_propertylist_update_string(plist,"TELESCOP","ESO-VLT-U4");
        cpl_propertylist_set_comment(plist,"TELESCOP","ESO telescope name");
        cpl_propertylist_update_string(plist,"INSTRUME","HAWKI");
        cpl_propertylist_set_comment(plist,"INSTRUME","Instrument used");
        cpl_propertylist_update_string(plist,"PRODCATG","SCIENCE.SRCTBL");
        cpl_propertylist_set_comment(plist,"PRODCATG","Data product category");
        cpl_propertylist_update_string(plist,"OBSTECH","IMAGE");
        cpl_propertylist_set_comment(plist,"OBSTECH","Observation Technique");
        cpl_propertylist_erase(plist,"ASSON1");
        cpl_propertylist_erase(plist,"ASSON2");
        cpl_propertylist_erase(plist,"ASSOC1");
        cpl_propertylist_erase(plist,"ASSOC2");
        cpl_propertylist_erase(plist,"BUNIT");
        cpl_propertylist_update_string(plist,"PROG_ID",
                                       cpl_propertylist_get_string(plist,"ESO OBS PROG ID"));
        cpl_propertylist_set_comment(plist,"PROG_ID",
                                     "ESO programme identification");
        cpl_propertylist_update_int(plist,"OBID1",
                                    cpl_propertylist_get_int(plist,"ESO OBS ID"));
        cpl_propertylist_set_comment(plist,"OBID1","Observation block ID");
        cpl_propertylist_update_bool(plist,"M_EPOCH",0);
        cpl_propertylist_set_comment(plist,"M_EPOCH",
                                     "TRUE if resulting from multiple epochs");
        cpl_propertylist_update_string(plist,"REFERENC","");
        cpl_propertylist_set_comment(plist,"REFERENC","Bibliographic Reference");
        cpl_propertylist_update_bool(plist,"SINGLEXP",0);
        cpl_propertylist_set_comment(plist,"SINGLEXP",
                                     "TRUE if resulting from a single exposure");
        cpl_propertylist_update_bool(plist,"ISAMP",0);
        cpl_propertylist_set_comment(plist,"ISAMP",
                                     "TRUE if image represents partially sampled sky");
        cpl_propertylist_update_string(plist,"IMATYPE","TILE");

        if (cpl_propertylist_has(plist,"NSTACK")) {
            n = cpl_propertylist_get_int(plist,"NSTACK");
        }
        else if (cpl_propertylist_has(plist, "NCOMBINE"))
        {
            n = cpl_propertylist_get_int(plist,"NCOMBINE");
        } else {
            n = 1 ; // This should never happen
        }

        hawki_pfits_get_ndit(plist,&ndit);
        hawki_pfits_get_dit(plist,&dit);
        texp = (float)(ndit)*dit;
        cpl_propertylist_update_double(plist,"EFF_EXPT",(double)texp);
        texp = (float)(n*ndit)*dit;
        cpl_propertylist_update_double(plist,"EXPTIME",(double)texp);
        cpl_propertylist_update_double(plist,"TEXPTIME",texp);

        if (cpl_propertylist_has(plist,"ESO QC MAGZPT") && ndit > 0) {
            double magzpt = cpl_propertylist_get_double(plist,"ESO QC MAGZPT");
            cpl_propertylist_update_double(plist,"ESO QC MAGZPT TEL",
                                           magzpt + 2.5 * log10((double)ndit));
            cpl_propertylist_set_comment(plist,"ESO QC MAGZPT TEL",
                                                 "[mag] photometric tel zeropoint");
        }
    }
        
    /* 'Save' the PHU image */                   

    if (cpl_image_save(NULL,fname,CPL_TYPE_UCHAR,plist,
                       CPL_IO_DEFAULT) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product PHU");
        cpl_frame_delete(product_frame);
        return(-1);
    }
    cpl_frameset_insert(framelist,product_frame);


    /* Get ndit from the primary for the correction */
    plist = casu_tfits_get_phu(outcat);
    hawki_pfits_get_ndit(plist,&ndit);


    /* Now save the extension */

    plist = casu_tfits_get_ehu(outcat);

    if (cpl_propertylist_has(plist,"ESO QC MAGZPT") && ndit > 0) {
        double magzpt = cpl_propertylist_get_double(plist,"ESO QC MAGZPT");
        cpl_propertylist_update_double(plist,"ESO QC MAGZPT TEL",
                                       magzpt + 2.5 * log10((double)ndit));
        cpl_propertylist_set_comment(plist,"ESO QC MAGZPT TEL",
                                             "[mag] photometric tel zeropoint");
    }

    hawki_dfs_set_product_exten_header(plist,product_frame,framelist,
                                       parlist,HAWKI_RECIPENAME,
                                       "PRO-1.15",template);
    cpl_propertylist_erase(plist,"BUNIT");
    if (cpl_table_save(casu_tfits_get_table(outcat),NULL,plist,fname,
                       CPL_IO_EXTEND) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product table extension -- %s",
                      cpl_error_get_message());
        return(-1);
    }
    return(0);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_product_name
    \par Purpose:
        Set up an output product file name
    \par Description:
        An output file name is defined based on a template name,  a
        'predictable' name or a temporary file name.
    \par Language:
        C
    \param template
        If the pretty file name option is used or if this is a temporary
        file name, then the this will be used as a reference name.
    \param producttype
        The type of product you are writing out. These are enumerated above.
    \param nametype
        This is: 0 -> predictable names, 1 -> pretty names based on input
        file names or 2 -> temporary file names.
    \param fnumber
        If the predictable file names are going to be used, then this is 
        a number that is appended to a file name root.
    \param outfname
        The output file name
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_product_name(const char *template, int producttype, 
                                   int nametype, int fnumber, char *outfname) {
    const char *esonames[] = {"tile_","tile_conf_","tile_var_","tile_cat_",
                              "tile_mstd_a_","tile_mstd_p_"};
    const char *suffix[] = {"_tl","_tl_conf","_tl_var","_tl_cat","_tl_mstd_a",
                            "_tl_mstd_p"};
    char *fname,*bname,*dot;

    /* If the name type is the ESO predictable sort, then it's easy. Just
       use the esonames defined above and append the correct number */

    switch (nametype) {
    case 0:
        (void)sprintf(outfname,"%s%d.fits",esonames[producttype],fnumber);
        break;

    /* If this is a temporary file, then just append tmp_ to the template
       name and return that */

    case 2:
        fname = cpl_strdup(template);
        bname = basename(fname);
        (void)sprintf(outfname,"tmp_%s",bname);
        freespace(fname);
        break;

    /* Ok, we want a pretty name... */

    case 1:
        fname = cpl_strdup(template);
        bname = basename(fname);
        (void)sprintf(outfname,"%s",bname);
        dot = strrchr(outfname,'.');
        (void)sprintf(dot,"%s.fits",suffix[producttype]);
        freespace(fname);
        break;

    /* something else ?? */

    default:
        (void)strcpy(outfname,"");
        break;
    }
    return;
}   

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_assoc_frames
    \par Purpose:
        Set up an association between the input images, the input
        variance maps and the optional input object catalogues.
    \par Description:
        The MJD of the input images is used as a key to define a pairing
        with one of the images from the variance map list and also (if
        given) from the list of object catalogues
    \par Language:
        C
    \param ps
        The recipe memory structure. This contains an association structure
    \return
        The number of input images that don't have a matching variance
        map.
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_assoc_frames(memstruct *ps) {
    int i,nobj,nvar,nfail,gotone,j;
    double *mjd_vars,mjd_obj;
    cpl_frame *frm;
    cpl_propertylist *plist;
    assoctype *asc;
    const char *fctid = "hawki_sci_assoc_frames";

    /* Start by getting the size of each frameset. Get some workspace
       for the mjds if needed */

    nobj = (int)cpl_frameset_get_size(ps->paw);
    nvar = (int)cpl_frameset_get_size(ps->var);
    mjd_vars = cpl_malloc(nvar*sizeof(double));

    /* Get the mjds of the vars */

    for (i = 0; i < nvar; i++) {
        frm = cpl_frameset_get_position(ps->var,i);
        plist = cpl_propertylist_load(cpl_frame_get_filename(frm),0);
        hawki_pfits_get_mjd(plist,mjd_vars+i);
        cpl_propertylist_delete(plist);
    }

    /* Loop for each object frame */

    asc = cpl_malloc(nobj*sizeof(assoctype));
    ps->asc = asc;
    nfail = 0;
    for (i = 0; i < nobj; i++) {
        frm = cpl_frame_duplicate(cpl_frameset_get_position(ps->paw,i));
        plist = cpl_propertylist_load(cpl_frame_get_filename(frm),0);
        hawki_pfits_get_mjd(plist,&mjd_obj);
        cpl_propertylist_delete(plist);
        asc[i].image = frm;
        gotone = -1;
        for (j = 0; j < nvar; j++) {
            if (mjd_vars[j] == mjd_obj) {
                gotone = j;
                break;
            }
        }
        if (gotone != -1) {
            asc[i].var = cpl_frame_duplicate(cpl_frameset_get_position(ps->var,
                                                                       gotone));
        } else {
            cpl_msg_error(fctid,"No matching variance map for %s\n",
                          cpl_frame_get_filename(frm));
            asc[i].var = NULL;
            nfail++;
        }
    }
    freespace(mjd_vars);
    return(nfail);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_assoc_tidy
    \par Purpose:
        Clear the allocated memory in the association structure
    \par Description:
        The section of the recipe memory structure that has the association
        information is cleared.
    \par Language:
        C
    \param ps
        The recipe memory structure. This contains an association structure
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_assoc_tidy(memstruct *ps) {
    int i;
 
    for (i = 0; i < ps->npaw; i++) {
        freeframe(ps->asc[i].image);
        freeframe(ps->asc[i].var);
    }
}

/**@}*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_postprocess_init
    \par Purpose:
        Initialise pointers in the global memory structure.
    \par Description:
        Initialise pointers in the global memory structure.
    \par Language:
        C
    \param ps
        The memory structure
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
            
static void hawki_sci_postprocess_init(memstruct *ps) {
    ps->labels = NULL;
    ps->paw = NULL;
    ps->var = NULL;
    ps->npaw = 0;
    ps->asc = NULL;
    ps->master_conf = NULL;
    ps->master_mstd_phot = NULL;
    ps->phottab = NULL;
    ps->tphottab = NULL;
    ps->catindex_a = NULL;
    ps->catpath_a = NULL;
    ps->catname_a = NULL;
    ps->catindex_p = NULL;
    ps->catpath_p = NULL;
    ps->catname_p = NULL;
    ps->schlf_n = NULL;
    ps->schlf_s = NULL;
    ps->outmos = NULL;
    ps->outmosconf = NULL;
    ps->outmosvar = NULL;
    ps->outmoscat = NULL;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_postprocess_tidy
    \par Purpose:
        Free allocated workspace in the global memory structure
    \par Description:
        Free allocated workspace in the global memory structure. 
    \par Language:
        C
    \param ps
        The memory structure
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_postprocess_tidy(memstruct *ps) {
    freespace(ps->labels);
    hawki_sci_assoc_tidy(ps);
    freeframeset(ps->paw);
    freespace(ps->asc);
    freeframeset(ps->var);
    freeframe(ps->phottab);
    freeframe(ps->master_conf);
    freeframe(ps->master_mstd_phot);
    freetable(ps->tphottab);
    freeframe(ps->catindex_a);
    freespace(ps->catpath_a);
    freespace(ps->catname_a);
    freeframe(ps->catindex_p);
    freespace(ps->catpath_p);
    freespace(ps->catname_p);
    freeframe(ps->schlf_n);
    freeframe(ps->schlf_s);
    freefits(ps->outmos);
    freefits(ps->outmosconf);
    freefits(ps->outmosvar);
    freetfits(ps->outmoscat);
}

/*

$Log: hawki_science_postprocess.c,v $
Revision 1.37  2015/11/27 12:22:21  jim
Added ability to define location of standard star location

Revision 1.36  2015/11/25 10:30:09  jim
Modified to fix the RA and DEC coordinates in the header to the
central position of the output tile

Revision 1.35  2015/11/16 11:43:57  jim
Fixed yet another provenance issue, yawn...

Revision 1.34  2015/10/22 12:14:00  jim
A few touchups

Revision 1.33  2015/10/20 11:54:10  jim
Added stk_nfst parameter

Revision 1.32  2015/10/15 11:22:56  jim
Included NSTACK header item

Revision 1.31  2015/09/22 09:36:51  jim
Fixed error in phase3 headers

Revision 1.30  2015/09/15 10:29:40  jim
Fixed so that _var and _conf files get the correct WCS

Revision 1.29  2015/09/11 09:31:20  jim
Changed parameter declarations from _value to _range where appropriate

Revision 1.28  2015/08/12 11:24:29  jim
Fixed bug affecting the loading of master photometric matched standards
catalogues

Revision 1.27  2015/08/07 13:07:06  jim
Fixed copyright to ESO

Revision 1.26  2015/07/29 09:18:30  jim
Fixed a few inconsistencies in the ugly mode file naming

Revision 1.25  2015/07/02 12:29:59  jim
Fixed so that we get the correct value of FLUXCAL now

Revision 1.24  2015/06/25 11:55:27  jim
Fixed call to casu_imstack to correct expkey parameter

Revision 1.23  2015/06/08 09:34:56  jim
Modified to add more phase3 stuff to headers

Revision 1.22  2015/05/21 07:48:48  jim
Fixed bug where error was thrown if more than one extension was found
in the photcal tab

Revision 1.21  2015/05/20 11:55:37  jim
Removed the ability to include source catalogues in the input list. This
was a design mistake...

Revision 1.20  2015/04/30 12:13:17  jim
Fixed problem in save routine which caused a seg fault when saving
headers

Revision 1.19  2015/03/12 09:16:51  jim
Modified to remove some compiler moans

Revision 1.18  2015/03/10 10:00:57  jim
Plugged another leak

Revision 1.17  2015/03/09 11:58:00  jim
Fixed memory leak

Revision 1.16  2015/03/02 10:51:14  jim
Fixed little bug in call to tidy routine

Revision 1.15  2015/02/17 11:23:34  jim
Now allows for a matched standards catalogue for photometry to be
included as a calibration frame

Revision 1.14  2015/02/14 12:34:53  jim
It is now possible to write out matched standards catalogues

Revision 1.13  2015/01/29 12:03:01  jim
Modified comments. Modified command line arguments to use strings for
enums where that makes sense. Also removed some support routines to
hawki_utils.c

Revision 1.12  2015/01/09 12:12:55  jim
Fixed so that RA and DEC columns are filled in the output catalogues

Revision 1.11  2014/12/12 21:44:31  jim
Fixed to remove inverse variance

Revision 1.10  2014/12/11 12:24:09  jim
lots of upgrades

Revision 1.9  2014/05/29 11:03:44  jim
Modified to create new product tags for jittered confidence maps and
catalogues as well as tile catalogues

Revision 1.8  2014/05/06 12:01:43  jim
Fixed bug in declaration of enum cdssearch

Revision 1.7  2014/04/27 10:10:33  jim
Modified to allow for local FITS file standard star catalogues

Revision 1.6  2014/04/09 09:09:51  jim
Detabbed

Revision 1.5  2014/03/26 16:11:22  jim
Removed calls to deprecated cpl routine. Confidence maps are now
floating point

Revision 1.4  2013/11/21 09:38:14  jim
detabbed

Revision 1.3  2013/11/08 06:54:11  jim
Modified to try and cut down the amount of memory being used

Revision 1.2  2013/11/07 10:06:47  jim
Modified to change the input to _ex files rather than _st files

Revision 1.1  2013-09-30 18:06:32  jim
Initial entry


*/
