/* $Id: hawki_science_process.c,v 1.56 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.56 $
 * $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_dfs.h"
#include "hawki_var.h"
#include "hawki_fwhm.h"
#include "casu_utils.h"
#include "casu_mask.h"
#include "casu_mods.h"
#include "casu_fits.h"
#include "casu_tfits.h"
#include "casu_sky.h"
#include "casu_stats.h"
#include "casu_wcsutils.h"

/* Data structures */

typedef struct {
    cpl_frameset   *contrib;
    cpl_frameset   *contrib_var;
    cpl_frame      *objmask;
    int            skyalgo;
    cpl_frame      *skyframe;
    cpl_frame      *skyframe_var;
    cpl_frame      *template;
    char           fname[BUFSIZ];
    char           fname_var[BUFSIZ];
} skystruct;

typedef struct {
    cpl_frameset *current;
    cpl_frameset *current_var;
    cpl_frameset *orig;
    int          *whichsky;
    casu_fits    *stack[HAWKI_NEXTN];
    casu_fits    *stackc[HAWKI_NEXTN];
    casu_fits    *stackv[HAWKI_NEXTN];
    casu_tfits   *cat[HAWKI_NEXTN];
    casu_tfits   *mstds_a[HAWKI_NEXTN];
    casu_tfits   *mstds_p[HAWKI_NEXTN];
    cpl_frame    *product_frame_im;
    cpl_frame    *product_frame_conf;
    cpl_frame    *product_frame_cat;
    cpl_frame    *product_frame_var;
    cpl_frame    *product_frame_mstd_a;
    cpl_frame    *product_frame_mstd_p;
    double       mjd_start;
    double       mjd_end;
} pawprint;

/* Structure for user supplied command line options */

typedef struct {

    /* General parameters */

    int         savecat;
    int         savemstd;
    int         skyalgo;
    int         preview_only;
    int         minphotom;
    int         prettynames;
    int         cdssearch_astrom;
    int         cdssearch_photom;
    char        *cacheloc;
    float       magerrcut;

    /* Pawsky mask parameters */

    int         psm_niter;
    int         psm_ipix;
    float       psm_thresh;
    int         psm_nbsize;
    float       psm_smkern;

    /* Stacking parameters */

    float       stk_lthr;
    float       stk_hthr;
    int         stk_method;
    int         stk_seeing;
    int         stk_fast;
    int         stk_nfst;

    /* Source catalogue extraction parameters */

    int         stk_cat_ipix;
    float       stk_cat_thresh;
    int         stk_cat_icrowd;
    float       stk_cat_rcore;
    int         stk_cat_nbsize;
} configstruct;


/* A structure with long term allocated memory. Very useful for 
   garbage collection */

typedef struct {

    /* Level 0 stuff */

    cpl_size         *labels;
    cpl_frame        *master_dark;
    cpl_frame        *master_twilight_flat;
    cpl_frame        *master_conf;
    cpl_frame        *master_sky;
    cpl_frame        *master_sky_var;
    cpl_frame        *master_objmask;
    cpl_frame        *master_mstd_phot;
    casu_mask        *mask;
    cpl_frame        *phottab;
    cpl_table        *tphottab;
    cpl_frameset     *science_frames;
    cpl_frameset     *offset_skies;
    cpl_frame        **product_frames_simple;
    cpl_frame        **product_frames_simple_var;
    cpl_frame        **product_frames_simple_off;
    cpl_frame        **product_frames_simple_off_var;
    float            *gaincors;
    char             *catpath_a;
    char             *catname_a;
    char             *catpath_p;
    char             *catname_p;
    pawprint         *scipaw;
    pawprint         *offpaw;
    int              nskys;
    skystruct        *skys;
    cpl_frame        *schlf_n;
    cpl_frame        *schlf_s;
    cpl_frame        *readgain;

    /* Level 1 stuff */

    casu_fits        *fdark;
    casu_fits        *fdark_var;
    casu_fits        *fflat;
    casu_fits        *fconf;
    casu_fits        *fsky;

    int              nscience;
    casu_fits        **sci_fits;
    int              noffsets;
    casu_fits        **offsky_fits;

} memstruct;


/* List of data products types */

enum {SIMPLE_FILE,
      SIMPLE_VAR,
      SIMPLE_CAT,
      STACK_FILE,
      STACK_VAR,
      STACK_CONF,
      STACK_CAT,
      SKY_FILE,
      SKY_FILE_VAR,
      SIMPLE_MSTD_ASTROM,
      STACK_MSTD_ASTROM,
      MSTD_PHOTOM
};

/* Current list of sky algorithms */

#define SKYNONE           0
#define SKYMASTER        -1
#define PAWSKY_MASK       1
#define PAWSKY_MASK_PRE   2
#define PSEUDO_TILESKY    3
#define SIMPLESKY_MASK    4
#define PAWSKY_MINUS      5
#define AUTO              6
static const char *skyalstr[] = {"none","pawsky_mask","pawsky_mask_pre",
                                 "pseudo_tilesky","simplesky_mask",
                                 "pawsky_minus"};

/* Recipe name for product headers */

#define HAWKI_RECIPENAME "hawki_science_process"

/* Required routines for CPL/esorex */

static int hawki_science_process_create(cpl_plugin *plugin);
static int hawki_science_process_exec(cpl_plugin *plugin);
static int hawki_science_process_destroy(cpl_plugin *plugin);
static int hawki_science_process(cpl_parameterlist *parlist,
                                 cpl_frameset *framelist);

/* Pawprint manipulation routines */

static void hawki_sci_paw_init(pawprint **paw, cpl_frameset *frms,
                               configstruct *cs, int *runno);
static void hawki_sci_paw_delete(pawprint **paws);

/* Processing routines */

static int hawki_sci_sky_wcs(pawprint *paw, int isoffsky,
                             casu_mask *mask, int nskys, skystruct *skys,
                             cpl_frame *master_conf, cpl_frameset *framelist, 
                             cpl_parameterlist *parlist, char *catname,
                             char *catpath, char *cacheloc, configstruct *cs, 
                             char *photosys, cpl_frameset *prod_im, 
                             cpl_frameset *prod_tab);
static void hawki_sci_cat(pawprint *paw, int extn, configstruct *cs);
static void hawki_sci_stack(pawprint *paw, cpl_frame *conf, int extn, 
                            configstruct *cs);
static void hawki_sci_wcsfit(casu_fits **in, casu_fits **conf, 
                             casu_tfits **incat, int nf, int level,
                             char *catname, char *catpath, 
                             char *cacheloc, int cdssearch);
static void hawki_sci_photcal(pawprint *paw, cpl_table *tphottab,
                              cpl_frame *schlf_n, cpl_frame *schlf_s,
                              int minphotom, float magerrcut);

/* Sky related routines */

static void hawki_sci_choose_skyalgo(int *nskys, skystruct **skys, 
                                     pawprint *offpaw, pawprint *scipaw, 
                                     cpl_frame *master_objmask,
                                     int prettynames);
static int hawki_sci_makesky(cpl_frameset *framelist, 
                             cpl_parameterlist *parlist, skystruct *sky,
                             cpl_frame *master_objmask, cpl_frame *master_conf,
                             casu_mask *mask, configstruct *cs, memstruct *ps);
static void hawki_sci_assign_sky_all(pawprint *paw, int whichone);
static skystruct hawki_sci_crsky(int algorithm, cpl_frameset *frms,
                                 cpl_frameset *frms_var, cpl_frame *template, 
                                 int prettynames, cpl_frame *master_objmask, 
                                 int snum);
static void hawki_sci_skydefine(int *nskys, skystruct **skys, 
                                cpl_frame *master_sky, 
                                cpl_frame *master_sky_var, pawprint *scipaw,
                                pawprint *offpaw, cpl_frame *master_objmask,
                                int skyalgo, int prettynames);
static int hawki_sci_pawsky_mask(cpl_frameset *framelist, 
                                 cpl_parameterlist *parlist, int algo,
                                 cpl_frameset *contrib, 
                                 cpl_frameset *contrib_var, cpl_frame *template,
                                 char *fname, char *fname_var, 
                                 cpl_frame *master_conf, casu_mask *mask, 
                                 configstruct *cs, cpl_frame *master_objmask,
                                 cpl_frame **product_frame, 
                                 cpl_frame **product_frame_var);
static int hawki_sci_pawsky_minus(cpl_frameset *framelist, 
                                  cpl_parameterlist *parlist,
                                  cpl_frameset *in, cpl_frameset *invar,
                                  cpl_frame *sky_template, char *skyname, 
                                  char *skyname_var, cpl_frame *master_objmask,
                                  cpl_frame *master_conf, char *catname,
                                  char *catpath, char *cacheloc, int cdssearch, 
                                  cpl_frame **skyframe, 
                                  cpl_frame **skyframe_var);
static int hawki_sci_simplesky_mask(cpl_frameset *framelist, 
                                    cpl_parameterlist *parlist, 
                                    cpl_frameset *contrib, 
                                    cpl_frameset *contrib_var, 
                                    cpl_frame *template, char *fname, 
                                    char *fname_var, cpl_frame *master_conf,
                                    casu_mask *mask, configstruct *cs,
                                    cpl_frame **product_frame,
                                    cpl_frame **product_frame_var);
static void hawki_sci_splitsky(cpl_frameset *frms, cpl_frameset *frms_var,
                               cpl_frameset *tpls, pawprint *offpaw, 
                               pawprint *scipaw, float xchar, float ychar, 
                               cpl_frame *master_objmask, int prettynames,
                               int *nskys, skystruct **skys);
static int hawki_sci_pseudo_tilesky(cpl_frameset *framelist, 
                                    cpl_parameterlist *parlist, 
                                    cpl_frameset *in, cpl_frameset *invar,
                                    cpl_frame *sky_template, char *skyname, 
                                    char *skyname_var, casu_mask *mask, 
                                    cpl_frame **skyframe, 
                                    cpl_frame **skyframe_var);

/* Save routines */

static int hawki_sci_save_simple(casu_fits *obj, cpl_frameset *framelist,
                                 cpl_parameterlist *parlist, int isprod,
                                 cpl_frame *template, int isfirst,
                                 const char *tag, char *fname, char *assoc,
                                 cpl_frame **product_frame);
static int hawki_sci_save_sky(casu_fits *outsky, cpl_frameset *framelist,
                              cpl_parameterlist *parlist, 
                              char *fname, cpl_frame *template, int isvar,
                              int isfirst, cpl_frame **product_frame);
static int hawki_sci_save_stack(casu_fits *stack, cpl_frameset *framelist,
                                cpl_parameterlist *parlist,
                                cpl_frame *template, int fnametype,
                                int filetype, char *assoc[], char *photosys,
                                cpl_frame **product_frame);
static int hawki_sci_save_stack_conf(casu_fits *stack, cpl_frameset *framelist,
                                     cpl_parameterlist *parlist,
                                     cpl_frame *template, int fnametype,
                                     cpl_frame **product_frame);
static int hawki_sci_save_cat(casu_tfits *scat, cpl_frameset *framelist,
                              cpl_parameterlist *parlist, 
                              cpl_frame *template, int fnametype, int ptype,
                              int fnumber, char *photosys,
                              cpl_frame **product_frame);
static void hawki_sci_product_name(char *template, int producttype, 
                                   int nametype, int fnumber, char *outfname);

/* Utility routines */

static cpl_frame *hawki_sci_findtemplate(cpl_frame *in, 
                                         cpl_frameset *framelist);
static cpl_frameset *hawki_sci_update_frameset(cpl_frameset *frms,
                                               configstruct *cs, int ftype,
                                               int *runno);
static int hawki_sci_all1tpl(cpl_frameset *frms);
static int hawki_sci_cmp_tstart(const cpl_frame *frame1, 
                                const cpl_frame *frame2);
static void hawki_sci_charshift(cpl_frameset *frms, float *xoff, float *yoff);
static void hawki_sci_update_hdr(cpl_frameset *simple, cpl_frame *stack, 
                                 int typef);
static void hawki_sci_update_hdr_stack(pawprint *stack);
static void hawki_sci_update_fwhm_dimm(pawprint *stack);
static void hawki_sci_update_catprov(cpl_frame *catfrm, cpl_frame *imfrm);

/* Garbage collection routines */

static void hawki_sci_init(memstruct *ps);
static void hawki_sci_tidy(memstruct *ps, int level);

static char hawki_science_process_description[] =
"hawki_science_process -- HAWKI science product recipe.\n\n"
"Process a complete pawprint of HAWKI data. Remove instrumental\n"
"signature, remove sky background, combine jitters, photometrically\n"
"and astrometrically calibrate the pawprint image\n\n"
"The program accepts the following files in the SOF:\n\n"
"    Tag                   Description\n"
"    -----------------------------------------------------------------------\n"
"    %-21s A list of raw science images \n"
"    %-21s A list of offset sky exposures (optional)\n"
"    %-21s A master dark frame\n"
"    %-21s A master twilight flat frame\n"
"    %-21s A master sky frame (optional)\n"
"    %-21s A master sky variance frame (optional)\n"
"    %-21s A photometric calibration table\n"
"    %-21s A master confidence map\n"
"    %-21s A master readgain 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 for photometry or\n"
"    %-21s A master local photometric FITS file\n"
"    %-21s A master matched standards catalogue for photometry\n"
"    %-21s A master object mask (optional)\n"
"    %-21s Northern Schlegel Map\n"
"    %-21s Southern Schlegel Map\n"
"All of the above are required unless specifically tagged as optional. The"
"astrometric and photometric files are not required if these can be obtained"
"from the CDS using the --cdssearch options\n"
"\n";

/**@{*/

/**
    \ingroup recipelist
    \defgroup hawki_science_process hawki_science_process
    \brief Reduce a full pawprint of HAWKI data

    \par Name: 
        hawki_science_process
    \par Purpose: 
        Reduce a full pawprint of HAWKI data
    \par Description: 
        A single pawprint of HAWKI data in a single filter is corrected 
        for instrumental signature. The sky is removed using a defined
        algorithm and the corrected simple images are dithered into the final 
        stacked image. This stacked image is astrometrically and 
        photometrically calibrated using either locally held catalogues or
        those queried from the CDS.
    \par Language:
        C
    \par Parameters:
        General reduction parameters:
        - \b savecat (int): If set, then catalogues for the simple images are saved
        - \b savemstd (int): If set, then matched standard catalogues are saved
        - \b skyalgo (string): The sky algorithm to be used
            - none: Don't do sky correction
            - master: Use a master sky. This must be included in the sof
            - pawsky_mask: pawsky_mask algorithm
            - pawsky_mask_pre: pawsky_mask with pre-existing object mask
            - pseudo_tilesky: useful for jitter sequences with large offsets
            - simplesky_mask: simple stack and reject with masking
            - auto: let the routine decide which routine to use
            - pawsky_minus: pawsky_minus algorithm
        - \b preview_only (bool): If set we only get a preview of the reduction
        - \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 of the 
              photometric standards
       
        Parameters for source detection on stacks:
        - \b stk_cat_ipix (int): The minimum allowable size of an object
        - \b stk_cat_thresh (float): The detection threshold in sigma above sky
        - \b stk_cat_icrowd (int): If set then the deblending software will be 
             used
        - \b stk_cat_rcore (float): The core radius in pixels
        - \b stk_cat_nbsize (int): The smoothing box size for background map 
             estimation
        
        Parameters for stacking algorithm:
        - \b stk_lthr (float): Low rejection threshold
        - \b stk_hthr (float): Upper rejection threshold
        - \b stk_method (string): The stacking method
            - nearest: Nearest neighbour
            - linear: Bi-linear interpolation
        - \b stk_seeing (bool): TRUE if we want to weight images by seeing
        - \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): If stk_fast is "auto" then this is the number
             of frames above which we switch to the slow algorithm.

        Parameters for pawsky_mask algorithm:
        - \b psm_ipix (int): The minimum allowable size of an object
        - \b psm_niter (int): The number of iterations in the algorithm
        - \b psm_thresh (float): The detection threshold in sigma above sky
        - \b psm_nbsize (int): The smoothing box size for background map estimation
        - \b psm_smkern (float): The smoothing kernel in the detection
        
    \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 OBJECT (required): The list of input science images.
        - \b SKY (optional): Offset sky observations if available
        - \b MASTER_DARK (required): A master dark frame.
        - \b MASTER_TWILIGHT_FLAT (required): A master twilight flat frame.
        - \b MASTER_SKY (optional): A master sky frame to be used for
             background correction. If this isn't available, then we
             look for offset skies. If these aren't available then we
             use the object images themselves.
        - \b MASTER_SKY_VAR (optional): A variance map for the
             master sky frame (if used)
        - \b PHOTCAL_TAB (required): A photometric calibration table
        - \b MASTER_READGAIN (required): A master read/gain table
        - \b MASTER_CONF (required): A master confidence map 
             for the filter used in the science images.
        - \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 if the CDS option is not to be 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 if the CDS option is not to be used.
        - \b MASTER_OBJMASK (optional): An object mask to be included if the
             pawsky_mask_pre or pawsky_minus algorithms are requested.
        - \b MATCHSTD_PHOTOM (optional): If this is given then this will be
             used to define the photometric solution.
        - \b SCHLEGEL_MAP_NORTH (required): The Northern Schlegel dust map
        - \b SCHLEGEL_MAP_SOUTH (required): The Southern Schlegel dust map
    \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:
        - Copies of the input science frames that have been corrected for
          instrumental signature and sky and their associated variance maps 
          (\b BASIC_CALIBRATED_SCI and BASIC_VAR_MAP)
        - Copies of the input offset sky frames that have been corrected for
          instrumental signature sky and their associated variance maps 
          (\b BASIC_CALIBRATED_SKY and BASIC_VAR_MAP_SKY)
        - A dithered stacked image, its associated confidence and variance
          maps (\b JITTERED_IMAGE_SCI, \b CONFIDENCE_MAP_JITTERED and
          \b JITTERED_VAR_IMAGE)
        - A catalogue of objects extracted from the stacked image 
          (\b OBJECT_CATALOGUE_JITTERED)
        - A mean sky frame and its variance if a master is not used
          (\b MEAN_SKY and \b MEAN_SKY_VAR)
        - 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 objects on the image in pixels
        - \b ELLIPTICITY
             The average ellipticity of stellar objects 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_process.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_process_description,
                   HAWKI_SCI_OBJECT_RAW,
                   HAWKI_OFFSET_SKY_RAW,HAWKI_CAL_DARK,
                   HAWKI_CAL_TWILIGHT_FLAT,HAWKI_CAL_SKY,HAWKI_CAL_SKY_VAR,
                   HAWKI_CAL_PHOTTAB,HAWKI_CAL_CONF,HAWKI_CAL_READGAIN,
                   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_OBJMASK,HAWKI_CAL_SCHL_N,
                   HAWKI_CAL_SCHL_S);

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_science_process",
                    "HAWKI jitter recipe",
                    alldesc,
                    "Jim Lewis",
                    "jrl@ast.cam.ac.uk",
                    hawki_get_license(),
                    hawki_science_process_create,
                    hawki_science_process_exec,
                    hawki_science_process_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_process_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 save the output catalogue for the simple images */

    p = cpl_parameter_new_value("hawki.hawki_science_process.savecat",
                                CPL_TYPE_BOOL,"Save catalogue?",
                                "hawki.hawki_science_process",FALSE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"savecat");
    cpl_parameterlist_append(recipe->parameters,p);

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

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

    /* Fill in flag to correct sky background */

    p = cpl_parameter_new_enum("hawki.hawki_science_process.skyalgo",
                               CPL_TYPE_STRING,"Sky subtraction algorithm",
                               "hawki.hawki_science_process","auto",8,
                               "master","none","pawsky_mask","pawsky_mask_pre",
                               "pseudo_tilesky","simplesky_mask","auto",
                               "pawsky_minus");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"skyalgo");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in flag to just print out how the data will be processed and 
       then exit */

    p = cpl_parameter_new_value("hawki.hawki_science_process.preview_only",
                                CPL_TYPE_BOOL,"Preview only?",
                                "hawki.hawki_science_process",FALSE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"preview_only");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in flag to just print out how the data will be processed and 
       then exit */

    p = cpl_parameter_new_range("hawki.hawki_science_process.minphotom",
                                CPL_TYPE_INT,
                                "Minimum stars for photometry solution",
                                "hawki.hawki_science_process",1,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_process.prettynames",
                                CPL_TYPE_BOOL,"Use pretty product names?",
                                "hawki.hawki_science_process",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 "none", then use the local catalogues specified in the sof. If not, 
       then use one of the selection of catalogues available from CDS */

    p = cpl_parameter_new_enum("hawki.hawki_science_process.cdssearch_astrom",
                               CPL_TYPE_STRING,
                               "CDS astrometric catalogue",
                               "hawki.hawki_science_process",
                               "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_process.cdssearch_photom",
                               CPL_TYPE_STRING,
                               "CDS photometric catalogue",
                               "hawki.hawki_science_process",
                               "none",3,"none","2mass","ppmxl");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cdssearch_photom");
    cpl_parameterlist_append(recipe->parameters,p);

    /* ***Special parameters for object detection on stacks*** */

    /* Fill in the minimum object size */

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

    /* Fill in the detection threshold parameter */

    p = cpl_parameter_new_range("hawki.hawki_science_process.stk_cat_thresh",
                                CPL_TYPE_DOUBLE,
                                "Detection threshold in sigma above sky",
                                "hawki.hawki_science_process",2.5,1.0e-6,
                                1.0e10);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_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_process.stk_cat_icrowd",
                                CPL_TYPE_BOOL,"Use deblending?",
                                "hawki.hawki_science_process",TRUE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_cat_icrowd");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Fill in core radius */

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

    /* Fill in background smoothing box size */

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

    /* ***Special parameters for stacking*** */

    /* The lower rejection threshold to be used during stacking */

    p = cpl_parameter_new_range("hawki.hawki_science_process.stk_lthr",
                                CPL_TYPE_DOUBLE,"Low rejection threshold",
                                "hawki.hawki_science_process",5.0,1.0e-6,
                                1.0e10);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_lthr");
    cpl_parameterlist_append(recipe->parameters,p);

    /* The upper rejection threshold to be used during stacking */

    p = cpl_parameter_new_range("hawki.hawki_science_process.stk_hthr",
                                CPL_TYPE_DOUBLE,"Upper rejection threshold",
                                "hawki.hawki_science_process",5.0,1.0e-6,
                                1.0e10);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_hthr");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Stacking method */

    p = cpl_parameter_new_enum("hawki.hawki_science_process.stk_method",
                               CPL_TYPE_STRING,"Stacking method",
                               "hawki.hawki_science_process","linear",2,
                               "nearest","linear");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_method");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Flag to use seeing weighting */

    p = cpl_parameter_new_value("hawki.hawki_science_process.stk_seeing",
                                CPL_TYPE_BOOL,"Weight by seeing?",
                                "hawki.hawki_science_process",FALSE);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_seeing");
    cpl_parameterlist_append(recipe->parameters,p);    

    /* Stacking speed method */

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

    /* Number of frames above which we use the slow algorithm in auto mode */

    p = cpl_parameter_new_value("hawki.hawki_science_process.stk_nfst",
                                CPL_TYPE_INT,"Nframes for slow stacking",
                                "hawki.hawki_science_process",30);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"stk_nfst");
    cpl_parameterlist_append(recipe->parameters,p);        

    /* ***Special parameters for pawsky_mask algorithm*** */

    /* The pixel area for detected objects */

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

    /* Number of iterations */

    p = cpl_parameter_new_range("hawki.hawki_science_process.psm_niter",
                                CPL_TYPE_INT,
                                "Number of iterations in pawsky mask",
                                "hawki.hawki_science_process",5,1,10);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"psm_niter");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Detection threshold */

    p = cpl_parameter_new_range("hawki.hawki_science_process.psm_thresh",
                                CPL_TYPE_DOUBLE,
                                "Detection threshold in sigma above sky",
                                "hawki.hawki_science_process",1.5,1.0e-6,
                                1.0e10);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"psm_thresh");
    cpl_parameterlist_append(recipe->parameters,p);
        
    /* Background smoothing box size */

    p = cpl_parameter_new_range("hawki.hawki_science_process.psm_nbsize",
                                CPL_TYPE_INT,"Background smoothing box size",
                                "hawki.hawki_science_process",128,1,2048);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"psm_nbsize");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Smoothing kernel size */

    p = cpl_parameter_new_range("hawki.hawki_science_process.psm_smkern",
                                CPL_TYPE_DOUBLE,
                                "Smoothing kernel size (pixels)",
                                "hawki.hawki_science_process",2.0,1.0e-6,5.0);
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"psm_smkern");
    cpl_parameterlist_append(recipe->parameters,p);

    /* Location for standard star cache */

    p = cpl_parameter_new_value("hawki.hawki_science_process.cacheloc",
                                CPL_TYPE_STRING,
                                "Location for standard star cache",
                                "hawki.hawki_science_process",".");
    cpl_parameter_set_alias(p,CPL_PARAMETER_MODE_CLI,"cacheloc");
    cpl_parameterlist_append(recipe->parameters,p);        

    /* Magnitude errror cut */

    p = cpl_parameter_new_value("hawki.hawki_science_process.magerrcut",
                                CPL_TYPE_DOUBLE,
                                "Magnitude error cut",
                                "hawki.hawki_science_process",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_process_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_process(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_process_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_process(cpl_parameterlist *parlist,
                                 cpl_frameset *framelist) {
    const char *fctid="hawki_science_process";
    cpl_parameter *p;
    int nfail,status,i,ndit,j,live,isfirst,nsci,njsteps,n,match,runno;
    cpl_size nlab;
    float gaincor_fac,dit,readnoise,gain;
    cpl_frame *catindex_a,*catindex_p,*template,*frm;
    cpl_propertylist *pp,*epp;
    cpl_table *stdscat,*tab,*t;
    casu_fits *ff,*ffv;
    char filt[16],projid[16],*fname,*junk1,*junk2,pcat[32],*assoc[2];
    char photosys[8];
    configstruct cs;
    memstruct ps;
    cpl_frameset *prod_im,*prod_tab;

    /* 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_init(&ps);

    /* Get the parameters */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.savecat");
    cs.savecat = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.savemstd");
    cs.savemstd = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.skyalgo");
    if (! strcmp(cpl_parameter_get_string(p),"none"))
        cs.skyalgo = SKYNONE;
    else if (! strcmp(cpl_parameter_get_string(p),"master"))
        cs.skyalgo = SKYMASTER;
    else if (! strcmp(cpl_parameter_get_string(p),"pawsky_mask"))
        cs.skyalgo = PAWSKY_MASK;
    else if (! strcmp(cpl_parameter_get_string(p),"pawsky_mask_pre"))
        cs.skyalgo = PAWSKY_MASK_PRE;
    else if (! strcmp(cpl_parameter_get_string(p),"pseudo_tilesky"))
        cs.skyalgo = PSEUDO_TILESKY;
    else if (! strcmp(cpl_parameter_get_string(p),"auto"))
        cs.skyalgo = AUTO;
    else if (! strcmp(cpl_parameter_get_string(p),"pawsky_minus"))
        cs.skyalgo = PAWSKY_MINUS;
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.preview_only");
    cs.preview_only = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.minphotom");
    cs.minphotom = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.prettynames");
    cs.prettynames = (cpl_parameter_get_bool(p) ? 1 : 0);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.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;
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.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;

    /* Stacking special parameters */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_lthr");
    cs.stk_lthr = cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_hthr");
    cs.stk_hthr = cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_method");
    cs.stk_method = (! strcmp(cpl_parameter_get_string(p),"nearest") ? 0 : 1);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_seeing");
    cs.stk_seeing = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.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;
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_nfst");
    cs.stk_nfst = cpl_parameter_get_int(p);

    /* Object detection on stacks special parameters */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_cat_ipix");
    cs.stk_cat_ipix = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_cat_thresh");
    cs.stk_cat_thresh = (float)cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_cat_icrowd");
    cs.stk_cat_icrowd = cpl_parameter_get_bool(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_cat_rcore");
    cs.stk_cat_rcore = (float)cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.stk_cat_nbsize");
    cs.stk_cat_nbsize = cpl_parameter_get_int(p);

    /* Pawsky_mask special parameters */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.psm_ipix");
    cs.psm_ipix = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.psm_niter");
    cs.psm_niter = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.psm_thresh");
    cs.psm_thresh = cpl_parameter_get_double(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.psm_nbsize");
    cs.psm_nbsize = cpl_parameter_get_int(p);
    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.psm_smkern");
    cs.psm_smkern = cpl_parameter_get_double(p);

    /* Cache location */

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

    /* Magnitude error cut */

    p = cpl_parameterlist_find(parlist,
                               "hawki.hawki_science_process.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_tidy(&ps,0);
        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_tidy(&ps,0);
        return(-1);
    }

    /* Get the input science frames */

    nfail = 0;
    if ((ps.science_frames = 
        casu_frameset_subgroup(framelist,ps.labels,nlab,
                               HAWKI_SCI_OBJECT_RAW)) == NULL) {
        cpl_msg_error(fctid,"No science images to process!");
        hawki_sci_tidy(&ps,0);
        return(-1);
    }
    nfail += hawki_testfrms(ps.science_frames,HAWKI_NEXTN,1,1);

    /* Get the offset sky frames */
    
    if ((ps.master_sky = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                  HAWKI_CAL_SKY)) == NULL) {        
        if ((ps.offset_skies = casu_frameset_subgroup(framelist,ps.labels,nlab,
                                                      HAWKI_OFFSET_SKY_RAW)) != NULL)
            nfail += hawki_testfrms(ps.offset_skies,HAWKI_NEXTN,1,1);
    } else {
        cs.skyalgo = -1;
        nfail += hawki_testfrm_1(ps.master_sky,HAWKI_NEXTN,1,0);
    }

    /* If a master sky is being used, then get the variance frame for it */

    if (ps.master_sky != NULL) {
        ps.master_sky_var = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                     HAWKI_CAL_SKY_VAR);
        if (ps.master_sky_var == NULL) {
            cpl_msg_error(fctid,"No master sky variance file");
            hawki_sci_tidy(&ps,0);
            return(-1);
        }
        nfail += hawki_testfrm_1(ps.master_sky_var,HAWKI_NEXTN,1,0);
    }
        
    /* Check to see if there is a master dark frame */

    if ((ps.master_dark = 
        casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                 HAWKI_CAL_DARK)) == NULL) {
        cpl_msg_error(fctid,"No master dark found");
        hawki_sci_tidy(&ps,0);
        return(-1);
    }
    nfail += hawki_testfrm_1(ps.master_dark,HAWKI_NEXTN,1,0);
        
    /* Check to see if there is a master twilight flat frame */

    if ((ps.master_twilight_flat = 
        casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                 HAWKI_CAL_TWILIGHT_FLAT)) == NULL) {
        cpl_msg_error(fctid,"No master twilight flat found");
        hawki_sci_tidy(&ps,0);
        return(-1);
    }
    nfail += hawki_testfrm_1(ps.master_twilight_flat,HAWKI_NEXTN,1,0);

    /* Get the master object mask. Does it make sense to have one in
       the context of the sky correction algorithm? */
    
    ps.master_objmask = casu_frameset_subgroup_1(framelist,ps.labels,
                                                 nlab,HAWKI_CAL_OBJMASK);
    if ((ps.master_objmask == NULL) && 
        (cs.skyalgo == PAWSKY_MASK_PRE)) {
        cpl_msg_error(fctid,
                      "No object mask found. Unable to use requested sky algorithm");
        hawki_sci_tidy(&ps,0);
        return(-1);
    } else if (ps.master_objmask != NULL && cs.skyalgo == PAWSKY_MASK) {
        cpl_msg_info(fctid,
                     "Requested sky subt algorithm requires no object mask");
    }
    nfail += hawki_testfrm_1(ps.master_objmask,0,1,1);

    /* 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,HAWKI_NEXTN,0,0);

    /* Get the gain corrections */

    status = CASU_OK;
    if (casu_gaincor_calc(ps.master_twilight_flat,&i,&(ps.gaincors),
                          &status) != CASU_OK) {
        cpl_msg_error(fctid,"Error calculating gain corrections");
        hawki_sci_tidy(&ps,0);
        return(-1);
    }

    /* 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_tidy(&ps,0);
        return(-1);
    }
    nfail += hawki_testfrm_1(ps.master_conf,HAWKI_NEXTN,1,0);
    ps.mask = casu_mask_define(framelist,ps.labels,nlab,HAWKI_CAL_CONF,
                               "NONE");

    /* Check to see if there is a master read/gain table */

    if ((ps.readgain = 
        casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                 HAWKI_CAL_READGAIN)) == NULL) {
        cpl_msg_error(fctid,"No master readgain table file found");
        hawki_sci_tidy(&ps,0);
        return(-1);
    }
    nfail += hawki_testrdgn(ps.master_conf,ps.readgain);

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

    catindex_a = NULL;
    if (cs.cdssearch_astrom == 0) {
        if ((catindex_a = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                   HAWKI_CAL_2MASS_A)) == NULL) {
            if ((catindex_a = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                       HAWKI_CAL_PPMXL_A)) == NULL) {
                if ((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_tidy(&ps,0);
                    return(-1);
                }
            }
        }
        nfail += hawki_testfrm_1(catindex_a,1,0,0);
    } else {
        (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? */

    catindex_p = NULL;
    if (cs.cdssearch_photom == 0 && ps.master_mstd_phot == NULL) {
        if ((catindex_p = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                   HAWKI_CAL_2MASS_P)) == NULL) {
            if ((catindex_p = casu_frameset_subgroup_1(framelist,ps.labels,nlab,
                                                       HAWKI_CAL_PPMXL_P)) == NULL) {
                if ((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_tidy(&ps,0);
                    return(-1);
                }
            }
        }
        nfail += hawki_testfrm_1(catindex_p,1,0,0);
        pp = cpl_propertylist_load(cpl_frame_get_filename(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(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 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_tidy(&ps,0);
        return(-1);
    }
    n = cpl_frame_get_nextensions(ps.phottab);
    nfail += hawki_testfrm_1(ps.phottab,n,0,0);

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

    match = 0;
    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) {
            if (cpl_propertylist_has(pp,"PHOTOSYS")) {
                strncpy(photosys,cpl_propertylist_get_string(pp,"PHOTOSYS"),8-1);
                photosys[8-1] = '\0';
            } else {
                cpl_msg_error(fctid,
                              "Photcal table missing PHOTOSYS information in extension %d",i);
                cpl_propertylist_delete(pp);
                break;
            }
            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_tidy(&ps,0);
        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_tidy(&ps,0);
        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);
        freeframe(catindex_a);
        freeframe(catindex_p);
        hawki_sci_tidy(&ps,0);
        return(-1);
    }
    
    /* Get catalogue parameters */

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

    /* Get the number of DITs. This assumes it is the same for all the science
       images (and it damn well better be!) */

    pp = cpl_propertylist_load(cpl_frame_get_filename(cpl_frameset_get_position(ps.science_frames,0)),0);
    if (hawki_pfits_get_ndit(pp,&ndit) != CASU_OK) {
        cpl_msg_error(fctid,"No value for NDIT available");
        freepropertylist(pp);
        hawki_sci_tidy(&ps,0);
        return(-1);
    }
    cpl_propertylist_delete(pp);

    /* Define the pawprint now */

    runno = 0;
    hawki_sci_paw_init(&(ps.scipaw),ps.science_frames,&cs,&runno);
    if (ps.offset_skies != NULL) 
        hawki_sci_paw_init(&(ps.offpaw),ps.offset_skies,&cs,&runno);

    /* Define the skys that will be made */

    hawki_sci_skydefine(&(ps.nskys),&(ps.skys),ps.master_sky,
                        ps.master_sky_var,ps.scipaw,ps.offpaw,ps.master_objmask,
                        cs.skyalgo,cs.prettynames);

    /* Print out some stuff if you want a preview */

    if (cs.preview_only) {
        fprintf(stdout,"This is a paw group with %" CPL_SIZE_FORMAT " files\n",
                cpl_frameset_get_size(ps.science_frames));
        frm = cpl_frameset_get_position(ps.science_frames,0);
        pp = cpl_propertylist_load(cpl_frame_get_filename(frm),0);
        (void)hawki_pfits_get_filter(pp,filt);
        (void)hawki_pfits_get_ndit(pp,&ndit);
        (void)hawki_pfits_get_dit(pp,&dit);
        (void)hawki_pfits_get_projid(pp,projid);
        cpl_propertylist_delete(pp);
        njsteps = cpl_frameset_get_size(ps.science_frames);

        fprintf(stdout,"Filter: %8s, DIT: %8.1f, NDIT: %" CPL_SIZE_FORMAT ", Project: %s\n",filt,
                dit,(cpl_size)ndit,projid);
        fprintf(stdout,"Dark: %s\n",cpl_frame_get_filename(ps.master_dark));
        fprintf(stdout,"Flat: %s\n",
                cpl_frame_get_filename(ps.master_twilight_flat));
        fprintf(stdout,"CPM: %s\n",cpl_frame_get_filename(ps.master_conf));
        fprintf(stdout,"Read/gain: %s\n",
                cpl_frame_get_filename(ps.readgain));
        fprintf(stdout,"A %" CPL_SIZE_FORMAT " point dither will be done\n",
                (cpl_size)njsteps);
        n = cpl_frameset_get_size(ps.scipaw->orig);
        for (j = 0; j < n; j++) {
            frm = cpl_frameset_get_position(ps.scipaw->orig,j);
            fprintf(stdout,"%s     sky%" CPL_SIZE_FORMAT "\n",
                    cpl_frame_get_filename(frm),
                    (cpl_size)(ps.scipaw->whichsky[j]+1));
        }
        if (ps.offpaw != NULL) {
            fprintf(stdout,"There are %" CPL_SIZE_FORMAT " offset sky frames\n",
                    cpl_frameset_get_size(ps.offset_skies));
            n = cpl_frameset_get_size(ps.offpaw->orig);
            for (j = 0; j < n; j++) {
                frm = cpl_frameset_get_position(ps.offpaw->orig,j);
                fprintf(stdout,"%s     sky%" CPL_SIZE_FORMAT "\n",
                        cpl_frame_get_filename(frm),
                        (cpl_size)(ps.offpaw->whichsky[j]+1));
            }
        }
        p = cpl_parameterlist_find(parlist,
                                   "hawki.hawki_science_process.skyalgo");
        fprintf(stdout,"\nRequested sky algorithm: %s\n",
                cpl_parameter_get_string(p));
        for (i = 0; i < ps.nskys; i++) {
            if (ps.skys[i].skyalgo == SKYMASTER) {
                fprintf(stdout,"Master sky %s will be used\n",
                        cpl_frame_get_filename(ps.master_sky));
            } else {
                fprintf(stdout,"Sky%" CPL_SIZE_FORMAT ": %s ",
                        (cpl_size)(i+1),skyalstr[ps.skys[i].skyalgo]);
                if (ps.skys[i].objmask != NULL)
                    fprintf(stdout,"mask: %s\nFormed from:\n",
                            cpl_frame_get_filename(ps.skys[i].objmask));
                else
                    fprintf(stdout,"\nFormed from:\n");
                for (j = 0; j < cpl_frameset_get_size(ps.skys[i].contrib); j++) {
                    frm = cpl_frameset_get_position(ps.skys[i].contrib,j);
                    fprintf(stdout,"    %s\n",cpl_frame_get_filename(frm));
                }
            }
        }
        if (cs.cdssearch_astrom != 0) {
            p = cpl_parameterlist_find(parlist,
                                       "hawki.hawki_science_process.cdssearch_astrom");
            fprintf(stdout,"Astrometry will be done using CDS catalogue: %s\n",
                    cpl_parameter_get_string(p));
        } else {
            fprintf(stdout,"Astrometry will be done using local catalogue %s in %s\n",
                    ps.catname_a,ps.catpath_a);
        }
        if (ps.master_mstd_phot != NULL) {
            fprintf(stdout,"Photometry will be done using master mstd: %s\n",
                    cpl_frame_get_filename(ps.master_mstd_phot));
        } else if (cs.cdssearch_photom != 0) {
            p = cpl_parameterlist_find(parlist,
                                       "hawki.hawki_science_process.cdssearch_photom");
            fprintf(stdout,"Photometry will be done using CDS catalogue: %s\n",
                    cpl_parameter_get_string(p));
        } else {
            fprintf(stdout,"Photometry will be done using local catalogue %s in %s\n",
                    ps.catname_p,ps.catpath_p);
        }
        hawki_sci_tidy(&ps,0);
        return(0);
    }

    /* Get some workspace for the product frames */

    ps.nscience = cpl_frameset_get_size(ps.science_frames);
    nsci = ps.nscience;
    ps.product_frames_simple = cpl_malloc(ps.nscience*sizeof(cpl_frame *));
    ps.product_frames_simple_var = cpl_malloc(ps.nscience*sizeof(cpl_frame *));
    for (i = 0; i < ps.nscience; i++) {
        ps.product_frames_simple[i] = NULL;
        ps.product_frames_simple_var[i] = NULL;
    }
    if (ps.offset_skies != NULL) {
        ps.noffsets = cpl_frameset_get_size(ps.offset_skies);
        ps.product_frames_simple_off = 
            cpl_malloc(ps.noffsets*sizeof(cpl_frame *));
        ps.product_frames_simple_off_var = 
            cpl_malloc(ps.noffsets*sizeof(cpl_frame *));
        for (i = 0; i < ps.noffsets; i++) {
            ps.product_frames_simple_off[i] = NULL;
            ps.product_frames_simple_off_var[i] = NULL;
        }
    } else {
        ps.noffsets = 0;
    }

    /* Do the stage1 processing. Loop for all extensions */

    cpl_msg_info(fctid,"Beginning stage1 reduction");
    cpl_msg_indent_more();
    for (j = 1; j <= HAWKI_NEXTN; j++) {
        isfirst = (j == 1);
        gaincor_fac = (ps.gaincors)[j-1];
        cpl_msg_info(fctid,"Extension [%" CPL_SIZE_FORMAT "]",(cpl_size)j);
        
        /* Load up the calibration frames. We know these won't fail now
           so we can skip all the boring testing... */
        
        ps.fdark = casu_fits_load(ps.master_dark,CPL_TYPE_FLOAT,j);
        ps.fflat = casu_fits_load(ps.master_twilight_flat,CPL_TYPE_FLOAT,j);
        ps.fconf = casu_fits_load(ps.master_conf,CPL_TYPE_INT,j);
        casu_mask_load(ps.mask,j,
                       (int)cpl_image_get_size_x(casu_fits_get_image(ps.fconf)),
                       (int)cpl_image_get_size_y(casu_fits_get_image(ps.fconf)));

        /* Get the master sky, the readnoise and gain. */

        if (ps.master_sky != NULL) 
            ps.fsky = casu_fits_load(ps.master_sky,CPL_TYPE_FLOAT,j);
        hawki_getrdgn(ps.readgain,casu_fits_get_extname(ps.fflat),
                      &readnoise,&gain);
        ps.fdark_var = hawki_var_create(ps.fdark,ps.mask,readnoise,gain);

        /* Now load up the science frames and offset skies, if needed */
        
        ps.nscience = cpl_frameset_get_size(ps.science_frames);
        ps.sci_fits = casu_fits_load_list(ps.science_frames,CPL_TYPE_FLOAT,j);
        if (ps.offset_skies != NULL) {
            ps.noffsets = cpl_frameset_get_size(ps.offset_skies);
            ps.offsky_fits = casu_fits_load_list(ps.offset_skies,
                                                 CPL_TYPE_FLOAT,j);
        } else {
            ps.noffsets = 0;
            ps.offsky_fits = NULL;
        }
           
        /* Loop through and mark the frames where the header says the detector
           wasn't live. Check for live detectors in the offset skies too */

        for (i = 0; i < ps.nscience; i++) {
            ff = ps.sci_fits[i];
            hawki_pfits_get_detlive(casu_fits_get_ehu(ff),&live);
            if (! live) 
                casu_fits_set_error(ff,CASU_FATAL);
        }
        for (i = 0; i < ps.noffsets; i++) {
            ff = ps.offsky_fits[i];
            hawki_pfits_get_detlive(casu_fits_get_ehu(ff),&live);
            if (! live) 
                casu_fits_set_error(ff,CASU_FATAL);
        }

        /* Do the corrections now. First for the object frames. Write
           out intermediate versions as we go */
       
        assoc[0] = NULL;
        assoc[1] = NULL;
        for (i = 0; i < ps.nscience; i++) {
            ff = ps.sci_fits[i];
            ffv = hawki_var_create(ff,ps.mask,readnoise,gain);
            if (casu_fits_get_status(ff) == CASU_FATAL) {
                cpl_msg_info(fctid,"Detector is flagged dead in %s",
                             casu_fits_get_fullname(ff));
            } else {
                hawki_updatewcs(casu_fits_get_ehu(ff));
                status = CASU_OK;
                (void)casu_darkcor(ff,ps.fdark,1.0,&status);
                hawki_var_add(ffv,ps.fdark_var);
                (void)casu_flatcor(ff,ps.fflat,&status);
                hawki_var_div_im(ffv,ps.fflat);
                (void)casu_gaincor(ff,gaincor_fac,&status);
                hawki_var_divk(ffv,gaincor_fac);
                casu_fits_set_error(ff,status);
            }
            cpl_propertylist_update_float(casu_fits_get_ehu(ff),"READNOIS",
                                          readnoise);
            cpl_propertylist_set_comment(casu_fits_get_ehu(ff),"READNOIS",
                                         "[ADU] Read noise used in reduction");
            cpl_propertylist_update_float(casu_fits_get_ehu(ff),"GAIN",
                                    gain);
            cpl_propertylist_set_comment(casu_fits_get_ehu(ff),"GAIN",
                                         "[e-/ADU] gain used in reduction");
            frm = cpl_frameset_get_position(ps.scipaw->current,i);
            fname = (char *)cpl_frame_get_filename(frm);
            hawki_sci_save_simple(ff,framelist,parlist,0,
                                  cpl_frameset_get_position(ps.science_frames,i),
                                  isfirst,HAWKI_PRO_SIMPLE_SCI,fname,assoc[0],
                                  ps.product_frames_simple+i);

            /* Save the variance map */

            frm = cpl_frameset_get_position(ps.scipaw->current_var,i);
            fname = (char *)cpl_frame_get_filename(frm);
            hawki_sci_save_simple(ffv,framelist,parlist,0,
                                  cpl_frameset_get_position(ps.science_frames,i),
                                  isfirst,HAWKI_PRO_VAR_SCI,fname,assoc[0],
                                  ps.product_frames_simple_var+i);
            freefits(ffv);
        }
        for (i = 0; i < ps.noffsets; i++) {
            ff = ps.offsky_fits[i];
            ffv = hawki_var_create(ff,ps.mask,readnoise,gain);
            if (casu_fits_get_status(ff) == CASU_FATAL) {
                cpl_msg_info(fctid,"Detector is flagged dead in %s",
                             casu_fits_get_fullname(ff));
            } else {
                hawki_updatewcs(casu_fits_get_ehu(ff));
                status = CASU_OK;
                (void)casu_darkcor(ff,ps.fdark,1.0,&status);
                hawki_var_add(ffv,ps.fdark_var);
                (void)casu_flatcor(ff,ps.fflat,&status);
                hawki_var_div_im(ffv,ps.fflat);
                (void)casu_gaincor(ff,gaincor_fac,&status);
                hawki_var_divk(ffv,gaincor_fac);
                casu_fits_set_error(ff,status);
            }
            frm = cpl_frameset_get_position(ps.offpaw->current,i);
            fname = (char *)cpl_frame_get_filename(frm);
            hawki_sci_save_simple(ff,framelist,parlist,0,
                                  cpl_frameset_get_position(ps.offset_skies,i),
                                  isfirst,HAWKI_PRO_SIMPLE_SKY,fname,assoc[0],
                                  ps.product_frames_simple_off+i);

            /* Save the variance maps */

            frm = cpl_frameset_get_position(ps.offpaw->current_var,i);
            fname = (char *)cpl_frame_get_filename(frm);
            hawki_sci_save_simple(ffv,framelist,parlist,0,
                                  cpl_frameset_get_position(ps.offset_skies,i),
                                  isfirst,HAWKI_PRO_VAR_SKY,fname,assoc[0],
                                  ps.product_frames_simple_off_var+i);
            freefits(ffv);
       }

        /* Intermediate tidy */

        hawki_sci_tidy(&ps,1);
    }
    for (i = 0; i < nsci; i++)  {
        freeframe(ps.product_frames_simple[i]);
        freeframe(ps.product_frames_simple_var[i]);
    }
    for (i = 0; i < ps.noffsets; i++) {
        freeframe(ps.product_frames_simple_off[i]);
        freeframe(ps.product_frames_simple_off_var[i]);
    }
    cpl_msg_indent_less();

    /* Form the sky frames. These are saved and registered as products */

    cpl_msg_info(fctid,"Making skies");
    for (i = 0; i < ps.nskys; i++) {
        if (hawki_sci_makesky(framelist,parlist,&(ps.skys[i]),ps.master_objmask,
                              ps.master_conf,ps.mask,&cs,&ps) != CASU_OK) {
            cpl_msg_error(fctid,"Error making sky %" CPL_SIZE_FORMAT "",
                          (cpl_size)i);
            hawki_sci_tidy(&ps,0);
            return(-1);
        }
    }

    /* Do sky subtraction, wcsfit and write them out */

    cpl_msg_info(fctid,"Beginning stage2 reduction");
    prod_im = cpl_frameset_new();
    if (cs.savecat) 
        prod_tab = cpl_frameset_new();
    else
        prod_tab = NULL;
    (void)hawki_sci_sky_wcs(ps.scipaw,0,ps.mask,ps.nskys,
                            ps.skys,ps.master_conf,framelist,parlist,
                            ps.catname_a,ps.catpath_a,cs.cacheloc,&cs,photosys,
                            prod_im,prod_tab);
    if (ps.offpaw != NULL)
        (void)hawki_sci_sky_wcs(ps.offpaw,1,ps.mask,
                                ps.nskys,ps.skys,ps.master_conf,framelist,
                                parlist,ps.catname_a,ps.catpath_a,cs.cacheloc,
                                &cs,photosys,prod_im,prod_tab);

    /* Loop for all science jitters. Jitter, WCSFIT, photcal and write out */

    cpl_msg_info(fctid,"Beginning group processing");
    cpl_msg_indent_more();
    for (j = 1; j <= HAWKI_NEXTN; j++) {
        cpl_msg_info(fctid,
                     "Stacking paw[%" CPL_SIZE_FORMAT "]",(cpl_size)j);
        hawki_sci_stack(ps.scipaw,ps.master_conf,j,&cs);
        cpl_msg_info(fctid,
                     "Source extraction paw[%" CPL_SIZE_FORMAT "]",(cpl_size)j);
        hawki_sci_cat(ps.scipaw,j,&cs);
    }
    cpl_msg_indent_less();
    cpl_msg_info(fctid,"WCS fit paw");
    hawki_wcsfit_multi(ps.scipaw->stack,ps.scipaw->cat,ps.catname_a,
                       ps.catpath_a,cs.cdssearch_astrom,cs.cacheloc,cs.savemstd,
                       ps.scipaw->mstds_a);
    for (j = 1; j <= HAWKI_NEXTN; j++) {
        hawki_copywcs(casu_fits_get_ehu(ps.scipaw->stack[j-1]),
                      casu_fits_get_ehu(ps.scipaw->stackv[j-1]));
        hawki_copywcs(casu_fits_get_ehu(ps.scipaw->stack[j-1]),
                      casu_fits_get_ehu(ps.scipaw->stackc[j-1]));
        if (casu_fits_get_status(ps.scipaw->stack[j-1]) != CASU_OK) {
            ps.scipaw->mstds_p[j-1] = NULL;
        } else if (ps.master_mstd_phot != NULL) {
            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);
            ps.scipaw->mstds_p[j-1] = casu_tfits_wrap(t,NULL,pp,epp);
        } else {
            (void)casu_getstds(casu_fits_get_ehu(ps.scipaw->stack[j-1]),
                               1,ps.catpath_p,ps.catname_p,cs.cdssearch_photom,
                               cs.cacheloc,&stdscat,&status);
            (void)casu_matchstds(casu_tfits_get_table(ps.scipaw->cat[j-1]),
                                 stdscat,300.0,&tab,&status);
            if (status != CASU_OK) {
                ps.scipaw->mstds_p[j-1] = NULL;
            } else {
                ps.scipaw->mstds_p[j-1] = casu_tfits_wrap(tab,
                                                          ps.scipaw->cat[j-1],
                                                          NULL,NULL);
                cpl_propertylist_update_string(casu_tfits_get_phu(ps.scipaw->mstds_p[j-1]),
                                               "PHOTCAT",pcat);
                cpl_propertylist_set_comment(casu_tfits_get_phu(ps.scipaw->mstds_p[j-1]),
                                             "PHOTCAT",
                                             "Originating photometry source");
            }
            freetable(stdscat);
        }
    }
    cpl_msg_info(fctid,"Doing photometric calibriation on paw");
    hawki_sci_photcal(ps.scipaw,ps.tphottab,ps.schlf_n,ps.schlf_s,cs.minphotom,
                      cs.magerrcut);
    template = cpl_frame_duplicate(cpl_frameset_get_position(ps.scipaw->orig,0));
    /* Update the headers of the stacks so that the RA, Dec in the primary
       reflects the true central coordinates of the all four images */

    hawki_sci_update_hdr_stack(ps.scipaw);
    
    /* Fall back to DIMM FWHM in case there are not enough stars to compute it */
    hawki_sci_update_fwhm_dimm(ps.scipaw);

    /* Save the pawprint */

    cpl_msg_info(fctid,"Saving paw");
    cpl_msg_indent_more();
    for (j = 1; j <= HAWKI_NEXTN; j++) {
        cpl_msg_info(fctid,"Saving confidence map paw[%" CPL_SIZE_FORMAT "]",
                     (cpl_size)j);
        hawki_sci_save_stack_conf(ps.scipaw->stackc[j-1],framelist,parlist,
                                  template,cs.prettynames,
                                  &(ps.scipaw->product_frame_conf));
        if (j == 1) 
            assoc[0] = cpl_strdup(cpl_frame_get_filename(ps.scipaw->product_frame_conf));
        cpl_msg_info(fctid,"Saving catalogue paw[%" CPL_SIZE_FORMAT "]",
                     (cpl_size)j);
        hawki_sci_save_cat(ps.scipaw->cat[j-1],framelist,parlist,
                           template,cs.prettynames,STACK_CAT,1,photosys,
                           &(ps.scipaw->product_frame_cat));
        cpl_msg_info(fctid,"Saving var paw[%" CPL_SIZE_FORMAT "]",
                     (cpl_size)j);
        hawki_sci_save_stack(ps.scipaw->stackv[j-1],framelist,parlist,
                             template,cs.prettynames,STACK_VAR,assoc,
                             photosys,&(ps.scipaw->product_frame_var));
        if (j == 1) 
            assoc[1] = cpl_strdup(cpl_frame_get_filename(ps.scipaw->product_frame_var));
        cpl_msg_info(fctid,"Saving image paw[%" CPL_SIZE_FORMAT "]",
                     (cpl_size)j);
        hawki_sci_save_stack(ps.scipaw->stack[j-1],framelist,parlist,
                             template,cs.prettynames,STACK_FILE,assoc,
                             photosys,&(ps.scipaw->product_frame_im));
        hawki_sci_update_catprov(ps.scipaw->product_frame_cat,ps.scipaw->product_frame_im);
        if (cs.savemstd) {
            cpl_msg_info(fctid,"Saving matched stds[%" CPL_SIZE_FORMAT "]",
                         (cpl_size)j);
            hawki_sci_save_cat(ps.scipaw->mstds_a[j-1],framelist,parlist,
                               template,cs.prettynames,STACK_MSTD_ASTROM,1,
                               photosys,&(ps.scipaw->product_frame_mstd_a));
            hawki_sci_save_cat(ps.scipaw->mstds_p[j-1],framelist,parlist,
                               template,cs.prettynames,MSTD_PHOTOM,1,
                               photosys,&(ps.scipaw->product_frame_mstd_p));
        }
    }

    /* Now update the headers of the simple images to reflect the 
       photometric calibration of the stack */

    hawki_sci_update_hdr(prod_im,ps.scipaw->product_frame_im,1);
    cpl_frameset_delete(prod_im);
    if (prod_tab != NULL) {
        hawki_sci_update_hdr(prod_tab,ps.scipaw->product_frame_im,2);
        cpl_frameset_delete(prod_tab);
    }

    /* Clean up and exit*/

    freespace(assoc[0]);
    freespace(assoc[1]);
    freeframe(template);
    hawki_sci_paw_delete(&(ps.scipaw));
    hawki_sci_paw_delete(&(ps.offpaw));
    cpl_msg_indent_less();
    hawki_sci_tidy(&ps,0);

    /* Propagate adaptive optic extensions if present */
    hawki_propagate_aoextensions(framelist, HAWKI_SCI_OBJECT_RAW,
                                 HAWKI_PRO_SIMPLE_SCI);

    return(0);
}

/*=========================== Support Routines ==============================*/

/*=========================== Pawprint Structure Routines ===================*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_paw_delete
    \par Purpose:
        Routine to free up workspace in a pawprint structure
    \par Description:
        Free up workspace in a pawprint structure.
    \par Language:
        C
    \param paw
        The input pawprint structure.
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_paw_delete(pawprint **paw) {
    int i;

    if (*paw == NULL)
        return;
    freespace((*paw)->whichsky);
    freeframeset((*paw)->orig);
    freeframeset((*paw)->current);
    freeframeset((*paw)->current_var);
    for (i = 0; i < HAWKI_NEXTN; i++) {
        freefits((*paw)->stack[i]);
        freefits((*paw)->stackc[i]);
        freefits((*paw)->stackv[i]);
        freetfits((*paw)->cat[i]);
        freetfits((*paw)->mstds_p[i]);
        freetfits((*paw)->mstds_a[i]);
    }
    freespace(*paw);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_paw_init
    \par Purpose:
        Routine to set up a pawprint structure 
    \par Description:
        A pawprint structure is set up to contain the information of the
        input data pawprint
    \par Language:
        C
    \param paw
        The input pawprint structure.
    \param frms
        The frameset containing the input pawprint
    \param cs
        The recipe configuration structure
    \param runno
        A starting run number for the ESO predictable file names
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_paw_init(pawprint **paw, cpl_frameset *frms, 
                               configstruct *cs, int *runno) {
    cpl_propertylist *plist;
    cpl_frame *fr;
    float exptime;
    int i,n;

    /* Initialise everything. Start with all the frames and framesets */

    *paw = cpl_malloc(sizeof(pawprint));
    (*paw)->orig = cpl_frameset_duplicate(frms);
    (*paw)->current = hawki_sci_update_frameset((*paw)->orig,cs,SIMPLE_FILE,
                                                runno);
    (*paw)->current_var = hawki_sci_update_frameset((*paw)->orig,cs,
                                                    SIMPLE_VAR,runno);
    n = cpl_frameset_get_size(frms);
    *runno += n;
    (*paw)->whichsky = cpl_malloc(n*sizeof(int));
    for (i = 0; i < n; i++)
        (*paw)->whichsky[i] = -1;
    for (i = 0; i < HAWKI_NEXTN; i++) {
        (*paw)->stack[i] = NULL;
        (*paw)->stackc[i] = NULL;
        (*paw)->stackv[i] = NULL;
        (*paw)->cat[i] = NULL;
        (*paw)->mstds_a[i] = NULL;
        (*paw)->mstds_p[i] = NULL;
    }
    (*paw)->product_frame_im = NULL;
    (*paw)->product_frame_conf = NULL;
    (*paw)->product_frame_cat = NULL;
    (*paw)->product_frame_var = NULL;
    (*paw)->product_frame_mstd_a = NULL;
    (*paw)->product_frame_mstd_p = NULL;

    /* Now get some useful information about the pawprint from the headers.
       Start with the jitter numbers */

    fr = cpl_frameset_get_position(frms,0);
    plist = cpl_propertylist_load(cpl_frame_get_filename(fr),0);

    /* The MJD of the start of the jitter and of the end */

    (void)hawki_pfits_get_mjd(plist,&((*paw)->mjd_start));
    cpl_propertylist_delete(plist);
    n = (int)cpl_frameset_get_size(frms);
    fr = cpl_frameset_get_position(frms,n-1);
    plist = cpl_propertylist_load(cpl_frame_get_filename(fr),0);
    (void)hawki_pfits_get_mjd(plist,&((*paw)->mjd_end));
    (void)hawki_pfits_get_exptime(plist,&exptime);
    (*paw)->mjd_end += exptime/(24.0*3600.0);

    /* Tidy and exit */

    cpl_propertylist_delete(plist);
}


/*=========================== Processing Routines ===========================*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_photcal
    \par Purpose:
        Driver routine to do photometric calibration
    \par Description:
        The photometric calibration for a given pawprint is done. The
        stack, a source catalogue from the stack and a matched standards
        catalogue must already exist.
    \par Language:
        C
    \param paw
        The input pawprint structure.
    \param tphottab
        The table with the pre-defined photometric transformations
    \param schlf_n
        The Northern Schlegel map
    \param schlf_s
        The Southern Schlegel map
    \param minphotom
        The minimum number of stars required for a photometric solution
    \param magerrcut
        A cut in the magnitude error to impose on the standards
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_photcal(pawprint *paw, cpl_table *tphottab,
                              cpl_frame *schlf_n, cpl_frame *schlf_s,
                              int minphotom, float magerrcut) {
    char filt[16];
    int status;

    /* Get the filter name for the pawprint */

    hawki_pfits_get_filter(casu_fits_get_phu(paw->stack[0]),filt);

    /* Call the routine */
    
    status = CASU_OK;
    casu_photcal_extinct(paw->stack,paw->mstds_p,paw->cat,HAWKI_NEXTN,filt,
                         tphottab,minphotom,schlf_n,schlf_s,"EXPTIME",
                         "ESO TEL AIRM START",magerrcut,&status);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_sky_wcs
    \par Purpose:
        Subtract sky and fit a WCS to each image in a pawprint
    \par Description:
        Each image in a pawprint structure has a sky estimate removed.
        A WCS is also fit to each image. Finally each image is written out
    \par Language:
        C
    \param paw
        The input pawprint structure.
    \param isoffsky
        If set then these are offset sky images
    \param mask
        A bad pixel mask derived from the confidence map
    \param nskys
        The number of skies to be used in correcting this pawprint
    \param skys
        The sky structures
    \param master_conf
        The master confidence map for this pawprint
    \param framelist
        The framelist containing the original SOF input
    \param parlist
        The command line parameter list
    \param catname
        The name of the standard star catalogue
    \param catpath
        The full path to the standard index file
    \param cacheloc
        The location of the standard star cache
    \param cs
        The config structure from the main routine
    \param photosys
        The photometric system
    \param prod_im
        A frameset for all the product science images
    \param prod_tab
        A frameset for all the product science catalogues
    \retval CASU_OK if all went well 
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_sky_wcs(pawprint *paw, int isoffsky,
                             casu_mask *mask, int nskys, skystruct *skys,
                             cpl_frame *master_conf, cpl_frameset *framelist,
                             cpl_parameterlist *parlist, char *catname,
                             char *catpath, char *cacheloc, configstruct *cs, 
                             char *photosys, cpl_frameset *prod_im, 
                             cpl_frameset *prod_tab) {
    int i,j,jj,k,nf,isfirst,whichsky,skyminus,status,skynone;
    long nx,ny,npts;
    cpl_frame *curframe,*template,*product_frame,*curframe_var;
    cpl_frame *product_frame_var,*product_frame_cat,*product_frame_mstd;
    cpl_frameset *objframes,*objframes_var;
    cpl_image *img,*simg;
    float med,sig,*sdata,gain;
    casu_fits **sky_extns,*curfits[HAWKI_NEXTN],*tmpfits,*fconf[HAWKI_NEXTN];
    casu_fits **sky_extns_var,*curfits_var[HAWKI_NEXTN];
    casu_tfits *fcat[HAWKI_NEXTN],*tab,*curmstd[HAWKI_NEXTN];
    cpl_table *t;
    unsigned char *bpm[HAWKI_NEXTN];
    const char *fctid = "hawki_sci_sky_wcs";
    char tmpname[BUFSIZ],tmpname_var[BUFSIZ],*tag,*assoc;

    /* Check to make sure the skys are available */

    objframes = paw->current;
    objframes_var = paw->current_var;
    nf = cpl_frameset_get_size(objframes);
    for (jj = 0; jj < nf; jj++) {
        whichsky = paw->whichsky[jj];
        if (whichsky >= nskys) {
            cpl_msg_error(fctid,"Requested sky [%" CPL_SIZE_FORMAT "]"
                          "doesn't exist. Programming error??",
                          (cpl_size)whichsky);
            return(CASU_FATAL);
        }
    }
    
    /* Set up the masking from the confidence maps */

    for (i = 1; i <= HAWKI_NEXTN; i++) {
        casu_mask_load(mask,i,0,0);
        nx = casu_mask_get_size_x(mask);
        ny = casu_mask_get_size_y(mask);
        bpm[i-1] = cpl_calloc(nx*ny,sizeof(unsigned char));
        memmove(bpm[i-1],casu_mask_get_data(mask),nx*ny*sizeof(unsigned char));
    }

    /* Loop for all the pawprints... */

    cpl_msg_indent_more();
    sky_extns = cpl_calloc(HAWKI_NEXTN,sizeof(casu_fits *));
    for (j = 0; j < HAWKI_NEXTN; j++)
        sky_extns[j] = NULL;
    sky_extns_var = cpl_calloc(HAWKI_NEXTN,sizeof(casu_fits *));
    for (j = 0; j < HAWKI_NEXTN; j++)
        sky_extns_var[j] = NULL;

    /* Do the sky subtraction for each frame in the pawprint. */

    whichsky = -2;
    skynone = (paw->whichsky[0] == -1);
    skyminus = 0;
    if (! skynone)
        skyminus = (skys[paw->whichsky[0]].skyalgo == PAWSKY_MINUS);
    for (jj = 0; jj < nf; jj++) {
        curframe = cpl_frameset_get_position(objframes,jj);
        curframe_var = cpl_frameset_get_position(objframes_var,jj);
        cpl_msg_info(fctid,"Beginning work on %s",
                     cpl_frame_get_filename(curframe));
        template = cpl_frameset_get_position(paw->orig,jj);
        (void)snprintf(tmpname,BUFSIZ,"tmp_%s",
                       cpl_frame_get_filename(curframe));
        (void)snprintf(tmpname_var,BUFSIZ,"tmp_%s",
                       cpl_frame_get_filename(curframe_var));

        /* First load up the sky extensions and normalise the data arrays
           to a zero median (so long as the sky correction hasn't
           already been done). */

        if (! skyminus && ! skynone &&
            (whichsky == -2 || paw->whichsky[jj] != whichsky)) {
            whichsky = paw->whichsky[jj];
            for (i = 1; i <= HAWKI_NEXTN; i++) {
                freefits(sky_extns[i-1]);
                freefits(sky_extns_var[i-1]);
                sky_extns[i-1] = casu_fits_load(skys[whichsky].skyframe,
                                                CPL_TYPE_FLOAT,i);
                sky_extns_var[i-1] = casu_fits_load(skys[whichsky].skyframe_var,
                                                    CPL_TYPE_FLOAT,i);
                if (casu_is_dummy(casu_fits_get_ehu(sky_extns[i-1])) == 0) {
                    img = casu_fits_get_image(sky_extns[i-1]);
                    sdata = cpl_image_get_data_float(img);
                    nx = (long)cpl_image_get_size_x(img);
                    ny = (long)cpl_image_get_size_y(img);
                    npts = nx*ny;
                    casu_qmedsig(sdata,bpm[i-1],npts,5.0,3,-1000.0,65535.0,
                                 &med,&sig);
                    for (k = 0; k < npts; k++)
                        sdata[k] -= med;
                } else {
                    casu_fits_set_status(sky_extns[i-1],CASU_FATAL);
                }
            }
        }

        /* Loop for all the extensions. First do the sky subtraction
           (if we need to do it) */

        for (i = 1; i <= HAWKI_NEXTN; i++) {
            curfits[i-1] = casu_fits_load(curframe,CPL_TYPE_FLOAT,i);
            curfits_var[i-1] = casu_fits_load(curframe_var,CPL_TYPE_FLOAT,i);
            if (casu_is_dummy(casu_fits_get_ehu(curfits[i-1]))) {
                casu_fits_set_status(curfits[i-1],CASU_FATAL);
                fconf[i-1] = NULL;
                if (cs->savecat) {
                    t = casu_dummy_catalogue(2);
                    tab = casu_tfits_wrap(t,NULL,
                                          casu_fits_get_phu(curfits[i-1]),
                                          casu_fits_get_ehu(curfits[i-1]));
                } else {
                    tab = NULL;
                }
            } else {
                img = casu_fits_get_image(curfits[i-1]);
                status = casu_fits_get_status(curfits[i-1]);
                if (sky_extns[i-1] != NULL && status != CASU_FATAL) {
                    simg = casu_fits_get_image(sky_extns[i-1]);
                    cpl_image_subtract(img,(const cpl_image *)simg);
                    hawki_var_add(curfits_var[i-1],sky_extns_var[i-1]);
                    cpl_propertylist_update_string(casu_fits_get_ehu(curfits[i-1]),
                                                   "ESO DRS SKYCOR",
                                                   casu_fits_get_fullname(sky_extns[i-1]));
                }
                fconf[i-1] = casu_fits_load(master_conf,CPL_TYPE_INT,i);
                (void)hawki_pfits_get_gain(casu_fits_get_ehu(curfits[i-1]),&gain);
                status = CASU_OK;
                (void)casu_imcore(curfits[i-1],fconf[i-1],
                                  cs->stk_cat_ipix,cs->stk_cat_thresh,
                                  cs->stk_cat_icrowd,cs->stk_cat_rcore,
                                  cs->stk_cat_nbsize,6,2.0,&tab,gain,&status);
            }
            fcat[i-1] = tab;
            curmstd[i-1] = NULL;
            freefits(fconf[i-1]);
        }

        /* Now fit the WCS */

        status = CASU_OK;
        hawki_wcsfit_multi(curfits,fcat,catname,catpath,cs->cdssearch_astrom,
                           cacheloc,cs->savemstd,curmstd);

        /* Now save them */

        isfirst = 1;
        for (i = 1; i <= HAWKI_NEXTN; i++) {

            /* Copy the WCS from the image to the variance map */

            hawki_copywcs(casu_fits_get_ehu(curfits[i-1]),
                          casu_fits_get_ehu(curfits_var[i-1]));

            /* First the simple images */

            if (isoffsky) 
                tag = (char *)HAWKI_PRO_SIMPLE_SKY;
            else
                tag = (char *)HAWKI_PRO_SIMPLE_SCI;
            assoc = cpl_strdup(casu_fits_get_filename(curfits_var[i-1]));
            tmpfits = casu_fits_duplicate(curfits[i-1]);
            hawki_sci_save_simple(tmpfits,framelist,parlist,1,template,
                                  isfirst,tag,tmpname,assoc,&product_frame);
            freefits(curfits[i-1]);
            freefits(tmpfits);

            /* Now the variance maps */

            if (isoffsky) 
                tag = (char *)HAWKI_PRO_VAR_SKY;
            else
                tag = (char *)HAWKI_PRO_VAR_SCI;
            tmpfits = casu_fits_duplicate(curfits_var[i-1]);
            hawki_sci_save_simple(tmpfits,framelist,parlist,1,template,
                                  isfirst,tag,tmpname_var,assoc,
                                  &product_frame_var);
            freefits(curfits_var[i-1]);
            freefits(tmpfits);
            freespace(assoc);

            /* Now the catalogues, if so desired */

            tab = NULL;
            if (cs->savecat && isoffsky == 0) {
                tab = casu_tfits_duplicate(fcat[i-1]);
                if (isfirst) 
                    product_frame_cat = NULL;
                hawki_sci_save_cat(tab,framelist,parlist,template,
                                   cs->prettynames,SIMPLE_CAT,jj+1,photosys,
                                   &product_frame_cat);
                if (i == 1) 
                    cpl_frameset_insert(prod_tab,
                                        cpl_frame_duplicate(product_frame_cat));
            }
            freetfits(fcat[i-1]);
            freetfits(tab);

            /* Finally the astrometric matched standards, if so desired */

            tab = NULL;
            if (cs->savemstd && isoffsky == 0) {
                tab = casu_tfits_duplicate(curmstd[i-1]);
                if (isfirst)
                    product_frame_mstd = NULL;
                hawki_sci_save_cat(tab,framelist,parlist,template,
                                   cs->prettynames,SIMPLE_MSTD_ASTROM,jj+1,
                                   photosys,&product_frame_mstd);
            }
            freetfits(curmstd[i-1]);
            freetfits(tab);
            isfirst = 0;
        }

        /* When we've finished writing out the new file. Delete the
           old one and replace it with the new one */

        remove(cpl_frame_get_filename(curframe));
        cpl_frame_set_filename(product_frame,cpl_frame_get_filename(curframe));
        rename(tmpname,cpl_frame_get_filename(curframe));
        cpl_frameset_insert(prod_im,cpl_frame_duplicate(curframe));
        remove(cpl_frame_get_filename(curframe_var));
        cpl_frame_set_filename(product_frame_var,
                               cpl_frame_get_filename(curframe_var));
        rename(tmpname_var,cpl_frame_get_filename(curframe_var));
    }

    /* Clear up some memory */

    for (i = 0; i < HAWKI_NEXTN; i++)
        freefits(sky_extns[i]);
    for (i = 0; i < HAWKI_NEXTN; i++)
        freefits(sky_extns_var[i]);
    for (i = 0; i < HAWKI_NEXTN; i++) 
        freespace(bpm[i]);
    freespace(sky_extns);
    freespace(sky_extns_var);
    cpl_msg_indent_less();
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_cat
    \par Purpose:
        Driver routine to do source extraction
    \par Description:
        Do the source extraction for a single extension of the the stack 
        associated with a pawprint
    \par Language:
        C
    \param paw
        The input pawprint structure.
    \param extn
        The extension number of the stack for the extraction
    \param cs
        The configuration structure defined in the main routine
    \returns
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_cat(pawprint *paw, int extn, configstruct *cs) {
    int status;
    casu_tfits *tab;
    float gain;
    cpl_table *t;
    const char *fctid = "hawki_sci_cat";

    /* Do the catalogue for the current extension */

    status = CASU_OK;
    if (casu_fits_get_status(paw->stack[extn-1]) != CASU_OK) {
        t = casu_dummy_catalogue(2);
        tab = casu_tfits_wrap(t,NULL,
                              cpl_propertylist_duplicate(casu_fits_get_phu(paw->stack[extn-1])),
                              cpl_propertylist_duplicate(casu_fits_get_ehu(paw->stack[extn-1])));
        casu_tfits_set_status(tab,CASU_FATAL);
        cpl_msg_warning(fctid,"Dummy stack extn %" CPL_SIZE_FORMAT " -- no catalogue",
                        (cpl_size)extn);
    } else {
        (void)hawki_pfits_get_gain(casu_fits_get_ehu(paw->stack[extn-1]),&gain);
        (void)casu_imcore(paw->stack[extn-1],paw->stackc[extn-1],
                          cs->stk_cat_ipix,cs->stk_cat_thresh,
                          cs->stk_cat_icrowd,cs->stk_cat_rcore,
                          cs->stk_cat_nbsize,6,2.0,&tab,gain,&status);
    }
    paw->cat[extn-1] = tab;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_stack
    \par Purpose:
        Driver routine to do stacking of a pawprint
    \par Description:
        Do a stack of the images of a given extension in the current pawprint
    \par Language:
        C
    \param paw
        The input pawprint structure.
    \param extn
        The extension number of the stack for the extraction
    \param master_conf
        The master confidence map for the pawprint
    \param cs
        The recipe configuration structure
    \returns
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_stack(pawprint *paw, cpl_frame *conf, int extn, 
                            configstruct *cs) {
    casu_fits **inf,*cinf,*out,*outc,*outv,**inf2,**cinf2,**infv,**infv2;
    int ninf,status,i,ninf2,fastslow,ndit;
    double sum,mjd,mjdmax,tmax,mjdmin;
    float dit;
    cpl_image *im,*imc,*imv;
    const char *fctid = "hawki_sci_stack";
    
    /* Do the stack for the current extension. Start by loading the images */

    inf = casu_fits_load_list(paw->current,CPL_TYPE_FLOAT,extn);
    infv = casu_fits_load_list(paw->current_var,CPL_TYPE_FLOAT,extn);
    cinf = casu_fits_load(conf,CPL_TYPE_INT,extn);
    ninf = (int)cpl_frameset_get_size(paw->current);

    /* Weed out the dummies */

    inf2 = cpl_malloc(ninf*sizeof(casu_fits *));
    infv2 = cpl_malloc(ninf*sizeof(casu_fits *));
    cinf2 = cpl_malloc(ninf*sizeof(casu_fits *));
    ninf2 = 0;
    for (i = 0; i < ninf; i++) {
        if (casu_is_dummy(casu_fits_get_ehu(inf[i])) == 0) {
            inf2[ninf2] = inf[i];
            infv2[ninf2] = infv[i];
            cinf2[ninf2++] = cinf;
        }
    }

    /* If there aren't any non-dummies, then create a dummy stack */

    if (ninf2 == 0) {
        im = casu_dummy_image(inf[0]);
        out = casu_fits_wrap(im,inf[0],NULL,NULL);
        casu_dummy_property(casu_fits_get_ehu(out));
        casu_fits_set_status(out,CASU_FATAL);
        imc = casu_dummy_image(cinf);
        outc = casu_fits_wrap(imc,cinf,NULL,NULL);
        casu_dummy_property(casu_fits_get_ehu(outc));
        casu_fits_set_status(outc,CASU_FATAL);
        imv = casu_dummy_image(infv[0]);
        outv = casu_fits_wrap(imv,cinf,NULL,NULL);
        casu_dummy_property(casu_fits_get_ehu(outv));
        casu_fits_set_status(outv,CASU_FATAL);
        cpl_msg_warning(fctid,"No good images to stack extn %" CPL_SIZE_FORMAT,
                        (cpl_size)extn);
        freespace(inf2);
        freespace(infv2);
        freespace(cinf2);

    /* Otherwise stack them */

    } else {
        if (cs->stk_fast == -1) 
            fastslow = (ninf2 <= cs->stk_nfst);
        else
            fastslow = cs->stk_fast;
        status = CASU_OK;
        casu_imstack(inf2,&cinf,infv2,NULL,ninf2,1,cs->stk_lthr,cs->stk_hthr,
                     cs->stk_method,cs->stk_seeing,fastslow,1,"ESO DET DIT",
                     &out,&outc,&outv,&status);
        freespace(inf2);
        freespace(cinf2);
        freespace(infv2);
    }

    /* Write NCOMBINE/NSTACK info to primary. For HAWKI these are the same */
    
    if (extn == 1) {
        cpl_propertylist_update_int(casu_fits_get_phu(out),"NCOMBINE",ninf);
        cpl_propertylist_set_comment(casu_fits_get_phu(out),"NCOMBINE",
                                     "Number of raw files");
        cpl_propertylist_update_int(casu_fits_get_phu(out),"NSTACK",ninf);
        cpl_propertylist_set_comment(casu_fits_get_phu(out),"NSTACK",
                                     "Number of images in stack");
        cpl_propertylist_update_int(casu_fits_get_phu(outc),"NCOMBINE",ninf);
        cpl_propertylist_set_comment(casu_fits_get_phu(outc),"NCOMBINE",
                                     "Number of raw files");
        cpl_propertylist_update_int(casu_fits_get_phu(outc),"NSTACK",ninf);
        cpl_propertylist_set_comment(casu_fits_get_phu(outc),"NSTACK",
                                     "Number of images in stack");
        cpl_propertylist_update_int(casu_fits_get_phu(outv),"NCOMBINE",ninf);
        cpl_propertylist_set_comment(casu_fits_get_phu(outv),"NCOMBINE",
                                     "Number of raw files");
        cpl_propertylist_update_int(casu_fits_get_phu(outv),"NSTACK",ninf);
        cpl_propertylist_set_comment(casu_fits_get_phu(outv),"NSTACK",
                                     "Number of images in stack");
    }
        
    /* 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 < ninf; i++) {
        hawki_pfits_get_mjd(casu_fits_get_phu(inf[i]),&mjd);
        sum += mjd;
        if (mjd > mjdmax) {
            mjdmax = mjd;
            hawki_pfits_get_ndit(casu_fits_get_phu(inf[i]),&ndit);
            hawki_pfits_get_dit(casu_fits_get_phu(inf[i]),&dit);
            tmax = (double)(ndit*dit);
        }
        mjdmin = min(mjd,mjdmin);
    }
    sum /= (double)ninf;
    cpl_propertylist_update_double(casu_fits_get_ehu(out),"MJD_MEAN",sum);
    cpl_propertylist_set_comment(casu_fits_get_ehu(out),"MJD_MEAN",
                                 "Mean MJD of the input images");
    cpl_propertylist_update_double(casu_fits_get_ehu(outc),"MJD_MEAN",sum);
    cpl_propertylist_set_comment(casu_fits_get_ehu(outc),"MJD_MEAN",
                                 "Mean MJD of the input images");
    cpl_propertylist_update_double(casu_fits_get_ehu(outv),"MJD_MEAN",sum);
    cpl_propertylist_set_comment(casu_fits_get_ehu(outv),"MJD_MEAN",
                                 "Mean MJD of the input images");
    cpl_propertylist_update_double(casu_fits_get_phu(out),"MJD-END",mjdmax+tmax/86400.0);
    cpl_propertylist_set_comment(casu_fits_get_phu(out),"MJD-END","End of observations");
    cpl_propertylist_update_double(casu_fits_get_phu(outc),"MJD-END",mjdmax+tmax/86400.0);
    cpl_propertylist_set_comment(casu_fits_get_phu(outc),"MJD-END","End of observations");
    cpl_propertylist_update_double(casu_fits_get_phu(outv),"MJD-END",mjdmax+tmax/86400.0);
    cpl_propertylist_set_comment(casu_fits_get_phu(outv),"MJD-END","End of observations");
    cpl_propertylist_update_double(casu_fits_get_phu(out),"MJD-OBS",mjdmin);
    cpl_propertylist_update_double(casu_fits_get_phu(outc),"MJD-OBS",mjdmin);
    cpl_propertylist_update_double(casu_fits_get_phu(outv),"MJD-OBS",mjdmin);

    /* Tidy */

    freefitslist(inf,ninf);
    freefitslist(infv,ninf);
    freefits(cinf);
    paw->stack[extn-1] = out;
    paw->stackc[extn-1] = outc;
    paw->stackv[extn-1] = outv;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_wcsfit
    \par Purpose:
        Do a WCS fit to an individual image
    \par Description:
        For each image in a list, create a source catalogue if one isn't 
        given in the argument list. Fit a WCS to the source positions.
    \par Language:
        C
    \param in
        A list of casu_fits structures for the input images
    \param conf
        A list of casu_fits structures for the input confidence maps
    \param incat
        A list of casu_tfits structures for the input source catalogues. NULL
        for level 1 WCS fit.
    \param nf
        The number of images in the lists
    \param level
        The WCS level: 1 -> preliminary, a temporary catalogue is generated 
        here; 2 -> final using given source catalogue.
    \param catname
        The name of the standard catalogue
    \param catpath
        The full path to the standard index file
    \param cacheloc
        The location of the star cache
    \param cdssearch
        The CDS catalogue to be searched for standards. 0 if using local
        catalogues.
    \returns
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
    
static void hawki_sci_wcsfit(casu_fits **in, casu_fits **conf, 
                             casu_tfits **incat, int nf, int level, 
                             char *catname, char *catpath, 
                             char *cacheloc, int cdssearch) {
    int status,nstd,ncat,slevel,n,i,j;
    float *x,*y,*ra,*dec;
    double r,d;
    casu_tfits *tcat;
    cpl_table *stdscat,*cat,*tmp,*tmp2,*matchstds;
    cpl_propertylist *p;
    cpl_wcs *wcs;
    const char *fctid = "hawki_sci_wcsfit";

    /* Loop for all the images */

    for (j = 0; j < nf; j++) {

        /* If this is a level one wcsfit, then we generate a temporary
           catalogue */

        status = CASU_OK;
        if (level == 1) 
            (void)casu_imcore(in[j],conf[j],10,1.5,0,3.0,64,3,2.0,&tcat,1.0,
                              &status);
        else 
            tcat = incat[j];

        /* Get some standard stars */

        (void)casu_getstds(casu_fits_get_ehu(in[j]),1,catpath,catname,cdssearch,
                           cacheloc,&stdscat,&status);
        if (status != CASU_OK) {
            freetable(stdscat);
            cpl_msg_error(fctid,
                          "Failed to find any standards for %s[%" CPL_SIZE_FORMAT "]",
                          casu_fits_get_filename(in[j]),
                          (cpl_size)casu_fits_get_nexten(in[j]));
            if (level == 1)
                freetfits(tcat);
            return;
        }

        /* Restrict to stars with good photometry for pure 2MASS. For PPMXL
           we don't have optical errors and if something doesn't appear
           in the IR part, then they well be deselected even though they
           might be OK in the optical bands. */

        (void)cpl_table_select_all(stdscat);
        nstd = (int)cpl_table_get_nrow(stdscat);

        /* If there are too many objects in the catalogue then first restrict
           ourselves by ellipticity. Cut so that there are similar numbers of
           objects in the standards and the object catalogues by retaining the
           brighter objects */

        cat = casu_tfits_get_table(tcat);
        ncat = (int)cpl_table_get_nrow(cat);
        if (ncat > 500 && ncat > 2.0*nstd) {
            tmp = cpl_table_duplicate(cat);
            (void)cpl_table_or_selected_float(tmp,"Ellipticity",CPL_LESS_THAN,0.5);
            tmp2 = cpl_table_extract_selected(tmp);
            ncat = (int)cpl_table_get_nrow(tmp2);
            freetable(tmp);
            p = cpl_propertylist_new();
            cpl_propertylist_append_bool(p,"Isophotal_flux",TRUE);            
            cpl_table_sort(tmp2,(const cpl_propertylist *)p);
            cpl_propertylist_delete(p);
            slevel = min(ncat,max(1,min(5000,max(500,2*nstd))));
            tmp = cpl_table_extract(tmp2,1,(cpl_size)slevel);
            freetable(tmp2);
            ncat = (int)cpl_table_get_nrow(tmp);
            cat = tmp;
        } else {
            tmp = NULL;
        }

        /* Now match this against the catalogue */

        (void)casu_matchstds(cat,stdscat,300.0,&matchstds,&status);
        freetable(stdscat);
        freetable(tmp);
        if (status != CASU_OK) {
            freetable(matchstds);
            cpl_msg_error(fctid,"Failed to match standards to catalogue");
            if (level == 1)
                freetfits(tcat);
            return;
        }

        /* Fit the plate solution */

        (void)casu_platesol(casu_fits_get_ehu(in[j]),
                            casu_tfits_get_ehu(tcat),matchstds,6,1,&status);
        freetable(matchstds);
        if (status != CASU_OK) {
            cpl_msg_error(fctid,"Failed to fit WCS");
            if (level == 1)
                freetfits(tcat);
            return;
        }

        /* Update the RA and DEC of the objects in the object catalogue */

        if (level == 2) {
            cat = casu_tfits_get_table(tcat);
            n = (int)cpl_table_get_nrow(cat);
            wcs = cpl_wcs_new_from_propertylist(casu_fits_get_ehu(in[j]));
            if (wcs == NULL) {
                cpl_msg_error(fctid,"Failed to fill RA and Dec in catalogue");
                return;
            }
            x = cpl_table_get_data_float(cat,"X_coordinate"); 
            y = cpl_table_get_data_float(cat,"Y_coordinate");
            ra = cpl_table_get_data_float(cat,"RA");
            dec = cpl_table_get_data_float(cat,"DEC");
            for (i = 0; i < n; i++) {
                casu_xytoradec(wcs,(double)x[i],(double)y[i],&r,&d);
                ra[i] = (float)r;
                dec[i] = (float)d;
            }
            cpl_wcs_delete(wcs);
        } else {
            freetfits(tcat);
        }
    }
}

/*=========================== Sky Routines ==================================*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_skydefine
    \par Purpose:
        Routine to define the skys to be made and the algorithms to be used.
    \par Description:
        Some structures are set up to define the sky frames to be made
        and the algorithms used to make them.
    \par Language:
        C
    \param nskys
        The output number of skys
    \param skys
        The output sky structures
    \param master_sky
        The master sky frame if it exists
    \param master_sky_var
        The master sky variance frame if it exists
    \param scipaw
        The pawprint for the science frame
    \param offpaw
        The pawprint structure for the offset sky observations
    \param master_objmask
        The frame for the master object mask, if it exists
    \param skyalgo
        The chosen sky algorithm
    \param prettynames
        A flag to determine whether pretty names are to be used.
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_skydefine(int *nskys, skystruct **skys, 
                                cpl_frame *master_sky, 
                                cpl_frame *master_sky_var, pawprint *scipaw,
                                pawprint *offpaw, cpl_frame *master_objmask,
                                int skyalgo, int prettynames) {
    cpl_frameset *newfrmset,*newfrmset_var;
    cpl_frame *template;

    /* Switch for the sky algorithm choices. Start with SKYNONE where
       we don't have to do anything as the sky frame index in the pawprints
       references nothing */

    switch (skyalgo) {
    case SKYNONE:
        break;

    /* AUTO means we use the data to determine what is the best way
       to estimate the sky. The routine also assigns the sky to be subtracted
       to each object image */
        
    case AUTO:
        hawki_sci_choose_skyalgo(nskys,skys,offpaw,scipaw,master_objmask,
                                 prettynames);
        break;

    /* A master sky exists. Therefore all we have to do here is to 
       create a sky structure and then assign it to all the input objects */

    case SKYMASTER:
        *nskys = 1;
        *skys = cpl_malloc(sizeof(skystruct));
        (*skys)[0].skyframe = cpl_frame_duplicate(master_sky);
        (*skys)[0].skyframe_var = cpl_frame_duplicate(master_sky_var);
        (*skys)[0].skyalgo = SKYMASTER;
        (*skys)[0].contrib = NULL;
        (*skys)[0].contrib_var = NULL;
        (*skys)[0].objmask = NULL;
        (*skys)[0].template = NULL;
        hawki_sci_assign_sky_all(scipaw,0);
        break;
    
    /* PAWSKY_MASK is requested. We need a section for the case where 
       offset skies have been provided or not */

    case PAWSKY_MASK:
        if (offpaw == NULL) {
            *nskys = 1;
            *skys = cpl_malloc(*nskys*sizeof(skystruct));
            newfrmset = cpl_frameset_duplicate(scipaw->current);
            newfrmset_var = cpl_frameset_duplicate(scipaw->current_var);
            template = cpl_frame_duplicate(cpl_frameset_get_position(scipaw->orig,0));
            (*skys)[0] = hawki_sci_crsky(PAWSKY_MASK,newfrmset,newfrmset_var,
                                         template,prettynames,master_objmask,1);
            hawki_sci_assign_sky_all(scipaw,0);        
        } else {
            *nskys = 1;
            *skys = cpl_malloc(*nskys*sizeof(skystruct));
            newfrmset = cpl_frameset_duplicate(offpaw->current);
            newfrmset_var = cpl_frameset_duplicate(offpaw->current_var);
            template = cpl_frame_duplicate(cpl_frameset_get_position(offpaw->orig,0));
            (*skys)[0] = hawki_sci_crsky(PAWSKY_MASK,newfrmset,newfrmset_var,
                                         template,prettynames,master_objmask,1);
            hawki_sci_assign_sky_all(offpaw,0);        
            hawki_sci_assign_sky_all(scipaw,0);        
        }            
        break;

    /* PAWSKY_MASK_PRE is requested. Offset skies are ignored because the 
       mask won't cover this. */

    case PAWSKY_MASK_PRE:
        *nskys = 1;
        *skys = cpl_malloc(*nskys*sizeof(skystruct));
        newfrmset = cpl_frameset_duplicate(scipaw->current);
        newfrmset_var = cpl_frameset_duplicate(scipaw->current_var);
        template = cpl_frame_duplicate(cpl_frameset_get_position(scipaw->orig,0));
        (*skys)[0] = hawki_sci_crsky(PAWSKY_MASK,newfrmset,newfrmset_var,
                                     template,prettynames,master_objmask,1);
        hawki_sci_assign_sky_all(scipaw,0);        
        break;

    /* SIMPLESKY_MASK is requested. We need a section for the case where 
       offset skies have been provided or not */

    case SIMPLESKY_MASK:
        if (offpaw == NULL) {
            *nskys = 1;
            *skys = cpl_malloc(*nskys*sizeof(skystruct));
            newfrmset = cpl_frameset_duplicate(scipaw->current);
            newfrmset_var = cpl_frameset_duplicate(scipaw->current_var);
            template = cpl_frame_duplicate(cpl_frameset_get_position(scipaw->orig,0));
            (*skys)[0] = hawki_sci_crsky(SIMPLESKY_MASK,newfrmset,newfrmset_var,
                                         template,prettynames,master_objmask,1);
            hawki_sci_assign_sky_all(scipaw,0);        
        } else {
            *nskys = 1;
            *skys = cpl_malloc(*nskys*sizeof(skystruct));
            newfrmset = cpl_frameset_duplicate(offpaw->current);
            newfrmset_var = cpl_frameset_duplicate(offpaw->current_var);
            template = cpl_frame_duplicate(cpl_frameset_get_position(offpaw->orig,0));
            (*skys)[0] = hawki_sci_crsky(SIMPLESKY_MASK,newfrmset,newfrmset_var,
                                         template,prettynames,master_objmask,1);
            hawki_sci_assign_sky_all(offpaw,0);        
            hawki_sci_assign_sky_all(scipaw,0);        
        }            
        break;

    /* PAWKSY_MINUS is requested */

    case PAWSKY_MINUS:
        *nskys = 1;
        *skys = cpl_malloc(*nskys*sizeof(skystruct));
        newfrmset = cpl_frameset_duplicate(scipaw->current);
        newfrmset_var = cpl_frameset_duplicate(scipaw->current_var);
        template = cpl_frame_duplicate(cpl_frameset_get_position(scipaw->orig,0));
        (*skys)[0] = hawki_sci_crsky(PAWSKY_MINUS,newfrmset,newfrmset_var,
                                     template,prettynames,master_objmask,1);
        hawki_sci_assign_sky_all(scipaw,0);        
        break;
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_choose_skyalgo
    \par Purpose:
        Choice of sky algorithm in AUTO mode.
    \par Description:
        If the user opts to let the recipe decide which sky algorithm should
        be used, then this routine makes that choice.
    \par Language:
        C
    \param nskys
        The output number of skys
    \param skys
        The output sky structures
    \param offpaw
        The pawprint structure for the offset sky observations
    \param scipaw
        The pawprint for the science frame
    \param master_objmask
        The frame for the master object mask, if it exists
    \param prettynames
        Flag to define whether pretty names are being used
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_choose_skyalgo(int *nskys, skystruct **skys, 
                                     pawprint *offpaw, pawprint *scipaw, 
                                     cpl_frame *master_objmask,
                                     int prettynames) {
    cpl_frameset *newfrmset,*newfrmset_var;
    cpl_frame *template;
    skystruct newsky;
    float xchar,ychar;

    /* Situation where we have offset skies. Look and see if they are all
       from the same template. If so, then just do a straight-forward 
       pawsky_mask. */

    if (offpaw != NULL) {
        newfrmset = cpl_frameset_duplicate(offpaw->current);
        newfrmset_var = cpl_frameset_duplicate(offpaw->current_var);
        if (hawki_sci_all1tpl(offpaw->orig)) {
            hawki_sci_charshift(offpaw->orig,&xchar,&ychar);
            hawki_sci_splitsky(newfrmset,newfrmset_var,offpaw->orig,offpaw,
                               scipaw,xchar,ychar,master_objmask,prettynames,
                               nskys,skys);
            cpl_frameset_delete(newfrmset);
            cpl_frameset_delete(newfrmset_var);
        } else {
            template = cpl_frame_duplicate(cpl_frameset_get_position(offpaw->orig,0));
            hawki_sci_charshift(offpaw->orig,&xchar,&ychar);
            newsky = hawki_sci_crsky(PSEUDO_TILESKY,newfrmset,newfrmset_var,
                                     template,prettynames,master_objmask,1);
            *nskys = 1;
            *skys = cpl_malloc(sizeof(skystruct));
            (*skys)[0] = newsky;
            hawki_sci_assign_sky_all(offpaw,0);
            hawki_sci_assign_sky_all(scipaw,0);
        }

    /* OK there aren't any offset skies. */

    } else {            
        newfrmset = cpl_frameset_duplicate(scipaw->current);
        newfrmset_var = cpl_frameset_duplicate(scipaw->current_var);
        hawki_sci_charshift(scipaw->orig,&xchar,&ychar);
        hawki_sci_splitsky(newfrmset,newfrmset_var,scipaw->orig,offpaw,
                           scipaw,xchar,ychar,master_objmask,prettynames,
                           nskys,skys);
        cpl_frameset_delete(newfrmset);
        cpl_frameset_delete(newfrmset_var);
    } 
}                                      

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_splitsky
    \par Purpose:
        Split observations into a number of potential sky frames
    \par Description:
        A list of frames to be used for working out the sky background
        is split into a number of sub-groups. This is done to ensure that
        the time coverage of any given sky isn't too long.
    \par Language:
        C
    \param frms
        The frameset of the images to be used in forming the sky. This can
        either be science or offset sky frames
    \param frms_var
        The frameset of the variance images to be used in forming the sky. 
        This can either be science or offset sky frames
    \param tpls
        The frameset of the original raw images corresponding to frms. 
    \param offpaw
        The offset sky pawprint structure (if it exists)
    \param scipaw
        The science pawprint structure
    \param xchar
        The mean absolute shift in X of the frameset being used for the sky
    \param ychar
        The mean absolute shift in Y of the frameset being used for the sky
    \param master_objmask
        The frame for the master object mask, if it exists
    \param prettynames
        Flag to define whether pretty names are being used
    \param nskys
        The number of skys 
    \param skys
        The list of sky structures
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_splitsky(cpl_frameset *frms, cpl_frameset *frms_var,
                               cpl_frameset *tpls, pawprint *offpaw, 
                               pawprint *scipaw, float xchar, float ychar,
                               cpl_frame *master_objmask, int prettynames,
                               int *nskys, skystruct **skys) {
    double dt,halfhour,tst,*timestart,*timeend,delta,tfn,mjd,*mjdsci,*mjdoff;
    skystruct newsky;
    cpl_frame *template,*frm;
    int *istart,*iend,*nf,nfrms,nsplit,js,is,nfs,nfo = 0,i,algo;
    cpl_propertylist *p;
    cpl_frameset *newfrmset,*newfrmset_var;

    /* Initialise a few things */

    halfhour = 1.0/48.0;
    *nskys = 0;
    *skys = NULL;

    /* Time covered by the pawprint(s) */

    if (offpaw == NULL) {
        dt = scipaw->mjd_end - scipaw->mjd_start;
        tst = scipaw->mjd_start;
    } else {
        dt = max(scipaw->mjd_end,offpaw->mjd_end) - 
            min(scipaw->mjd_start,offpaw->mjd_start);
        tst = min(scipaw->mjd_start,offpaw->mjd_start);
    }

    /* Look at characteristic jitter offsets of the images that will 
       form the sky. If they are reasonably large then user SIMPLESKY_MASK.
       Otherwise use PAWSKY_MASK */

    if (xchar > HAWKI_NX/2 || ychar > HAWKI_NY/2)
        algo = SIMPLESKY_MASK;
    else
        algo = PAWSKY_MASK;

    /* The easy part...If dt < 30 minutes then we just bundle all these
       into a single sky */

    if (dt < halfhour) {
        *nskys = 1;
        *skys = cpl_malloc(*nskys*sizeof(skystruct));
        template = cpl_frame_duplicate(cpl_frameset_get_position(tpls,0));
        newsky = hawki_sci_crsky(algo,cpl_frameset_duplicate(frms),
                                 cpl_frameset_duplicate(frms_var),
                                 template,prettynames,master_objmask,1);
        (*skys)[*nskys-1] = newsky;
        hawki_sci_assign_sky_all(offpaw,*nskys-1);
        hawki_sci_assign_sky_all(scipaw,*nskys-1);
        return;
    }

    /* Get some workspace to monitor which frames are in which time bin,
       how many frames are in each time bin and the time range that each
       bin covers */

    nsplit = (int)(dt/halfhour) + 1;
    istart = cpl_malloc(nsplit*sizeof(int));
    iend = cpl_malloc(nsplit*sizeof(int));
    nf = cpl_malloc(nsplit*sizeof(int));
    timestart = cpl_malloc(nsplit*sizeof(double));
    timeend = cpl_malloc(nsplit*sizeof(double));
    
    /* OK, so let's see how we can divide these guys up. Try and break the
       time interval into roughly 30 minute slots. Also try and ensure that
       there are enough frames in each time bin */

    istart[0] = 0;
    delta = halfhour;
    nfrms = cpl_frameset_get_size(frms);
    for (js = 0; js < nsplit; js++) {
        timestart[js] = tst + js*delta;
        tfn = tst + (float)(js+1)*delta;
        iend[js] = istart[js];
        timeend[js] = tfn;
        for (is = istart[js]; is < nfrms; is++) {
            p = cpl_propertylist_load(cpl_frame_get_filename(cpl_frameset_get_position(tpls,is)),0);
            (void)hawki_pfits_get_mjd(p,&mjd);
            cpl_propertylist_delete(p);
            if (mjd <= tfn || (js == (nsplit-1) && is == (nfrms-1))) {
                iend[js] = is;
            } else
                break;
        }
        if (js != nsplit-1) 
            istart[js+1] = iend[js] + 1;
        nf[js] = iend[js] - istart[js] + 1;
    }

    /* Check the last bin and see if it's too small because the time interval
       is just a little over the halfhour quantum */

    if (nsplit > 1 && nf[nsplit-1] < 4) {
        nsplit--;

        /* OK, redefine the limits based on this number of divisions */

        istart[0] = 0;
        delta = dt/(double)nsplit;
        nfrms = cpl_frameset_get_size(frms);
        for (js = 0; js < nsplit; js++) {
            timestart[js] = tst + js*delta;
            tfn = tst + (float)(js+1)*delta;
            iend[js] = istart[js];
            timeend[js] = tfn;
            for (is = istart[js]; is < nfrms; is++) {
                p = cpl_propertylist_load(cpl_frame_get_filename(cpl_frameset_get_position(tpls,is)),0);
                (void)hawki_pfits_get_mjd(p,&mjd);
                cpl_propertylist_delete(p);
                if (mjd <= tfn || (js == (nsplit-1) && is == (nfrms-1))) {
                    iend[js] = is;
                } else
                    break;
            }
            if (js != nsplit-1) 
                istart[js+1] = iend[js] + 1;
            nf[js] = iend[js] - istart[js] + 1;
        }
    }

    /* Get some space for the sky structures and for the mjd of the pawprint
       frames */

    *nskys = nsplit;
    *skys = cpl_malloc(*nskys*sizeof(skystruct));
    nfs = cpl_frameset_get_size(scipaw->current);
    mjdsci = cpl_malloc(nfs*sizeof(double));
    if (offpaw != NULL) {
        nfo = cpl_frameset_get_size(offpaw->current);
        mjdoff = cpl_malloc(nfo*sizeof(double));
    } else {
        mjdoff = NULL;
    }

    /* Get the mjds from the pawprint(s) */

    for (i = 0; i < nfs; i++) {
        frm = cpl_frameset_get_position(scipaw->orig,i);
        p = cpl_propertylist_load(cpl_frame_get_filename(frm),0);
        hawki_pfits_get_mjd(p,mjdsci+i);
        cpl_propertylist_delete(p);
    }
    if (offpaw != NULL) {
        for (i = 0; i < nfo; i++) {
            frm = cpl_frameset_get_position(offpaw->orig,i);
            p = cpl_propertylist_load(cpl_frame_get_filename(frm),0);
            hawki_pfits_get_mjd(p,mjdoff+i);
            cpl_propertylist_delete(p);
        }
    }
        
    /* Ok, now create the sky structures */

    for (js = 0; js < nsplit; js++) {
        newfrmset = cpl_frameset_new();
        newfrmset_var = cpl_frameset_new();
        for (is = istart[js]; is <= iend[js]; is++) {
            cpl_frameset_insert(newfrmset,
                                cpl_frame_duplicate(cpl_frameset_get_position(frms,is)));
            cpl_frameset_insert(newfrmset_var,
                                cpl_frame_duplicate(cpl_frameset_get_position(frms_var,is)));
        }
        template = cpl_frame_duplicate(cpl_frameset_get_position(tpls,istart[js]));
        newsky = hawki_sci_crsky(algo,newfrmset,newfrmset_var,template,
                                 prettynames,master_objmask,js+1);
        (*skys)[js] = newsky;

        /* Decide which of the pawprints' frames will be corrected by the
           current sky */
        
        for (i = 0; i < nfs; i++) {
            if (mjdsci[i] >= timestart[js] && mjdsci[i] <= timeend[js])
                scipaw->whichsky[i] = js;
        }
        if (offpaw != NULL) {
            for (i = 0; i < nfo; i++) {
                if (mjdoff[i] >= timestart[js] && mjdoff[i] <= timeend[js])
                    offpaw->whichsky[i] = js;
            }
        }
    }

    /* Free up workspace and exit */

    freespace(istart);
    freespace(iend);
    freespace(nf);
    freespace(timestart);
    freespace(timeend);
    freespace(mjdsci);
    freespace(mjdoff);
    return;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_assign_sky_all
    \par Purpose:
        Assign a given sky to all of the images in a pawprint
    \par Description:
        Each image in a given pawprint will be assigned the same sky
        frame. 
    \par Language:
        C
    \param paw
        The pawprint for the science frames
    \param whichone
        The index number of the sky frame to be used
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_assign_sky_all(pawprint *paw, int whichone) {
    int i,n;

    if (paw == NULL)
        return;
    n = cpl_frameset_get_size(paw->current);
    for (i = 0; i < n; i++) 
        paw->whichsky[i] = whichone;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_crsky
    \par Purpose:
        Set up a sky structure
    \par Description:
        A sky structure is set up, giving it a list of files used to form
        the sky, an algorithm and a few other useful pieces of information
    \par Language:
        C
    \param algorithm
        The sky algorithm to be used. Chosen from the list defined above.
    \param frms
        The frameset containing the frames to be used in forming the sky
    \param frms_var
        The frameset containing the variance frames to be used in forming the 
        sky
    \param template
        A template to be used for saving the output sky frame
    \param prettynames
        A flag to decide whether pretty names are being used
    \param master_objmask
        The master object mask, if it exists. 
    \param snum
        The sky number. This is used in the case where the plain ESO names
        are requested.
    \return 
        The skystruct structure for the output sky.
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static skystruct hawki_sci_crsky(int algorithm, cpl_frameset *frms, 
                                 cpl_frameset *frms_var, cpl_frame *template, 
                                 int prettynames, cpl_frame *master_objmask, 
                                 int snum) {
    skystruct s;

    s.contrib = frms;
    s.contrib_var = frms_var;
    if (master_objmask != NULL) 
        s.objmask = cpl_frame_duplicate(master_objmask);
    else
        s.objmask = NULL;
    s.skyalgo = algorithm;
    s.skyframe = NULL;
    s.template = template;
    hawki_sci_product_name((char *)cpl_frame_get_filename(template),
                           SKY_FILE,prettynames,snum,s.fname);
    hawki_sci_product_name((char *)cpl_frame_get_filename(template),
                           SKY_FILE_VAR,prettynames,snum,s.fname_var);
    return(s);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_pawsky_mask
    \par Purpose:
        Driver routine for the pawsky_mask and pawsky_mask_pre algorithms
    \par Description:
        For each extension a set of images is combined using either the
        pawksy_mask or pawsky_mask_pre algorithm. Each extension is saved 
        in turn.
    \par Language:
        C
    \param framelist
        The frameset with the SOF input frames
    \param parlist
        The parameter list with the input command line parameters
    \param algo
        The sky algorithm to be used
    \param contrib
        The frameset that contains all the frames that will contribute to 
        the sky image
    \param contrib_var
        The frameset that contains all the variances that will contribute to 
        the sky variance
    \param template
        A template frame used in the save routine
    \param fname
        A name for the output sky file
    \param fname_var
        A name for the output sky variance file
    \param master_conf
        The master confidence map for the contributing frameset
    \param mask
        The bad pixel mask derived from the master confidence map
    \param cs
        The configuration structure from the main routine
    \param master_objmask
        The master object mask. This must exist for pawsky_mask_pre to 
        be used.
    \param product_frame
        The output frame for the sky data product
    \param product_frame_var
        The output frame for the sky variance data product
    \retval CASU_OK if all went well
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
    
static int hawki_sci_pawsky_mask(cpl_frameset *framelist, 
                                 cpl_parameterlist *parlist, int algo,
                                 cpl_frameset *contrib, 
                                 cpl_frameset *contrib_var, cpl_frame *template,
                                 char *fname, char *fname_var, 
                                 cpl_frame *master_conf, casu_mask *mask, 
                                 configstruct *cs, cpl_frame *master_objmask,
                                 cpl_frame **product_frame,
                                 cpl_frame **product_frame_var) {
    int j,nfiles,status,isfirst,k;
    const char *fctid="hawki_sci_pawsky_mask";
    casu_fits **infits,*conf,*skyout,*opmfits,**infits_var,*skyout_var;

    /* Output product frame */

    *product_frame = NULL;

    /* Loop for each extension */

    nfiles = cpl_frameset_get_size(contrib);
    cpl_msg_info(fctid,"Creating sky %s",fname);
    cpl_msg_indent_more();
    for (j = 1; j <= HAWKI_NEXTN; j++) {
        cpl_msg_info(fctid,"Extension [%" CPL_SIZE_FORMAT "]",(cpl_size)j);
        isfirst = (j == 1);
        
        /* Load up input images for this extension + confidence map
           and associated bad pixel mask... */

        infits = casu_fits_load_list(contrib,CPL_TYPE_FLOAT,j);
        for (k = 0; k < nfiles; k++) {
            if (casu_is_dummy(casu_fits_get_ehu(infits[k])))
                casu_fits_set_status(infits[k],CASU_FATAL);
        }
        conf = casu_fits_load(master_conf,CPL_TYPE_INT,j);
        casu_mask_load(mask,j,
                       (int)cpl_image_get_size_x(casu_fits_get_image(conf)),
                       (int)cpl_image_get_size_y(casu_fits_get_image(conf)));
        infits_var = casu_fits_load_list(contrib_var,CPL_TYPE_FLOAT,j);

        /* Form the sky for this extension */

        status = CASU_OK;
        skyout = NULL;
        if (algo == PAWSKY_MASK) {
            (void)casu_pawsky_mask(infits,infits_var,nfiles,conf,mask,&skyout,
                                   &skyout_var,cs->psm_niter,cs->psm_ipix,
                                   cs->psm_thresh,cs->psm_nbsize,cs->psm_smkern,
                                   &status);
        } else {
            opmfits = casu_fits_load(master_objmask,CPL_TYPE_INT,1);
            (void)casu_pawsky_mask_pre(infits,infits_var,nfiles,mask,opmfits,
                                       cs->psm_nbsize,&skyout,&skyout_var,
                                       &status);
            casu_fits_delete(opmfits);
        }

        /* Save the sky frame */

        if (hawki_sci_save_sky(skyout,framelist,parlist,fname,template,
                               0,isfirst,product_frame) != CASU_OK) {
            freefitslist(infits,nfiles); 
            freefitslist(infits_var,nfiles); 
            freefits(conf);
            freefits(skyout);
            freefits(skyout_var);
            return(CASU_FATAL);
        }
        
        /* And the sky variance frame */

        if (hawki_sci_save_sky(skyout_var,framelist,parlist,fname_var,template,
                               1,isfirst,product_frame_var) != CASU_OK) {
            freefitslist(infits,nfiles); 
            freefitslist(infits_var,nfiles); 
            freefits(conf);
            freefits(skyout);
            freefits(skyout_var);
            return(CASU_FATAL);
        }

        /* Tidy up and move on to the next one */

        freefitslist(infits,nfiles);
        freefitslist(infits_var,nfiles);
        freefits(conf);
        freefits(skyout);
        freefits(skyout_var);
    }
    cpl_msg_indent_less();
    return(CASU_OK);
}                

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_simplesky_mask
    \par Purpose:
        Driver routine for the simplesky_mask algorithm
    \par Description:
        For each extension a set of images is combined using the
        simpleksy_mask algorithm. Each extension is saved in turn.
    \par Language:
        C
    \param framelist
        The frameset with the SOF input frames
    \param parlist
        The parameter list with the input command line parameters
    \param contrib
        The frameset that contains all the frames that will contribute to 
        the sky image
    \param contrib_var
        The frameset that contains all the variances that will contribute to 
        the sky variance
    \param template
        A template frame used in the save routine
    \param fname
        A name for the output sky file
    \param fname_var
        A name for the output sky variance map
    \param master_conf
        The master confidence map for the contributing frameset
    \param mask
        The bad pixel mask derived from the master confidence map
    \param cs
        The configuration structure from the main routine
    \param product_frame
        The output frame for the sky data product
    \param product_frame_var
        The output frame for the sky variance data product
    \retval CASU_OK if all went well
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
    
static int hawki_sci_simplesky_mask(cpl_frameset *framelist, 
                                    cpl_parameterlist *parlist, 
                                    cpl_frameset *contrib, 
                                    cpl_frameset *contrib_var, 
                                    cpl_frame *template, char *fname, 
                                    char *fname_var, cpl_frame *master_conf,
                                    casu_mask *mask, configstruct *cs,
                                    cpl_frame **product_frame,
                                    cpl_frame **product_frame_var) {
    int j,nfiles,status,isfirst,k;
    const char *fctid="hawki_sci_simplesky_mask";
    casu_fits **infits,*conf,*skyout,**infits_var,*skyout_var;

    /* Output product frame */

    *product_frame = NULL;

    /* Loop for each extension */

    nfiles = cpl_frameset_get_size(contrib);
    cpl_msg_info(fctid,"Creating sky %s",fname);
    cpl_msg_indent_more();
    for (j = 1; j <= HAWKI_NEXTN; j++) {
        cpl_msg_info(fctid,"Extension [%" CPL_SIZE_FORMAT "]",(cpl_size)j);
        isfirst = (j == 1);
        
        /* Load up input images for this extension + confidence map
           and associated bad pixel mask... */

        infits = casu_fits_load_list(contrib,CPL_TYPE_FLOAT,j);
        for (k = 0; k < nfiles; k++) {
            if (casu_is_dummy(casu_fits_get_ehu(infits[k])))
                casu_fits_set_status(infits[k],CASU_FATAL);
        }
        conf = casu_fits_load(master_conf,CPL_TYPE_INT,j);
        casu_mask_load(mask,j,
                         (int)cpl_image_get_size_x(casu_fits_get_image(conf)),
                         (int)cpl_image_get_size_y(casu_fits_get_image(conf)));
        infits_var = casu_fits_load_list(contrib_var,CPL_TYPE_FLOAT,j);

        /* Form the sky for this extension */

        status = CASU_OK;
        skyout = NULL;
        (void)casu_simplesky_mask(infits,infits_var,nfiles,conf,mask,&skyout,
                                  &skyout_var,cs->psm_niter,cs->psm_ipix,
                                  cs->psm_thresh,cs->psm_nbsize,cs->psm_smkern,
                                  &status);

        /* Save the sky frame */

        if (hawki_sci_save_sky(skyout,framelist,parlist,fname,template,
                               0,isfirst,product_frame) != CASU_OK) {
            freefitslist(infits,nfiles); 
            freefitslist(infits_var,nfiles); 
            freefits(conf);
            freefits(skyout);
            freefits(skyout_var);
            return(CASU_FATAL);
        }

        /* Save the sky variance frame */

        if (hawki_sci_save_sky(skyout_var,framelist,parlist,fname_var,template,
                               1,isfirst,product_frame_var) != CASU_OK) {
            freefitslist(infits,nfiles); 
            freefitslist(infits_var,nfiles); 
            freefits(conf);
            freefits(skyout);
            freefits(skyout_var);
            return(CASU_FATAL);
        }

        /* Tidy up and move on to the next one */

        freefitslist(infits,nfiles);
        freefitslist(infits_var,nfiles);
        freefits(conf);
        freefits(skyout);
        freefits(skyout_var);
    }
    cpl_msg_indent_less();
    return(CASU_OK);
}                

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_makesky
    \par Purpose:
        Driver routine for to make sky frames
    \par Description:
        Given a sky structure, the routine calls the correct algorithm to
        make the sky frame.
    \par Language:
        C
    \param framelist
        The frameset with the SOF input frames
    \param parlist
        The parameter list with the input command line parameters
    \param sky
        The input skystruct structure for the sky to be formed
    \param master_objmask
        The master object mask. This must exist for pawsky_mask_pre to 
        be used.
    \param master_conf
        The master confidence map for the contributing frameset
    \param mask
        The bad pixel mask derived from the master confidence map
    \param cs
        The configuration structure defined in the main routine
    \param ps
        The memory structure defined in the main routine
    \retval CASU_OK if all went well
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
    
static int hawki_sci_makesky(cpl_frameset *framelist, 
                             cpl_parameterlist *parlist, skystruct *sky,
                             cpl_frame *master_objmask, cpl_frame *master_conf,
                             casu_mask *mask, configstruct *cs,
                             memstruct *ps) {
    int retval;

    /* Switch for the various options to sky creation. Options SKYNONE,
       and SKYMASTER we don't do anything at this point. */

    retval = CASU_OK;
    switch (sky->skyalgo) {
    case SKYNONE:
        break;
    case SKYMASTER:
        break;
    case PAWSKY_MINUS:
        retval = hawki_sci_pawsky_minus(framelist,parlist,sky->contrib,
                                        sky->contrib_var,sky->template,
                                        sky->fname,sky->fname_var,
                                        master_objmask,master_conf,
                                        ps->catname_a,ps->catpath_a,
                                        cs->cacheloc,cs->cdssearch_astrom,
                                        &(sky->skyframe),&(sky->skyframe_var));
        break;
    case PAWSKY_MASK:
        retval = hawki_sci_pawsky_mask(framelist,parlist,sky->skyalgo,
                                       sky->contrib,sky->contrib_var,
                                       sky->template,sky->fname,sky->fname_var,
                                       master_conf,mask,cs,master_objmask,
                                       &(sky->skyframe),&(sky->skyframe_var));
        break;
    case PAWSKY_MASK_PRE:
        retval = hawki_sci_pawsky_mask(framelist,parlist,sky->skyalgo,
                                       sky->contrib,sky->contrib_var, 
                                       sky->template,sky->fname,sky->fname_var,
                                       master_conf,mask,cs,master_objmask,
                                       &(sky->skyframe),&(sky->skyframe_var));
        break;
    case SIMPLESKY_MASK:
        retval = hawki_sci_simplesky_mask(framelist,parlist,sky->contrib,
                                          sky->contrib_var,sky->template,
                                          sky->fname,sky->fname_var,
                                          master_conf,mask,cs,&(sky->skyframe),
                                          &(sky->skyframe_var));
        break;
    case PSEUDO_TILESKY:
        retval = hawki_sci_pseudo_tilesky(framelist,parlist,sky->contrib,
                                          sky->contrib_var,sky->template,
                                          sky->fname,sky->fname_var,mask,
                                          &(sky->skyframe),
                                          &(sky->skyframe_var));
        break;
    }
    return(retval);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_pseudo_tilesky
    \par Purpose:
        Make a sky frame using a pseudo-tilesky algorithm
    \par Description:
        Subdivide a group of offset sky images by template start time.
        Treat the subgroups as though they are pawprints in a tilesky
    \par Language:
        C
    \param framelist
        The frameset with the SOF input frames
    \param parlist
        The parameter list with the input command line parameters
    \param in
        The frameset that contains all the frames that will contribute to 
        the sky images and be corrected by them
    \param invar
        The frameset that contains all the variances that will contribute to 
        the sky varaince
    \param sky_template
        A template frame used in the sky_save routine
    \param skyname
        A name for the output sky file
    \param skyname_var
        A name for the output sky variance file
    \param mask
        The master pixel mask. 
    \param skyframe
        The output frame for the sky data product
    \param skyframe_var
        The output frame for the sky variance data product
    \retval CASU_OK if all went well
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
    
static int hawki_sci_pseudo_tilesky(cpl_frameset *framelist, 
                                    cpl_parameterlist *parlist, 
                                    cpl_frameset *in, cpl_frameset *invar, 
                                    cpl_frame *sky_template, char *skyname, 
                                    char *skyname_var, casu_mask *mask, 
                                    cpl_frame **skyframe, 
                                    cpl_frame **skyframe_var) {
    int i,j,isfirst,nf,k,status,nf2;
    cpl_size *labels,nlab;
    cpl_frameset *copy,*subset,*subset_var,*copy_var;
    unsigned char *inbpm,*rejmask,*rejplus;
    cpl_propertylist *drs;
    cpl_image *outim,*outim_var;
    cpl_mask *cplmask;
    casu_fits **fpskys,**infits,*skyout,**infits2,**infvar,**infvar2,**fpvars;
    casu_fits *skyout_var;
    const char *fctid = "hawki_sci_pseudo_tilesky";

    /* How many subsets are there? */

    cpl_msg_info(fctid,"Creating sky %s",skyname);
    copy = cpl_frameset_duplicate(in);
    copy_var = cpl_frameset_duplicate(invar);
    labels = cpl_frameset_labelise(copy,hawki_sci_cmp_tstart,&nlab);

    /* Loop for each extension */

    for (i = 1; i <= HAWKI_NEXTN; i++) {
        isfirst = (i == 1);
        casu_mask_load(mask,i,HAWKI_NX,HAWKI_NY);
        inbpm = casu_mask_get_data(mask);
        cplmask = cpl_mask_wrap(HAWKI_NX,HAWKI_NY,(cpl_binary *)inbpm);

        /* Loop for each label and form a first pass sky for this extension */

        fpskys = cpl_malloc(nlab*sizeof(casu_fits *));
        fpvars = cpl_malloc(nlab*sizeof(casu_fits *));
        for (j = 0; j < nlab; j++) {
            subset = cpl_frameset_extract(copy,labels,(cpl_size)j);
            nf = cpl_frameset_get_size(subset);
            infits = casu_fits_load_list(subset,CPL_TYPE_FLOAT,i);
            subset_var = cpl_frameset_extract(copy_var,labels,(cpl_size)j);
            infvar = casu_fits_load_list(subset_var,CPL_TYPE_FLOAT,i);
            infits2 = cpl_malloc(nf*sizeof(casu_fits *));
            infvar2 = cpl_malloc(nf*sizeof(casu_fits *));
            nf2 = 0;
            for (k = 0; k < nf; k++) {
                if (casu_is_dummy(casu_fits_get_ehu(infits[k])) == 0) {
                    infits2[nf2] = infits[k];
                    infvar2[nf2] = infvar[k];
                    nf2++;
                }
            }
            if (nf2 > 0) {
                for (k = 0; k < nf2; k++) 
                    cpl_image_reject_from_mask(casu_fits_get_image(infits2[k]),
                                               cplmask);
            
                /* Do the combination for this frameset */
                
                status = CASU_OK;
                (void)casu_imcombine(infits2,infvar2,nf2,1,1,0,1.0,"",&outim,
                                     &outim_var,&rejmask,&rejplus,&drs,&status);
                freespace(rejmask);
                freespace(rejplus);
                freepropertylist(drs);
                fpskys[j] = casu_fits_wrap(outim,infits[0],NULL,NULL);
                fpvars[j] = casu_fits_wrap(outim_var,infvar[0],NULL,NULL);
            } else {
                outim = casu_dummy_image(infits[0]);
                fpskys[j] = casu_fits_wrap(outim,infits[0],NULL,NULL);
                casu_fits_set_status(fpskys[j],CASU_FATAL);
                outim_var = casu_dummy_image(infvar[0]);
                fpvars[j] = casu_fits_wrap(outim_var,infvar[0],NULL,NULL);
            }
            freeframeset(subset);
            freefitslist(infits,nf);
            freespace(infits2);
            freefitslist(infvar,nf);
            freespace(infvar2);
        }
        cpl_mask_unwrap(cplmask);

        /* Now combine the first-pass skys to form a final sky for this 
           extension */

        nf2 = 0;
        infits2 = cpl_malloc(nlab*sizeof(casu_fits *));
        infvar2 = cpl_malloc(nlab*sizeof(casu_fits *));
        for (k = 0; k < nlab; k++) {
            if (casu_fits_get_status(fpskys[k]) == CASU_OK) {
                infits2[nf2] = fpskys[k];
                infvar2[nf2] = fpvars[k];
                nf2++;
            }
        }
        if (nf2 > 0) {
            (void)casu_imcombine(infits2,infvar2,nf2,1,1,0,1.0,"",&outim,
                                 &outim_var,&rejmask,&rejplus,&drs,&status);
            freespace(rejmask);
            freespace(rejplus);
            freepropertylist(drs);
            skyout = casu_fits_wrap(outim,fpskys[0],NULL,NULL);
            skyout_var = casu_fits_wrap(outim_var,fpvars[0],NULL,NULL);
            casu_inpaint(skyout,64,&status);
        } else {
            outim = casu_dummy_image(fpskys[0]);
            skyout = casu_fits_wrap(outim,fpskys[0],NULL,NULL);
            casu_fits_set_status(skyout,CASU_FATAL);
            outim_var = casu_dummy_image(fpvars[0]);
            skyout_var = casu_fits_wrap(outim_var,fpvars[0],NULL,NULL);
        }
        freefitslist(fpskys,nlab);
        freespace(infits2);
        freefitslist(fpvars,nlab);
        freespace(infvar2);

        /* Now save the sky frame */

        hawki_sci_save_sky(skyout,framelist,parlist,skyname,sky_template,
                           0,isfirst,skyframe);
        hawki_sci_save_sky(skyout_var,framelist,parlist,skyname_var,
                           sky_template,1,isfirst,skyframe_var);
        freefits(skyout);
        freefits(skyout_var);
    }
    
    /* Tidy and exit */

    freespace(labels);
    freeframeset(copy);
    return(CASU_OK);
}
            
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_pawsky_minus
    \par Purpose:
        Driver routine for the pawsky_minus algorithm
    \par Description:
        A set of frames is corrected using the pawsky_minus algorithm.
        This involves making a separate sky estimate for each frame in
        the set. The science images are saved along the way. The output
        sky frame is not that which was used to correct the input frames,
        but rather a sort of representative sky frame for the frameset.
    \par Language:
        C
    \param framelist
        The frameset with the SOF input frames
    \param parlist
        The parameter list with the input command line parameters
    \param in
        The frameset that contains all the frames that will contribute to 
        the sky images and be corrected by them
    \param invar
        The frameset that contains all the variances that will contribute to 
        the sky variance image
    \param sky_template
        A template frame used in the sky_save routine
    \param skyname
        A name for the output sky file
    \param skyname_var
        A name for the output sky variance file
    \param master_objmask
        The master object mask. This is optional and should be NULL if it
        doesn't exist.
    \param master_conf
        The master confidence map for the contributing frameset
    \param catname
        The name of the standard catalogue
    \param catpath
        The full path to the standard index file
    \param cacheloc
        The location for the standard star cache
    \param cdssearch
        The CDS catalogue to be searched for standards. 0 if using local
        catalogues.
    \param skyframe
        The output frame for the sky data product
    \param skyframe_var
        The output frame for the sky variance data product
    \retval CASU_OK if all went well
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_pawsky_minus(cpl_frameset *framelist, 
                                  cpl_parameterlist *parlist,
                                  cpl_frameset *in, cpl_frameset *invar,
                                  cpl_frame *sky_template, char *skyname, 
                                  char *skyname_var, cpl_frame *master_objmask, 
                                  cpl_frame *master_conf, char *catname, 
                                  char *catpath, char *cacheloc, int cdssearch, 
                                  cpl_frame **skyframe, 
                                  cpl_frame **skyframe_var) {
    int n,i,status,isfirst,j,k;
    casu_fits *objmaskfits,**infits,*inconf,*tmpfits,*skyout,**infits_var;
    casu_fits *skyout_var;
    cpl_frame **product_frames,*curframe,*template,**product_frames_var;
    char tmpname[BUFSIZ],*assoc,skyname2[BUFSIZ];
    const char *fctid = "hawki_sci_pawsky_minus";

    n = cpl_frameset_get_size(in);
    if (master_objmask != NULL) 
        objmaskfits = casu_fits_load(master_objmask,CPL_TYPE_INT,0);
    else
        objmaskfits = NULL;
    product_frames = cpl_malloc(n*sizeof(cpl_frame *));
    product_frames_var = cpl_malloc(n*sizeof(cpl_frame *));
    cpl_msg_info(fctid,"Creating sky %s",skyname);
    cpl_msg_indent_more();
    assoc = NULL;
    for (i = 1; i <= HAWKI_NEXTN; i++) {
        cpl_msg_info(fctid,"Beginning on extn %" CPL_SIZE_FORMAT "",
                     (cpl_size)i);
        infits = casu_fits_load_list(in,CPL_TYPE_FLOAT,i);
        for (k = 0; k < n; k++) {
            if (casu_is_dummy(casu_fits_get_ehu(infits[k])))
                casu_fits_set_status(infits[k],CASU_FATAL);
        }
        inconf = casu_fits_load(master_conf,CPL_TYPE_INT,i);
        infits_var = casu_fits_load_list(invar,CPL_TYPE_FLOAT,i);
        status = CASU_OK;
        if (master_objmask != NULL) {
            for (k = 0; k < n; k++)
                hawki_sci_wcsfit(infits+k,&inconf,NULL,1,1,catname,catpath,
                                 cacheloc,cdssearch);
        }
        if (casu_pawsky_minus(infits,infits_var,inconf,objmaskfits,n,&skyout,
                              &skyout_var,&status) == CASU_FATAL) {
            freefitslist(infits,n);
            freefitslist(infits_var,n);
            freefits(inconf);
            freespace(product_frames);
            freespace(product_frames_var);
            return(CASU_FATAL);
        }
        isfirst = (i == 1);
        hawki_sci_save_sky(skyout,framelist,parlist,skyname,sky_template,
                           0,isfirst,skyframe);
        (void)snprintf(skyname2,BUFSIZ,"%s[%d]",skyname,i);
        hawki_sci_save_sky(skyout_var,framelist,parlist,skyname_var,
                           sky_template,1,isfirst,skyframe_var);
        for (j = 0; j < n; j++) {
            tmpfits = casu_fits_duplicate(infits[j]);
            (void)snprintf(tmpname,BUFSIZ,"tmp_%s",
                           casu_fits_get_filename(infits[j]));
            casu_fits_set_filename(tmpfits,tmpname);
            curframe = cpl_frameset_get_position(in,j);
            template = hawki_sci_findtemplate(curframe,framelist);
            cpl_propertylist_update_string(casu_fits_get_ehu(tmpfits),
                                           "ESO DRS SKYCOR",
                                           skyname);
            hawki_sci_save_simple(tmpfits,framelist,parlist,0,template,
                                  isfirst,HAWKI_PRO_SIMPLE_SCI,tmpname,assoc,
                                  (product_frames+j));
            freefits(tmpfits);
            tmpfits = casu_fits_duplicate(infits_var[j]);
            (void)snprintf(tmpname,BUFSIZ,"tmp_%s",
                           casu_fits_get_filename(infits_var[j]));
            casu_fits_set_filename(tmpfits,tmpname);
            curframe = cpl_frameset_get_position(invar,j);
            template = hawki_sci_findtemplate(curframe,framelist);
            hawki_var_add(tmpfits,skyout_var);
            hawki_sci_save_simple(tmpfits,framelist,parlist,0,template,
                                  isfirst,HAWKI_PRO_VAR_SCI,tmpname,assoc,
                                  (product_frames_var+j));
        }
        freefitslist(infits,n);
        freefitslist(infits_var,n);
        freefits(inconf);
        freefits(skyout);
    }
    cpl_msg_indent_less();
    for (i = 0; i < n; i++) {
        curframe = cpl_frameset_get_position(in,i);
        template = product_frames[i];
        remove(cpl_frame_get_filename(curframe));
        rename(cpl_frame_get_filename(template),
               cpl_frame_get_filename(curframe));
        freeframe(product_frames[i]);
        curframe = cpl_frameset_get_position(invar,i);
        template = product_frames_var[i];
        remove(cpl_frame_get_filename(curframe));
        rename(cpl_frame_get_filename(template),
               cpl_frame_get_filename(curframe));
        freeframe(product_frames_var[i]);
    }
    freespace(product_frames);
    freespace(product_frames_var);
    freefits(objmaskfits);
    return(CASU_OK);
}

/*=========================== Save Routines =================================*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_save_simple
    \par Purpose:
        Save the simple image products
    \par Description:
        An extension of a simple image product is saved here
    \par Language:
        C
    \param obj
        The casu_fits structure for the image to be saved
    \param framelist
        The input recipe frameset
    \param parlist
        The input recipe parameter list
    \param isprod
        Set of this frame is to be an output data product
    \param template
        A template to be used in the cpl saving routine
    \param isfirst
        Set if this is the first extension to be written to the file
    \param tag
        The product tag for the saved file
    \param fname
        The output file name
    \param assoc
        The list of associated files for Phase III headers
    \param product_frame
        The output product frame
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_save_simple(casu_fits *obj, cpl_frameset *framelist,
                                 cpl_parameterlist *parlist, int isprod,
                                 cpl_frame *template, int isfirst,
                                 const char *tag, char *fname, char *assoc,
                                 cpl_frame **product_frame) {
    cpl_propertylist *plist;
    int isdummy,ndit;
    float dit;
    double texp;
    char filt[16];
    const char *fctid = "hawki_sci_save_simple";

    cpl_ensure(product_frame != NULL, CPL_ERROR_NULL_INPUT, CASU_FATAL);

    /* Get some information about this guy */

    isdummy = (casu_fits_get_status(obj) != CASU_OK);

    /* If we need to make a PHU then do that now based on the first frame
       in the input frame list. Get rid of the file if it already exists */

    if (isfirst) {
        if (access(fname,F_OK)) 
            remove(fname);

        /* Create a new product frame object and define some tags */

        *product_frame = cpl_frame_new();
        cpl_frame_set_filename(*product_frame,fname);

        /* Set the product tags */

        cpl_frame_set_tag(*product_frame,tag);
        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 = casu_fits_get_phu(obj);
        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,"RADECSYS","ICRS");
        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","PAWPRINT");
        cpl_propertylist_update_bool(plist,"ISAMP",1);
        cpl_propertylist_set_comment(plist,"ISAMP",
                                     "TRUE if image represents partially sampled sky");
        cpl_propertylist_erase_regexp(plist,"PROV[0-9]*",0);
        if (isprod && (strcmp(tag,HAWKI_PRO_SIMPLE_SCI) == 0 || 
                       strcmp(tag,HAWKI_PRO_SIMPLE_SKY) == 0)) {
            cpl_propertylist_update_bool(plist,"SINGLEXP",1);
            cpl_propertylist_set_comment(plist,"SINGLEXP",
                                         "TRUE if resulting from a single exposure");
            cpl_propertylist_update_int(plist,"NCOMBINE",1);
            cpl_propertylist_set_comment(plist,"NCOMBINE","Number of input images");
            cpl_propertylist_update_string(plist,"PRODCATG","SCIENCE.MEFIMAGE");
            cpl_propertylist_set_comment(plist,"PRODCATG","Data product category");
            cpl_propertylist_update_string(plist,"ASSON1",assoc);
            cpl_propertylist_set_comment(plist,"ASSON1","Associated file");
            cpl_propertylist_update_string(plist,"ASSOC1","ANCILLARY.VARMAP");
            cpl_propertylist_set_comment(plist,"ASSOC1","Associated file category");
            cpl_propertylist_update_string(plist,"PROV1",
                                           cpl_propertylist_get_string(plist,"ARCFILE"));
        } else {
            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");
        }
        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 = (double)(ndit*dit);
        cpl_propertylist_update_double(plist,"EXPTIME",texp);
        cpl_propertylist_update_double(plist,"TEXPTIME",texp);
        cpl_propertylist_update_double(plist,"EFF_EXPT",texp);
        cpl_propertylist_update_double(plist,"MJD-END",
                                       cpl_propertylist_get_double(plist,"MJD-OBS") +
                                       texp/86400.0);
        cpl_propertylist_set_comment(plist,"MJD-END","End of observations");
        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_string(plist,"FLUXCAL","UNCALIBRATED");
        cpl_propertylist_set_comment(plist,"FLUXCAL","Certifies the validity of PHOTZP");
        cpl_propertylist_update_double(plist,"DIT",
                                       cpl_propertylist_get_double(plist,"ESO DET DIT"));
        cpl_propertylist_set_comment(plist,"DIT","Detector integration time");
        
        /* '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(CASU_FATAL);
        }
        if (isprod) 
            cpl_frameset_insert(framelist,*product_frame);
    }

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

    /* Get the extension property list */

    plist = casu_fits_get_ehu(obj);
    if (isdummy)
        casu_dummy_property(plist);

    /* Add some PhaseIII stuff */

    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");
    }



    /* Fiddle with the header now */

    hawki_dfs_set_product_exten_header(plist,*product_frame,framelist,parlist,
                                       HAWKI_RECIPENAME,"PRO-1.15",template);
    if (cpl_image_save(casu_fits_get_image(obj),fname,CPL_TYPE_FLOAT,plist,
                       CPL_IO_EXTEND) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product image extension -- %s",
                      cpl_error_get_message());
        return(CASU_FATAL);
    }

    /* Get out of here */

    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_save_stack
    \par Purpose:
        Save an extension of stacked image or variance data products
    \par Description:
        An extension of a stacked image/variance data product is saved here. 
    \par Language:
        C
    \param stack
        The casu_fits structure for the stack to be saved
    \param framelist
        The input recipe frameset
    \param parlist
        The input recipe parameter list
    \param template
        A template to be used in the cpl saving routine
    \param fnametype
        The type of file name. This could either be 0 -> ESO 'predictable'
        names, 1 -> pretty names based on input file names, 2 -> temporary
        file names.   
    \param filetype
        Should be either STACK_FILE or STACK_VAR
    \param assoc
        The list of associated files for Phase III headers
    \param photosys
        The photometric system
    \param product_frame
        The output product frame
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_save_stack(casu_fits *stack, cpl_frameset *framelist,
                                cpl_parameterlist *parlist, 
                                cpl_frame *template, int fnametype, 
                                int filetype, char *assoc[], char *photosys,
                                cpl_frame **product_frame) {
    cpl_propertylist *plist;
    int isfirst,isdummy,ndit,n;
    float dit,texp;
    char bname[BUFSIZ],*base,*tname,filt[16];
    const char *fctid = "hawki_sci_save_stack";

    /* Get some information about this guy */

    isdummy = (casu_fits_get_status(stack) != CASU_OK);
    isfirst = (*product_frame == NULL);
    tname = cpl_strdup(cpl_frame_get_filename(template));
    base = basename(tname);
    hawki_sci_product_name(base,filetype,fnametype,1,bname);
    freespace(tname);

    /* If we need to make a PHU then do that now based on the first frame
       in the input frame list. Get rid of the file if it already exists */

    if (isfirst) {
        if (access(bname,F_OK)) 
            remove(bname);

        /* Create a new product frame object and define some tags */

        *product_frame = cpl_frame_new();
        cpl_frame_set_filename(*product_frame,bname);

        /* Define frame properties */

        if (filetype == STACK_FILE) 
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_JITTERED_SCI);
        else
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_JITTERED_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 = casu_fits_get_phu(stack);
        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,"RADECSYS","ICRS");
        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","PAWPRINT");
        cpl_propertylist_update_bool(plist,"ISAMP",1);
        cpl_propertylist_set_comment(plist,"ISAMP",
                                     "TRUE if image represents partially sampled sky");
        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);
        n = cpl_propertylist_get_int(plist,"NSTACK");
        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",(double)texp);
        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");
        if (filetype == STACK_FILE) {
            cpl_propertylist_update_bool(plist,"SINGLEXP",0);
            cpl_propertylist_set_comment(plist,"SINGLEXP",
                                         "TRUE if resulting from a single exposure");        
            cpl_propertylist_update_string(plist,"PRODCATG","SCIENCE.MEFIMAGE");
            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_erase(plist,"ASSOC1");
            cpl_propertylist_erase(plist,"ASSOC2");
            if (cpl_propertylist_has(casu_fits_get_ehu(stack),"ZPFUDGED")) {
                if (cpl_propertylist_get_bool(casu_fits_get_ehu(stack),"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");
        } else {
            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");
            cpl_propertylist_erase(plist,"NCOMBINE");
            cpl_propertylist_erase(plist,"NSTACK");
            cpl_propertylist_erase(plist,"FLUXCAL");
            cpl_propertylist_update_string(plist,"PRODCATG","ANCILLARY.VARMAP");
            cpl_propertylist_set_comment(plist,"PRODCATG","Data product category");
        }        
        cpl_propertylist_update_double(plist,"DIT",
                                       cpl_propertylist_get_double(plist,"ESO DET DIT"));
        cpl_propertylist_set_comment(plist,"DIT","Detector integration time");

        /* 'Save' the PHU image */                         

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

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

    /* Get the extension property list */

    plist = casu_fits_get_ehu(stack);
    if (isdummy)
        casu_dummy_property(plist);

    /* Add some PhaseIII stuff */

    cpl_propertylist_update_string(plist,"BUNIT","ADU");
    cpl_propertylist_set_comment(plist,"BUNIT","Physical unit of array values");
    cpl_propertylist_update_string(plist,"PHOTSYS",photosys);
    cpl_propertylist_set_comment(plist,"PHOTSYS","Photometric System");


    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");
    }

    /* Fiddle with the header now */

    hawki_dfs_set_product_exten_header(plist,*product_frame,framelist,
                                       parlist,HAWKI_RECIPENAME,
                                       "PRO-1.15",template);
    if (cpl_image_save(casu_fits_get_image(stack),bname,CPL_TYPE_FLOAT,
                       plist,CPL_IO_EXTEND) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product image extension -- %s",
                      cpl_error_get_message());
        return(CASU_FATAL);
    }

    /* Get out of here */

    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_save_stack_conf
    \par Purpose:
        Save an extension of stacked image confidence map data products
    \par Description:
        An extension of a stacked image confidence map data product is saved 
        here. 
    \par Language:
        C
    \param stack
        The casu_fits structure for the stack confidence map to be saved
    \param framelist
        The input recipe frameset
    \param parlist
        The input recipe parameter list
    \param template
        A template to be used in the cpl saving routine
    \param fnametype
        The type of file name. This could either be 0 -> ESO 'predictable'
        names, 1 -> pretty names based on input file names, 2 -> temporary
        file names.   
    \param product_frame
        The output product frame
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_save_stack_conf(casu_fits *stack, cpl_frameset *framelist,
                                     cpl_parameterlist *parlist, 
                                     cpl_frame *template, int fnametype,
                                     cpl_frame **product_frame) {
    cpl_propertylist *plist;
    int isfirst,isdummy;
    char bname[BUFSIZ],*base,*tname;
    const char *fctid = "hawki_sci_save_stack_conf";
    char filt[16];
    int n,ndit;
    float dit,texp;

    /* Get some information about this guy */

    isdummy = (casu_fits_get_status(stack) != CASU_OK);
    isfirst = (*product_frame == NULL);
    tname = cpl_strdup(cpl_frame_get_filename(template));
    base = basename(tname);
    hawki_sci_product_name(base,STACK_CONF,fnametype,1,bname);
    freespace(tname);

    /* If we need to make a PHU then do that now based on the first frame
       in the input frame list. Get rid of the file if it already exists */

    if (isfirst) {
        if (access(bname,F_OK)) 
            remove(bname);

        /* Create a new product frame object and define some tags */

        *product_frame = cpl_frame_new();
        cpl_frame_set_filename(*product_frame,bname);

        /* Tag the product */

        cpl_frame_set_tag(*product_frame,HAWKI_PRO_CONF_JITTERED);
        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 = casu_fits_get_phu(stack);

        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");

        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);

        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");
        cpl_propertylist_erase(plist,"NCOMBINE");
        cpl_propertylist_erase(plist,"NSTACK");
        cpl_propertylist_erase(plist,"PROV*");
        cpl_propertylist_erase(plist,"FLUXCAL");
        cpl_propertylist_update_string(plist,"PRODCATG","ANCILLARY.WEIGHTMAP");
        cpl_propertylist_set_comment(plist,"PRODCATG","Data product category");
        hawki_dfs_set_product_primary_header(plist,*product_frame,framelist,
                                             parlist,HAWKI_RECIPENAME,
                                             "PRO-1.15",template,1);

        /* 'Save' the PHU image */                         

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

    /* Get the extension property list */

    plist = casu_fits_get_ehu(stack);
    if (isdummy)
        casu_dummy_property(plist);

    /* Fiddle with the header now */

    hawki_dfs_set_product_exten_header(plist,*product_frame,framelist,
                                       parlist,HAWKI_RECIPENAME,
                                       "PRO-1.15",template);
    if (cpl_image_save(casu_fits_get_image(stack),bname,CPL_TYPE_INT,
                       plist,CPL_IO_EXTEND) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product image extension -- %s",
                      cpl_error_get_message());
        return(CASU_FATAL);
    }

    /* Get out of here */

    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_save_cat
    \par Purpose:
        Save an extension of image source catalogue data products
    \par Description:
        An extension of an image source catalogue data product is 
        saved here. 
    \par Language:
        C
    \param scat
        The casu_tfits structure for the source catalogue to be saved
    \param framelist
        The input recipe frameset
    \param parlist
        The input recipe parameter list
    \param template
        A template to be used in the cpl saving routine
    \param fnametype
        The type of file name. This could either be 0 -> ESO 'predictable'
        names, 1 -> pretty names based on input file names, 2 -> temporary
        file names.   
    \param ptype
        The product type. Either STACK_CAT or SIMPLE_CAT
    \param fnumber
        The file number. Used if the ESO name is chosen
    \param photosys
        The photometric system
    \param product_frame
        The output product frame
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_save_cat(casu_tfits *scat, cpl_frameset *framelist,
                              cpl_parameterlist *parlist, 
                              cpl_frame *template, int fnametype, int ptype,
                              int fnumber, char *photosys,
                              cpl_frame **product_frame) {
    cpl_propertylist *plist;
    int isfirst,isdummy,n,ndit;
    char bname[BUFSIZ],*base,*tname;
    float dit,texp;
    const char *fctid = "hawki_sci_save_cat";
    char filt[16];

    /* Get some information about this guy */

    isdummy = (casu_tfits_get_status(scat) != CASU_OK);
    isfirst = (*product_frame == NULL);
    tname = cpl_strdup(cpl_frame_get_filename(template));
    base = basename(tname);
    hawki_sci_product_name(base,ptype,fnametype,fnumber,bname);
    freespace(tname);

    /* If we need to make a PHU then do that now based on the first frame
       in the input frame list. Get rid of the file if it already exists */

    if (isfirst) {
        if (access(bname,F_OK)) 
            remove(bname);

        /* Create a new product frame object and define some tags */

        *product_frame = cpl_frame_new();
        cpl_frame_set_filename(*product_frame,bname);

        /* Tag the product */

        switch (ptype) {
        case STACK_CAT:
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_OBJCAT_JITTERED);
            break;
        case SIMPLE_CAT:
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_OBJCAT_SCI);
            break;
        case SIMPLE_MSTD_ASTROM:
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_MATCHSTD_ASTROM);
            break;
        case STACK_MSTD_ASTROM:
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_MATCHSTD_ASTROM);
            break;
        case MSTD_PHOTOM:
            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(scat);
        hawki_dfs_set_product_primary_header(plist,*product_frame,framelist,
                                             parlist,HAWKI_RECIPENAME,
                                             "PRO-1.15",NULL,1);

        /* Set some PhaseIII parameters */

        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");

        cpl_propertylist_update_string(plist,"RADECSYS","ICRS");
        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_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_set_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");
        if (ptype == STACK_CAT) {
            cpl_propertylist_update_bool(plist,"SINGLEXP",0);
            n = cpl_propertylist_get_int(plist,"NSTACK");
            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",(double)texp);

            /* Write/update the FLUCAL keyword */
            if (cpl_propertylist_has(casu_tfits_get_ehu(scat),"ZPFUDGED")) {
                if (cpl_propertylist_get_bool(casu_tfits_get_ehu(scat),"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");

        } else {
            cpl_propertylist_update_bool(plist,"SINGLEXP",1);
        }
        cpl_propertylist_set_comment(plist,"SINGLEXP",
                                     "TRUE if resulting from a single exposure");        
        cpl_propertylist_update_bool(plist,"ISAMP",1);
        cpl_propertylist_set_comment(plist,"ISAMP",
                                     "TRUE if image represents partially sampled sky");

        cpl_propertylist_erase(plist,"ASSON1");
        cpl_propertylist_erase(plist,"ASSON2");
        cpl_propertylist_erase(plist,"ASSOC1");
        cpl_propertylist_erase(plist,"ASSOC2");       

        /* 'Save' the PHU image */                         

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

    /* Get ndit from the primary for the correction */

    plist = casu_tfits_get_phu(scat);
    hawki_pfits_get_ndit(plist,&ndit);

    /* Get the extension property list */

    plist = casu_tfits_get_ehu(scat);
    if (isdummy)
        casu_dummy_property(plist);

    /* Fiddle with the header now */

    cpl_propertylist_update_string(plist,"PHOTSYS",photosys);
    cpl_propertylist_set_comment(plist,"PHOTSYS","Photometric System");
    cpl_propertylist_erase(plist,"BUNIT");

    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",NULL);
    if (cpl_table_save(casu_tfits_get_table(scat),NULL,
                       plist,bname,CPL_IO_EXTEND) != CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product table extension -- %s",
                      cpl_error_get_message());
        return(CASU_FATAL);
    }

    /* Get out of here */

    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_save_sky
    \par Purpose:
        Save the mean sky image products
    \par Description:
        Extensions of sky frame image products are saved here.
    \par Language:
        C
    \param outsky
        The casu_fits structure for the sky frame to be saved
    \param framelist
        The input recipe frameset
    \param parlist
        The input recipe parameter list
    \param fname
        The name of the output file 
    \param template
        A template to be used in the cpl saving routine
    \param isvar
        Set if this is the sky variance frame
    \param isfirst
        Set if this is the first extension to be saved.
    \param product_frame
        The output product frame
    \retval CASU_OK if everything is ok
    \retval CASU_FATAL if something failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_save_sky(casu_fits *outsky, cpl_frameset *framelist,
                              cpl_parameterlist *parlist, 
                              char *fname, cpl_frame *template, int isvar,
                              int isfirst, cpl_frame **product_frame) {
    cpl_propertylist *p;
    int isdummy;
    cpl_image *fim;
    const char *fctid = "hawki_sci_save_sky";

    /* Work out which frame to base the output on. If this particular
       sequence failed for whatever reason, there will be a dummy sky frame. */

    fim = casu_fits_get_image(outsky);
    isdummy = (casu_fits_get_status(outsky) != CASU_OK);

    /* If we need to make a PHU then do that now based on the first frame
       in the input frame list */

    if (isfirst) {

        /* Create a new product frame object and define some tags */

        *product_frame = cpl_frame_new();
        cpl_frame_set_filename(*product_frame,fname);
        if (isvar) 
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_MEAN_SKY_VAR);
        else
            cpl_frame_set_tag(*product_frame,HAWKI_PRO_MEAN_SKY);
        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. */

        p = casu_fits_get_phu(outsky);
        hawki_dfs_set_product_primary_header(p,*product_frame,framelist,
                                             parlist,HAWKI_RECIPENAME,
                                             "PRO-1.15",template,0);

        /* 'Save' the PHU image */                         

        if (cpl_image_save(NULL,fname,CPL_TYPE_UCHAR,p,
                           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 the extension property list */

    p = cpl_propertylist_duplicate(casu_fits_get_ehu(outsky));
    if (isdummy)
        casu_dummy_property(p);

    /* Fiddle with the header now */

    hawki_dfs_set_product_exten_header(p,*product_frame,framelist,parlist,
                                       HAWKI_RECIPENAME,"PRO-1.15",template);
    if (cpl_image_save(fim,fname,CPL_TYPE_FLOAT,p,CPL_IO_EXTEND) != 
        CPL_ERROR_NONE) {
        cpl_msg_error(fctid,"Cannot save product image extension");
        return(-1);
    }

    /* Quick tidy */

    cpl_propertylist_delete(p);

    /* Get out of here */

    return(0);
}

/*========================= Other Useful Routines ============================*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_findtemplate
    \par Purpose:
        Find a template frame for a current frame by finding the original file.
    \par Description:
        The input frameset is searched to match the mjd of a current frame.
        This provides a template for writing the new frame out.
    \par Language:
        C
    \param in
        Input frame to be matched
    \param framelist
        The frameset of the input raw files
    \return
        The frame of the matching raw file or NULL if no match is found
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_frame *hawki_sci_findtemplate(cpl_frame *in, 
                                         cpl_frameset *framelist) {
    int n,i,matched;
    double mjd1,mjd2;
    cpl_frame *frm;
    cpl_propertylist *p;

    /* Get the mjd of the input frame */

    p = cpl_propertylist_load(cpl_frame_get_filename(in),0);
    (void)hawki_pfits_get_mjd(p,&mjd1);
    cpl_propertylist_delete(p);

    /* Now search the input files for the matching one...*/

    matched = 0;
    n = cpl_frameset_get_size(framelist);
    for (i = 0; i < n; i++) {
        frm = cpl_frameset_get_position(framelist,i);
        p = cpl_propertylist_load(cpl_frame_get_filename(frm),0);
        (void)hawki_pfits_get_mjd(p,&mjd2);
        cpl_propertylist_delete(p);
        if (mjd1 == mjd2) {
            matched = 1;
            break;
        }
    }
    if (matched) 
        return(frm);
    else 
        return(NULL);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_update_frameset
    \par Purpose:
        Create a new frameset from an old one and update the file names
    \par Description:
        A new frameset is created from an old one. The file names in the
        new frameset are modified so that they are only the basenames of
        the filenames in the old frameset (i.e. directory info is stripped)
    \par Language:
        C
    \param frms
        The input frameset
    \param cs
        The config structure defined in the main routine
    \param ftype
        The file type (used for creating file name)
    \param runno
        The starting run number for the standard ESO file names
    \return
        The output frameset
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_frameset *hawki_sci_update_frameset(cpl_frameset *frms, 
                                               configstruct *cs,
                                               int ftype, int *runno) {
    cpl_frameset *copy;
    cpl_size i,n;
    cpl_frame *fr;
    char *fname,bname[BUFSIZ];

    /* NULL input... */

    if (frms == NULL)
        return(NULL);

    /* First make a copy of the input frameset */

    copy = cpl_frameset_duplicate(frms);

    /* Now go through and change the file names */

    n = cpl_frameset_get_size(frms);
    for (i = 0; i < n; i++) {
        fr = cpl_frameset_get_position(copy,i);
        fname = cpl_strdup(cpl_frame_get_filename(fr));
        hawki_sci_product_name(fname,ftype,cs->prettynames,i+1+(*runno),bname);
        cpl_frame_set_filename(fr,bname);
        cpl_free(fname);
    }

    /* Get out of here */

    return(copy);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_charshift
    \par Purpose:
        Utility routine to get a characteristic shift in a jitter sequence
    \par Description:
        Calculate the shifts of all frames in a frameset compared to the first
        one. Work out the mean absolute shift in each coordinate.
    \par Language:
        C
    \param frms
        The input frameset
    \param xoff
        The mean absolute shift in the X direction
    \param yoff
        The mean absolute shift in the Y direction
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_charshift(cpl_frameset *frms, float *xoff, float *yoff) {
    float x,y;
    int n,i,status;
    cpl_propertylist *p1,*p2;
    cpl_frame *frm1,*frm2;
    cpl_wcs *wcs1,*wcs2;

    /* Get the size of the frameset. If there is only 1 then set the offsets
       to be zero */

    n = cpl_frameset_get_size(frms);
    if (n < 2) {
        *xoff = 0.0;
        *yoff = 0.0;
        return;
    }

    /* Loop through the frameset */

    frm1 = cpl_frameset_get_position(frms,0);
    p1 = cpl_propertylist_load(cpl_frame_get_filename(frm1),1);
    wcs1 = cpl_wcs_new_from_propertylist(p1);
    *xoff = 0.0;
    *yoff = 0.0;
    for (i = 0; i < n-1; i++) {
        frm2 = cpl_frameset_get_position(frms,i+1);
        p2 = cpl_propertylist_load(cpl_frame_get_filename(frm2),1);
        wcs2 = cpl_wcs_new_from_propertylist(p2);
        status = CASU_OK;
        (void)casu_diffxywcs(wcs2,wcs1,&x,&y,&status);
        *xoff += (float)fabs((double)x);
        *yoff += (float)fabs((double)y);
        cpl_wcs_delete(wcs2);
        cpl_propertylist_delete(p2);
    }
    cpl_propertylist_delete(p1);
    cpl_wcs_delete(wcs1);

    /* Get the mean absolute shift in each direction */
    
    *xoff /= (float)(n-1);
    *yoff /= (float)(n-1);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_cmp_tstart
    \par Purpose:
        Utility routine to compare template start times
    \par Description:
        Compare the template start times between two frames
    \par Language:
        C
    \param frame1
        The first input frame
    \param frame2
        The second input frame
    \retval 0 if the frames are from templates with a different start time
    \retval 1 if the frames are from templates with the same start time
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_cmp_tstart(const cpl_frame *frame1, 
                                const cpl_frame *frame2) {
    char ts1[80],ts2[80];
    cpl_propertylist *pp;

    /* Test entries */

    if (frame1 == NULL || frame2 == NULL)
        return(-1);

    /* Load the propertylist for each frame and extract the template start
       time */

    pp = cpl_propertylist_load(cpl_frame_get_filename(frame1),0);
    (void)hawki_pfits_get_tplstart(pp,ts1);
    cpl_propertylist_delete(pp);
    pp = cpl_propertylist_load(cpl_frame_get_filename(frame2),0);
    (void)hawki_pfits_get_tplstart(pp,ts2);
    cpl_propertylist_delete(pp);

    /* Compare the start times */

    if (strcmp(ts1,ts2))
        return(0);
    else 
        return(1);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_all1tpl
    \par Purpose:
        See if all the frames in a frameset were all observed in one template
    \par Description:
        The template start times in a list of frames are compared to see if
        they are all the same. If so, then it is assumed that they all 
        originated in the same frameset
    \par Language:
        C
    \param frms
        The input frameset
    \retval 0 if the frameset is composed of frames from multiple tempalates
    \retval 1 if the frameset is composed of frames from a single template
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int hawki_sci_all1tpl(cpl_frameset *frms) {
    char tpl1[64],tpl2[64];
    int n,i;
    cpl_propertylist *p;

    /* Silly situation with only 1 frame... */
    
    n = cpl_frameset_get_size(frms);
    if (n <= 1)
        return(1);

    /* Get the start time for the first frame */

    p = cpl_propertylist_load(cpl_frame_get_filename(cpl_frameset_get_position(frms,0)),0);
    hawki_pfits_get_tplstart(p,tpl1);
    cpl_propertylist_delete(p);

    /* Now loop through and as soon as we get one that doesn't match then
       we break out */

    for (i = 1; i < n; i++) {
        p = cpl_propertylist_load(cpl_frame_get_filename(cpl_frameset_get_position(frms,i)),0);
        hawki_pfits_get_tplstart(p,tpl2);
        cpl_propertylist_delete(p);
        if (strcmp(tpl1,tpl2)) 
            return(0);
    }

    /* If we've gotten this far, then they are all the same... */

    return(1);
}
    
/*---------------------------------------------------------------------------*/
/**
    \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(char *template, int producttype, 
                                   int nametype, int fnumber, char *outfname) {
    const char *esonames[] = {"exp_","exp_var_","exp_cat_","stack_",
                              "stack_var_","stack_conf_","stack_cat_","sky_",
                              "sky_var_","exp_mstd_a_","stack_mstd_a_",
                              "stack_mstd_p_"};
    const char *suffix[] = {"_ex","_ex_var","_ex_cat","_st","_st_var",
                            "_st_conf","_st_cat","sky_","sky_var_","_ex_mstd_a",
                            "_st_mstd_a","_st_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);
        if (producttype != SKY_FILE && producttype != SKY_FILE_VAR) {
            (void)sprintf(outfname,"%s",bname);
            dot = strrchr(outfname,'.');
            (void)sprintf(dot,"%s.fits",suffix[producttype]);
        } else {
            sprintf(outfname,"%s%s",suffix[producttype],bname);
        }
        freespace(fname);
        break;

    /* something else ?? */

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

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_update_hdr
    \par Purpose:
        Update header of simple products to give them photometric info
    \par Description:
        Simple products are not photometrically calibrated. This routine
        gives the simple products the photometric information that is
        calculated for the stacked images, which will be roughly right.
        This is provided as a stop-gap until CPL can provide a way to update
        the header of a file without having to re-write the whole file.
    \par Language:
        C
    \param simple
        A frameset of simple products
    \param stack
        The stacked image
    \param typef
        The type of file: 1 => MEF of float images, 2 => MEF of binary
        tables
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_update_hdr(cpl_frameset *simple, cpl_frame *stack, 
                                 int typef) {
    int i,j,k,np_prim=1,np_extn=4,nsimple;
    const char *wanted_primary[] = {"FLUXCAL"};
    const char *wanted_extn[] = {"PHOTSYS","ABMAGSAT","PHOTZP","ABMAGLIM"};
    char tmpfname[BUFSIZ];
    cpl_propertylist *p,*new_primary,*new_extn[HAWKI_NEXTN];
    cpl_frame *fr;
    cpl_image *im;
    cpl_table *tab;

    /* First of all extract the stuff you really want from the primary header
       of the stack */

    new_primary = cpl_propertylist_new();
    p = cpl_propertylist_load(cpl_frame_get_filename(stack),0);
    for (i = 0; i < np_prim; i++) 
        cpl_propertylist_copy_property(new_primary,p,wanted_primary[i]); 
    cpl_propertylist_delete(p);

    /* Now from the extensions */

    for (j = 1; j <= HAWKI_NEXTN; j++) {
        p = cpl_propertylist_load(cpl_frame_get_filename(stack),(cpl_size)j);
        new_extn[j-1] = cpl_propertylist_new();
        for (i = 0; i < np_extn; i++) 
            cpl_propertylist_copy_property(new_extn[j-1],p,wanted_extn[i]);
        cpl_propertylist_delete(p);
    }
        
    /* Loop for each file. Define a temporary file name and load the 
       primary header */

    nsimple = cpl_frameset_get_size(simple);
    for (k = 0; k < nsimple; k++) {
        fr = cpl_frameset_get_position(simple,k);
        (void)sprintf(tmpfname,"scitmp_%s",cpl_frame_get_filename(fr));
        if (access(tmpfname,F_OK)) 
            remove(tmpfname);
        p = cpl_propertylist_load(cpl_frame_get_filename(fr),0);

        /* Update the header and write it out */

        casu_merge_propertylists(p,new_primary);
        cpl_image_save(NULL,tmpfname,CPL_TYPE_UCHAR,p,CPL_IO_DEFAULT);
        cpl_propertylist_delete(p);

        /* Loop for each extension now and do exactly the same thing. The
           only difference now is that we have to load either the image
           or table so that we can write it back out again */

        for (i = 1; i <= HAWKI_NEXTN; i++) {
            p = cpl_propertylist_load(cpl_frame_get_filename(fr),i);
            casu_merge_propertylists(p,new_extn[i-1]);
            if (typef == 1) {
                im = cpl_image_load(cpl_frame_get_filename(fr),
                                    CPL_TYPE_FLOAT,0,i);
                cpl_image_save(im,tmpfname,CPL_TYPE_FLOAT,p,CPL_IO_EXTEND);
                cpl_image_delete(im);
            } else {
                tab = cpl_table_load(cpl_frame_get_filename(fr),i,0);
                cpl_table_save(tab,NULL,p,tmpfname,CPL_IO_EXTEND);
                cpl_table_delete(tab);
            }
            cpl_propertylist_delete(p);
        }

        /* Now get rid of the old file and rename the new one */

        remove(cpl_frame_get_filename(fr));
        rename(tmpfname,cpl_frame_get_filename(fr));
    }

    /* Delete some stuff and get out of here */

    cpl_propertylist_delete(new_primary);
    for (i = 0; i < HAWKI_NEXTN; i++)
        cpl_propertylist_delete(new_extn[i]);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_update_hdr_stack
    \par Purpose:
        Update header of stack products to give them correct central 
        coordinates. Also deal with NULL photometric values
    \par Description:
        The WCS of the stack images are used to define a true central
        point for the coverage of the current stack. This is written 
        to the RA and Dec keywords in the primary of the products
    \par Language:
        C
    \param stack
        The pawprint structure for the current stack
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_update_hdr_stack(pawprint *paw) {
    double ra1,dec1,ra2,dec2,ramin,ramax,decmin,decmax,sum1,sum2;
    double abmagsat[HAWKI_NEXTN],abmaglim[HAWKI_NEXTN];
    int i,status,ngood;
    cpl_propertylist *p[4],*pp;
    cpl_image *im;

    /* For each extension of the input stack, get the extension header 
       and work out the central RA and Dec */

    ramin = 370.0;
    ramax = -370.0;
    decmin = 95.0;
    decmax = -95.0;
    sum1 = 0.0;
    sum2 = 0.0;
    ngood = 0;
    for (i = 1; i <= HAWKI_NEXTN; i++) {
        status = CASU_OK;
        pp = casu_fits_get_ehu(paw->stack[i-1]);
        im = casu_fits_get_image(paw->stack[i-1]);
        cpl_propertylist_update_int(pp,"NAXIS1",cpl_image_get_size_x(im));
        cpl_propertylist_update_int(pp,"NAXIS2",cpl_image_get_size_y(im));
        cpl_propertylist_update_int(pp,"ZNAXIS1",cpl_image_get_size_x(im));
        cpl_propertylist_update_int(pp,"ZNAXIS2",cpl_image_get_size_y(im));
        (void)casu_coverage(pp,0,&ra1,&ra2,&dec1,&dec2,&status);
        ramin = min(ramin,ra1);
        ramax = max(ramax,ra2);
        decmin = min(decmin,dec1);
        decmax = max(decmax,dec2);
        if (cpl_propertylist_has(pp,"ABMAGSAT")) 
            abmagsat[i-1] = cpl_propertylist_get_double(pp,"ABMAGSAT");
        else
            abmagsat[i-1] = 0.0;
        sum1 += abmagsat[i-1];
        if (abmagsat[i-1] != 0.0)
            ngood++;
        if (cpl_propertylist_has(pp,"ABMAGLIM")) 
            abmaglim[i-1] = cpl_propertylist_get_double(pp,"ABMAGLIM");
        else
            abmaglim[i-1] = 0.0;
        sum2 += abmaglim[i-1];
    }
    ra1 = 0.5*(ramin + ramax);
    if (ra1 < 0.0)
        ra1 += 360.0;
    dec1 = 0.5*(decmin + decmax);

    /* Fix the extension photometric stuff */

    if (ngood > 0) {
        sum1 /= (double)ngood;
        sum2 /= (double)ngood;
        for (i = 1; i <= HAWKI_NEXTN; i++) {
            pp = casu_fits_get_ehu(paw->stack[i-1]);
            if (abmagsat[i-1] == 0.0)
                cpl_propertylist_update_double(pp,"ABMAGSAT",sum1);
            if (abmaglim[i-1] == 0.0)
                cpl_propertylist_update_double(pp,"ABMAGLIM",sum2);
        }
    }

    /* Load up the propertylists to be updated */

    p[0] = casu_fits_get_phu(paw->stack[0]);
    p[1] = casu_fits_get_phu(paw->stackc[0]);
    p[2] = casu_fits_get_phu(paw->stackv[0]);
    p[3] = casu_tfits_get_phu(paw->cat[0]);

    /* Loop for each one now and update the RA and Dec value */
        
    for (i = 0; i < 4; i++) {
        cpl_propertylist_update_double(p[i],"RA",ra1);
        cpl_propertylist_set_comment(p[i],"RA","RA of field centre");
        cpl_propertylist_update_double(p[i],"DEC",dec1);
        cpl_propertylist_set_comment(p[i],"DEC","Dec of field centre");
    }
}
        
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_update_fwhm_dimm
    \par Purpose:
        Update header of stack products with the FWHM from the DIMM
        monitor, in case there are not enough stars to compute it from the
        catalogues.
    \par Description:
        Update header of stack products with the FWHM from the DIMM
        monitor, in case there are not enough stars to compute it from the
        catalogues.
    \par Language:
        C
    \param stack
        The pawprint structure for the current stack
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_update_fwhm_dimm(pawprint *paw) 
{
    int n_goood_psf = 0;
    double psf_fwhm_mean = 0;
    int bad_psf_idx[HAWKI_NEXTN];
    double psf_fwhm_dimm = 0;

    //Mark extensions without a valid FWHM and
    //compute a mean of the good FWHM
    for (int i = 0; i < HAWKI_NEXTN; i++) 
    {
        double psf_fwhm;
        psf_fwhm = cpl_propertylist_get_float(casu_tfits_get_ehu(paw->cat[i]),
                                          "PSF_FWHM");
        if(psf_fwhm != -1)
        {
            psf_fwhm_mean = (psf_fwhm_mean * n_goood_psf + psf_fwhm) / 
                            (n_goood_psf + 1); //Running mean
            n_goood_psf++;
        }
        else
            bad_psf_idx[i-n_goood_psf] = i;
    }

    //If there is no good PSF in any extension, take it from the DIMM
    if(n_goood_psf == 0)
        psf_fwhm_dimm = hawki_get_dimm_fwhm(casu_fits_get_phu(paw->stack[0]));

    for (int i_bad = 0; i_bad < HAWKI_NEXTN - n_goood_psf; i_bad++)
    {
        if(n_goood_psf == 0)
        {
            if(psf_fwhm_dimm < 0)
            {
                cpl_propertylist_update_float
                    (casu_fits_get_ehu(paw->stack[bad_psf_idx[i_bad]]), 
                     "PSF_FWHM", -1);
                cpl_propertylist_set_comment
                    (casu_fits_get_ehu(paw->stack[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", "no spatial res. could be computed");
                cpl_propertylist_update_float
                    (casu_tfits_get_ehu(paw->cat[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", -1);
                cpl_propertylist_set_comment
                    (casu_tfits_get_ehu(paw->cat[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", "no spatial res. could be computed");
            }
            else
            {
                cpl_propertylist_update_float(casu_fits_get_ehu
                    (paw->stack[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", psf_fwhm_dimm);
                cpl_propertylist_set_comment
                    (casu_fits_get_ehu(paw->stack[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", 
                     "spatial res. computed from DIMM seeing [arcsec]");
                cpl_propertylist_update_float
                    (casu_tfits_get_ehu(paw->cat[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", psf_fwhm_dimm);
                cpl_propertylist_set_comment
                    (casu_tfits_get_ehu(paw->cat[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", 
                     "spatial res. computed from DIMM seeing [arcsec]");
            }
        }
        else
        {
                cpl_propertylist_update_float(casu_fits_get_ehu
                    (paw->stack[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", psf_fwhm_mean);
                cpl_propertylist_set_comment
                    (casu_fits_get_ehu(paw->stack[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", 
                     "spatial res. computed from ext avg [arcsec]");
                cpl_propertylist_update_float
                    (casu_tfits_get_ehu(paw->cat[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", psf_fwhm_mean);
                cpl_propertylist_set_comment
                    (casu_tfits_get_ehu(paw->cat[bad_psf_idx[i_bad]]),
                     "PSF_FWHM", 
                     "spatial res. computed from ext avg [arcsec]");
        }
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_update_catprov
    \par Purpose:
        Update provenance in the header of catalogues
    \par Description:
        Update the provenance of the header of catalogues so that they
        point to the originating image. 
    \par Language:
        C
    \param catfrm
        The catalogue frame
    \param imfrm
        The image frame
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_update_catprov(cpl_frame *catfrm, cpl_frame *imfrm) {
    char *fname,tmpfname[BUFSIZ];
    cpl_table *tab;
    cpl_propertylist *plist;
    int i,nextn;

    /* Get the image name */

    fname = (char *)cpl_frame_get_filename(imfrm);

    /* Create an output file */

    (void)sprintf(tmpfname,"scitmp_%s",cpl_frame_get_filename(catfrm));
    nextn = cpl_frame_get_nextensions(catfrm);

    /* Loop for each extension. For the primary replace the provenance.
       For all the others, just do a simple copy */

    for (i = 0; i <= nextn; i++) {
        plist = cpl_propertylist_load(cpl_frame_get_filename(catfrm),i);
        if (i == 0) {
            cpl_propertylist_erase_regexp(plist,"PROV[0-9]*",0);
            cpl_propertylist_append_string(plist,"PROV1",fname);
            cpl_propertylist_set_comment(plist,"PROV1","Originating image");
            cpl_image_save(NULL,tmpfname,CPL_TYPE_UCHAR,plist,CPL_IO_DEFAULT);
        } else {
            tab = cpl_table_load(cpl_frame_get_filename(catfrm),i,0);
            cpl_table_save(tab,NULL,plist,tmpfname,CPL_IO_EXTEND);
            cpl_table_delete(tab);
        }
        cpl_propertylist_delete(plist);
    }
    remove(cpl_frame_get_filename(catfrm));
    rename(tmpfname,cpl_frame_get_filename(catfrm));       
}
    
    
/**@}*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_init
    \par Purpose:
        Initialise pointers in the recipe memory structure.
    \par Description:
        Initialise pointers in the recipe memory structure.
    \par Language:
        C
    \param ps
        The memory structure
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_init(memstruct *ps) {

    /* Level 0 stuff */

    ps->labels = NULL;
    ps->master_dark = NULL;
    ps->master_twilight_flat = NULL;
    ps->master_conf = NULL;
    ps->master_sky = NULL;
    ps->master_objmask = NULL;
    ps->master_mstd_phot = NULL;
    ps->mask = NULL;
    ps->phottab = NULL;
    ps->tphottab = NULL;
    ps->science_frames = NULL;
    ps->offset_skies = NULL;
    ps->product_frames_simple = NULL;
    ps->product_frames_simple_off = NULL;
    ps->product_frames_simple_var = NULL;
    ps->product_frames_simple_off_var = NULL;
    ps->gaincors = NULL;
    ps->catpath_a = NULL;
    ps->catname_a = NULL;
    ps->catpath_p = NULL;
    ps->catname_p = NULL;
    ps->scipaw = NULL;
    ps->offpaw = NULL;
    ps->nskys = 0;
    ps->skys = NULL;
    ps->schlf_n = NULL;
    ps->schlf_s = NULL;
    ps->readgain = NULL;

    /* Level 1 stuff */

    ps->fdark = NULL;
    ps->fdark_var = NULL;
    ps->fflat = NULL;
    ps->fconf = NULL;
    ps->fsky = NULL;
    ps->nscience = 0;
    ps->sci_fits = NULL;
    ps->noffsets = 0;
    ps->offsky_fits = NULL;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        hawki_sci_tidy
    \par Purpose:
        Free allocated workspace in the recipe memory structure
    \par Description:
        Free allocated workspace in the recipe memory structure. The tidy works
        on two levels. Level 1 is for items that are usually cleared up after
        each extension is processed. Level 0 is for cleaning up the whole
        recipe
    \par Language:
        C
    \param ps
        The memory structure
    \param level
        The level of the tidy to be done. 1: Tidy up after finishing an 
        extension, 0: Tidy up after finishing the recipe.
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void hawki_sci_tidy(memstruct *ps, int level) {
    int i;

    /* Level 1 stuff */

    freefits(ps->fdark); 
    freefits(ps->fdark_var); 
    freefits(ps->fflat); 
    freefits(ps->fconf);
    freefits(ps->fsky);
    freefitslist(ps->sci_fits,ps->nscience);
    freefitslist(ps->offsky_fits,ps->noffsets);
    ps->nscience = 0;

    if (level == 1)
        return;
    
    /* Level 0 stuff */

    freespace(ps->labels);
    freeframe(ps->master_dark);
    freeframe(ps->master_twilight_flat);
    freeframe(ps->master_conf);
    freeframe(ps->master_sky);
    freeframe(ps->master_objmask);
    freeframe(ps->master_mstd_phot);
    freemask(ps->mask);
    freeframe(ps->phottab);
    freetable(ps->tphottab);
    freeframeset(ps->science_frames);
    freeframeset(ps->offset_skies);
    freespace(ps->product_frames_simple);     /* NB: We only have to delete */
                                              /* the arrays and not the */
                                              /* frames as these get passed */
    freespace(ps->product_frames_simple_off); /* back to esorex */
    freespace(ps->product_frames_simple_var);
    freespace(ps->product_frames_simple_off_var);
    freespace(ps->gaincors);
    freespace(ps->catpath_a);
    freespace(ps->catname_a);
    freespace(ps->catpath_p);
    freespace(ps->catname_p);
    if (ps->skys != NULL) {
        for (i = 0; i < ps->nskys; i++) {
            freeframeset(ps->skys[i].contrib);
            freeframeset(ps->skys[i].contrib_var);
            freeframe(ps->skys[i].objmask);
            freeframe(ps->skys[i].template);
        }
        freespace(ps->skys);
    }
    hawki_sci_paw_delete(&(ps->scipaw));
    hawki_sci_paw_delete(&(ps->offpaw));
    freeframe(ps->schlf_n);
    freeframe(ps->schlf_s);
    freeframe(ps->readgain);
}

/*

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

Revision 1.55  2015/11/25 10:29:28  jim
Modified to fix the central RA and DEC in the primary of the stacks
to be the centre of all four detectors

Revision 1.54  2015/11/22 15:45:15  jim
superficial changes

Revision 1.53  2015/11/05 13:09:13  jim
Fixed typo

Revision 1.52  2015/11/05 12:02:26  jim
Modified to add  hawki_sci_update_catprov.

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

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

Revision 1.49  2015/10/15 11:22:27  jim
Fixed a few minor bugs

Revision 1.48  2015/10/04 18:44:07  jim
Fixed bug that occurs if there are no photometry standards in on extension

Revision 1.47  2015/09/23 19:25:53  jim
file naming revisited...

Revision 1.46  2015/09/23 19:16:49  jim
fixed problem with file numbering

Revision 1.45  2015/09/23 09:43:07  jim
Fixed a leak

Revision 1.44  2015/09/22 09:36:24  jim
Modified default rejection thresholds for stacking

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

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

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

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

Revision 1.39  2015/08/06 05:32:17  jim
A few fixes to get rid of compiler complaints

Revision 1.38  2015/08/03 13:44:48  jim
Fixed uninitialised variable issue

Revision 1.37  2015/08/03 12:47:31  jim
Fixed problem in sky_wcs that stopped a non-skysub option being used

Revision 1.36  2015/07/29 09:17:41  jim
plugged a memory leak and fixed an inconsistency with the ugly mode file naming

Revision 1.35  2015/07/02 12:30:00  jim
Fixed so that we get the correct value of FLUXCAL now

Revision 1.34  2015/06/29 10:35:59  jim
photosys comes from photcal table header now

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

Revision 1.32  2015/06/12 07:38:54  jim
Fixed a few comments

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

Revision 1.30  2015/05/13 11:57:32  jim
Offset sky variance maps now have a different product name. Fixed bug in
numbering of output catalogues.

Revision 1.29  2015/04/30 12:11:26  jim
Fixed bug where 'predictable' catalogue names are not unique

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

Revision 1.27  2015/03/10 13:27:35  jim
fixed problem with framesets trying to own the same copy of a frame

Revision 1.26  2015/03/04 10:42:45  jim
Fixed a few more memory leaks

Revision 1.25  2015/03/03 10:48:11  jim
Fixed some memory leaks

Revision 1.24  2015/03/02 10:51:34  jim
Fixed memory leak

Revision 1.23  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.22  2015/02/14 12:34:53  jim
It is now possible to write out matched standards catalogues

Revision 1.21  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.20  2014/12/12 21:44:31  jim
Fixed to remove inverse variance

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

Revision 1.18  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.17  2014/05/06 12:01:43  jim
Fixed bug in declaration of enum cdssearch

Revision 1.16  2014/05/01 09:22:09  jim
fixed little error message bug

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

Revision 1.14  2014/04/23 05:37:11  jim
Fixed a few little bugs

Revision 1.13  2014/04/09 11:08:21  jim
Get rid of a couple of compiler moans

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

Revision 1.11  2014/04/02 16:56:11  jim
fixed typo

Revision 1.10  2014/04/02 09:49:26  jim
Modified to add the readnoise and gain information

Revision 1.9  2014/03/26 16:10:30  jim
Removed calls to deprecated cpl routine. First attempt at error propagation.
Confidence maps are now floating point

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

Revision 1.7  2013/11/07 10:05:59  jim
Fixed calls to tidy routine

Revision 1.6  2013-10-24 09:32:20  jim
Many upgrades made to make sure things progress smoothly in the event that
a detector is flagged as dead.

Revision 1.5  2013-09-30 18:04:48  jim
Added simplesky_mask algorithm

Revision 1.4  2013-08-30 09:10:15  jim
fixed a few memory leaks

Revision 1.3  2013-08-29 17:30:26  jim
Altered to remove remaining globals

Revision 1.2  2013-08-28 05:46:47  jim
fixed some tabs

Revision 1.1.1.1  2013-08-27 12:07:48  jim
Imported


*/
