/* $Id: casu_imstack.c,v 1.10 2015/11/25 10:27:19 jim Exp $
 *
 * This file is part of the CASU Pipeline utilities
 * 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/25 10:27:19 $
 * $Revision: 1.10 $
 * $Name:  $
 */

/* Includes */

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

#include <math.h>
#include <cpl.h>
#include <string.h>
#include <unistd.h>
#include "casu_mods.h"
#include "casu_utils.h"
#include "casu_fits.h"
#include "casu_stats.h"
#include "casu_wcsutils.h"
#include "casu_tfits.h"

#define DATAMIN -1000.0
#define DATAMAX 65535.0

typedef struct {
    int           nx;
    int           ny;
    casu_fits     *fname;
    casu_fits     *conf;
    casu_fits     *var;
    unsigned char *bpm;
    cpl_wcs       *vwcs;
    float         xoff;
    float         yoff;
    cpl_array     *trans;
    cpl_array     *invtrans;
    float         sky;
    float         skydiff;
    float         noise;
    float         expscale;
    float         weight;
    double        ramin;
    double        ramax;
    double        decmin;
    double        decmax;
    int           confcopy;
} dstrct;

static void linecoverage(cpl_wcs *outwcs, cpl_matrix *xyin, double *lra1,
                         double *lra2, double *ldec1, double *ldec2);
static int stack_nn(int nim, dstrct *fileptrs, float lsig, float hsig, 
                    float sumweights, casu_fits *outfits, casu_fits *outconf, 
                    casu_fits *outvar, cpl_wcs *outwcs);
static int stack_lin(int nim, dstrct *fileptrs, float sumweights, 
                     casu_fits *outfits, casu_fits *outconf, casu_fits *outvar,
                     cpl_wcs *outwcs);
static void stackslow(int nimages, dstrct *fileptrs, float lsig, float hsig,
                      float sumweights, casu_fits *outfits, casu_fits *outconf,
                      casu_fits *outvar, cpl_wcs *outwcs, int dolin);
static void seeing_wt(int nim, dstrct *fileptrs);
static void output_files(int nim, dstrct *fileptrs, casu_fits **outfits, 
                         casu_fits **outconf, casu_fits **outvar, 
                         cpl_wcs **outwcs);
static void diffxy(cpl_wcs *wref, cpl_wcs *wprog, float *dx, float *dy);
static void outloc(cpl_wcs *win, cpl_matrix *in, cpl_wcs *wout, 
                   cpl_array *trans, cpl_matrix **out);
static void inloc(cpl_wcs *wout, cpl_matrix *xyout, cpl_wcs *win,
                  cpl_array *trans, cpl_matrix **xyin);
static void backgrnd_ov(int nim, dstrct *fileptrs, int unmap);
static void skyest(float *data, int *cdata, long npts, float thresh, 
                   float *skymed, float *skynoise);
static void do_averages(int ncontrib, float *data, float *vdata, float *wconf, 
                        float *conf, unsigned char *id, float lclip, 
                        float hclip, float hsig, float extra, dstrct *fileptrs,
                        float sumweights, short int *lowcl, short int *highcl, 
                        float *outval, float *outvalc, float *outvalv);
static cpl_table *mknewtab(casu_tfits *cat, dstrct *ddcur, dstrct *ddref);
static cpl_array *transinit(void);
static void fileptrs_close(int nim, dstrct *fileptrs);
static void tidy(int nim, dstrct *fileptrs, cpl_wcs *outwcs);


/**@{*/

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Stack images into a mean image using WCS info 

    \par Name:
        casu_imstack
    \par Purpose:
        Stack images into a mean image using WCS info
    \par Description:
        A list of frames is given. A mean image is stack by using the 
        given WCS to work out the shift of an input pixel relative to the
        grid defined by the first image in the list. At first all are
        stacked using a nearest neighbour algorithm. Any discordant pixels
        are marked at this stage. If a different stacking algorithm is 
        required, then the images are restacked, but with the discordant
        pixels found earlier removed. If object catalogues for each input
        image are available, then these are used to refine the coordinate
        transformation further. Variance maps can also be included.
    \par Language:
        C
    \param inf
        Input image list
    \param inconf
        Input list of confidence maps
    \param invar
        Input list of variance maps (NULL if not using)
    \param cats
        Input list of object catalogues. If NULL, then no refinement will
        be done.
    \param nimages
        The number of input images and (if given) the number of input 
        object catalogues
    \param nconfs
        The number of input confidence maps
    \param lthr
        The lower rejection threshold
    \param hthr
        The upper rejection threshold
    \param method
        The interpolation method:
        - 0: Nearest neighbour
        - 1: Bi-linear interpolation
    \param seeing
        If set, then the input frames will be weighted by the seeing
        specified in the header keyword ESO DRS SEEING.
    \param fast
        If set, then the 'fast' algorithm will be used. The fast algorithm
        uses much more memory, so if the input image list is large it is
        best to use the slow algorithm.
    \param unmap
        If set then the images can be unmapped from time to time and
        then re-read. This should only be set if the input images also
        exist on disc
    \param expkey
        The keyword in the headers that specify the exposure time
    \param out
        The output stacked image
    \param outc
        The output confidence map
    \param outv
        The output variance map
    \param status
        The usual input/output status variable
    \retval CASU_OK 
        if everything is ok
    \retval CASU_FATAL 
        if there are no images to combine or there is a mismatch between
        the confidence map sizes and image sizes.
    \par DRS headers:
        The following DRS keywords are written to the drs propertylist
        - \b PROV****
            The provenance keywords
    \author
        Jim Lewis, CASU

 */
/*---------------------------------------------------------------------------*/

extern int casu_imstack(casu_fits **inf, casu_fits **inconf, casu_fits **invar,
                        casu_tfits **cats, int nimages, int nconfs, float lthr,
                        float hthr, int method, int seeing, int fast, int unmap,
                        const char *expkey, casu_fits **out, casu_fits **outc, 
                        casu_fits **outv, int *status) {
    int i,nm;
    dstrct *dd;
    float expref,exposure,xoff,yoff,sumweights;
    casu_fits *outfits=NULL,*outconf=NULL,*outvar=NULL;
    casu_tfits *catref;
    cpl_table *newtab,*mtab;
    cpl_propertylist *phu;
    cpl_wcs *outwcs = NULL;
    dstrct *fileptrs=NULL;
    const char *fctid = "casu_imstack";

    /* Inherited status */

    *out = NULL;
    *outc = NULL;
    *outv = NULL;
    if (*status != CASU_OK)
        return(*status);

    /* Is there any point in being here? */

    if (nimages == 0) {
        cpl_msg_error(fctid,"No input files to combine");
        tidy(nimages,fileptrs,outwcs);
        FATAL_ERROR
    } else if (nimages == 1) {
        *out = casu_fits_duplicate(inf[0]);
        if (invar != NULL) 
            *outv = casu_fits_duplicate(invar[0]);
        *outc = casu_fits_duplicate(inconf[0]);
        casu_prov(casu_fits_get_ehu(*out),inf,nimages,1);
        return(*status);
    }

    /* Allocate file struct array and fill in some values */

    fileptrs = cpl_malloc(nimages*sizeof(dstrct));
    if (cpl_propertylist_has(casu_fits_get_phu(inf[0]),expkey)) {
        exposure = (float)cpl_propertylist_get_double(casu_fits_get_phu(inf[0]),
                                                      expkey);
    } else {
        exposure = 0.5;
    }
    expref = max(0.5,exposure);
    catref = NULL;
    for (i = 0; i < nimages; i++) {
        dd = fileptrs + i;
        dd->fname = inf[i];

        /* Get the confidence map and it's header scale */

        dd->confcopy = 0;
        if (nconfs == 0) {
            dd->conf = NULL;
        } else if (nconfs == 1) {
            dd->conf = inconf[0];
            dd->confcopy = 1;
        } else {
            dd->conf = inconf[i];
        }

        /* Load up the variance arrays if they exist */

        if (invar != NULL) 
            dd->var = invar[i];
        else
            dd->var = NULL;

        /* Get the image size */

        dd->nx = (int)cpl_image_get_size_x(casu_fits_get_image(dd->fname));
        dd->ny = (int)cpl_image_get_size_y(casu_fits_get_image(dd->fname));
        if (unmap)
            casu_fits_unload_im(dd->fname);

        /* Get the RA/Dec range covered by this image (only needed for
           slow algorithm) */

        if (fast == 0) {
            *status = CASU_OK;
            (void)casu_coverage(casu_fits_get_ehu(dd->fname),0,&(dd->ramin),
                                &(dd->ramax),&(dd->decmin),&(dd->decmax),
                                status);
        } else {
            dd->ramin = 0.0;
            dd->ramax = 0.0;
            dd->decmin = 0.0;
            dd->decmax = 0.0;
        }
        
        /* Get exposure time info */

        phu = casu_fits_get_phu(dd->fname);
        if (cpl_propertylist_has(phu,expkey)) {
            exposure = (float)cpl_propertylist_get_double(phu,expkey);
            exposure = max(0.5,exposure);
        } else {
            exposure = 0.5;
        }
        dd->expscale = exposure/expref;

        /* Zero the bad pixel mask */

        dd->bpm = cpl_calloc((dd->nx)*(dd->ny),sizeof(unsigned char));

        /* Read the WCS from the header */

        dd->vwcs = cpl_wcs_new_from_propertylist(casu_fits_get_ehu(inf[i]));

        /* Double check to make sure the confidence maps and images have the
           same dimensions */

        if ((int)cpl_image_get_size_x(casu_fits_get_image(dd->conf)) != dd->nx || 
            (int)cpl_image_get_size_y(casu_fits_get_image(dd->conf)) != dd->ny) {
            cpl_msg_error(fctid,"Image %s and Confidence map %s don't match",
                          casu_fits_get_fullname(dd->fname),
                          casu_fits_get_fullname(dd->conf));
            tidy(nimages,fileptrs,outwcs);
            FATAL_ERROR
        }

        /* Double check to make sure the variance maps and images have the
           same dimensions */

        if (dd->var != NULL) {
            if ((int)cpl_image_get_size_x(casu_fits_get_image(dd->var)) != dd->nx || 
                (int)cpl_image_get_size_y(casu_fits_get_image(dd->var)) != dd->ny) {
                cpl_msg_error(fctid,"Image %s and Variance map %s don't match",
                              casu_fits_get_fullname(dd->fname),
                              casu_fits_get_fullname(dd->var)); 
                tidy(nimages,fileptrs,outwcs);
                FATAL_ERROR
            }
            if (unmap)
                casu_fits_unload_im(dd->var);
        }

        /* Get a rough shift between the frames and the first one */

        diffxy(fileptrs->vwcs,dd->vwcs,&(dd->xoff),&(dd->yoff));

        /* Do we have catalogues? If so, then work out the transformation 
           corrections from them */

        if (cats != NULL) {
            if (cats[i] != NULL) 
                catref = cats[i];
            if (i == 0 || cats[i] == NULL) {
                dd->trans = transinit();
                dd->invtrans = transinit();
            } else {
                newtab = mknewtab(cats[i],dd,fileptrs);           
                (void)casu_matchxy(newtab,casu_tfits_get_table(catref),
                                   1024,&xoff,&yoff,&nm,&mtab,status);
                if (*status != CASU_OK) {
                    dd->trans = transinit();
                    *status = CASU_OK;
                } else { 
                    (void)casu_platexy(mtab,6,&(dd->trans),status);
                    freetable(mtab);
                    (void)casu_matchxy(casu_tfits_get_table(catref),newtab,
                                       1024,&xoff,&yoff,&nm,&mtab,status);
                    (void)casu_platexy(mtab,6,&(dd->invtrans),status);
                }
                freetable(mtab);
                freetable(newtab);
            }
        } else {
            dd->trans = NULL;
            dd->invtrans = NULL;
        }
    }

    /* Get the background levels in the overlap regions */

    backgrnd_ov(nimages,fileptrs,unmap);

    /* Do the seeing weighting if you want to */

    if (seeing) 
        seeing_wt(nimages,fileptrs);

    /* Get a sum of weights */

    sumweights = 0.0;
    for (i = 0; i < nimages; i++)
        sumweights += (fileptrs+i)->weight;

    /* Create the output file */

    output_files(nimages,fileptrs,&outfits,&outconf,&outvar,&outwcs);

    /* Do the stacking for the fast routines */

    if (fast == 1) {

        /* Our first job is to do a nearest neighbour stack in order
           to find the objects that will be rejected. If we only want
           the NN algorithm, then we're done. Otherwise we can use the
           rejection information to restack using any other algorithm */

        stack_nn(nimages,fileptrs,lthr,hthr,sumweights,outfits,outconf,
                 outvar,outwcs);

        /* Switch for final stacking method */

        switch (method) {
        case 0:
            break;
        case 1:
            stack_lin(nimages,fileptrs,sumweights,outfits,outconf,outvar,outwcs);
            break;
        default:
            break;
        }

    /* Otherwise use the slow routine */

    } else {
        stackslow(nimages,fileptrs,lthr,hthr,sumweights,outfits,outconf,
                  outvar,outwcs,method);
    }

    /* Add the provenance cards to the header */

    casu_prov(casu_fits_get_ehu(outfits),inf,nimages,1);

    /* Assign some output info, tidy and get out of here */

    *out = outfits;
    *outc = outconf;
    *outv = outvar;
    tidy(nimages,fileptrs,outwcs);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        stackslow
    \par Purpose:
        Do slow algorithm stacking
    \par Description:
        Do both the nearest neighbour and linear stacking using the slow
        algorithm. This combines the algorithms used in stack_nn and stack_lin
        but does an output row at a time. There are a lot more calculations
        to be done by using this approach, but it uses a great deal less
        memory the the fast algorithms.
    \par Language:
        C
    \param nimages
        The number of images
    \param fileptrs
        The input image list structure
    \param lsig
        The lower rejection threshold
    \param hsig
        The upper rejection threshold
    \param sumweights
        The total sum of all the image weights
    \param outfits
        The output image FITS structure
    \param outconf
        The output confidence map FITS structure
    \param outvar
        The output variance map FITS structure
    \param outwcs
        The output image WCS structure
    \param dolin
        If set, then the linear interpolation will also be done.
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void stackslow(int nimages, dstrct *fileptrs, float lsig, float hsig,
                      float sumweights, casu_fits *outfits, casu_fits *outconf, 
                      casu_fits *outvar, cpl_wcs *outwcs, int dolin) {
    float scale_avvar,*outdata,*outdatav,lclip,hclip,*data;
    float *vdata = NULL; /* return value for cpl_image_get_data_float on error */
    float *workbuf,*workbufv,*workbufw,*workbufc,*dbuf,*wbuf,*cbuf;
    float outval,outvalc,outvalv,avlev,avvar,dx,dy,value,cval,*confwork;
    float med,wt = 0.,w[4],renorm,*vbuf,vval,*workdatav,*workdatav2,*workdatav3;
    double yout,xin,yin,oxf,oyf,lra1,lra2,ldec1,ldec2;
    int *nbuf,*iloc,jout,bufloc_row,bufloc_before,iim,i,j,*outdatac;
    int gotit,iout,ixin,iyin,ind,ind1,ox,oy,bufloc,ncontrib,*lbuf,nin;
    int xmin,xmax,ymin,ymax,nz,nx,*bloc,*bbuf,m,nn,ii,jj,*cdata;
    short int clipped_low,clipped_high;
    long nxo,nyo,npts;
    cpl_image *im,*cim,*vim;
    unsigned char *clipmap,*id,*ibuf,**bpms,*bbpm;
    dstrct *dd;
    cpl_matrix *xyin,*xyin2,*xyout;

    outdatav = NULL; /* Remove falsepositive compiler warning */
    workdatav = NULL; /* Remove falsepositive compiler warning from freespace macro*/
    workdatav2 = NULL; /* Remove falsepositive compiler warning from freespace macro*/
    workdatav3 = NULL; /* Remove falsepositive compiler warning from freespace macro*/

    /* Useful constant */

    scale_avvar = sqrt(CPL_MATH_PI/2.0)/9.0;

    /* Output file info */

    outdata = cpl_image_get_data_float(casu_fits_get_image(outfits));
    if (outvar != NULL) 
        outdatav = cpl_image_get_data_float(casu_fits_get_image(outvar));
    outdatac = cpl_image_get_data_int(casu_fits_get_image(outconf));
    nxo = cpl_image_get_size_x(casu_fits_get_image(outfits));
    nyo = cpl_image_get_size_y(casu_fits_get_image(outfits));

    /* Rejection thresholds */

    lclip = fileptrs->sky - lsig*(fileptrs->noise);
    hclip = fileptrs->sky + hsig*(fileptrs->noise);

    /* Get some workspace and initialise it */

    nz = nimages + 2;
    workbuf = cpl_malloc(2*nxo*nz*sizeof(float));
    if (outvar != NULL)
        workbufv = cpl_malloc(2*nxo*nz*sizeof(float));
    else
        workbufv = NULL;
    workbufw = cpl_malloc(2*nxo*nz*sizeof(float));
    workbufc = cpl_malloc(2*nxo*nz*sizeof(float));
    confwork = cpl_calloc(nxo*nyo,sizeof(float));
    id = cpl_malloc(2*nxo*nz*sizeof(unsigned char));
    nbuf = cpl_malloc(2*nxo*sizeof(int));
    iloc = cpl_malloc(2*nxo*nz*sizeof(int));
    bloc = cpl_malloc(2*nxo*nz*sizeof(int));
    clipmap = cpl_calloc(2*nxo*nz,sizeof(unsigned char));
    bpms = cpl_calloc(nimages,sizeof(unsigned char *));
    dbuf = cpl_malloc(2*nz*sizeof(float));
    wbuf = cpl_malloc(2*nz*sizeof(float));
    if (outvar != NULL) 
        vbuf = cpl_malloc(2*nz*sizeof(float));
    else
        vbuf = NULL;
    cbuf = cpl_malloc(2*nz*sizeof(float));
    ibuf = cpl_malloc(2*nz*sizeof(unsigned char));
    lbuf = cpl_malloc(2*nz*sizeof(int));
    bbuf = cpl_malloc(2*nz*sizeof(int));

    /* Get a bad pixel mask for each input file */

    for (i = 0; i < nimages; i++) 
        bpms[i] = (fileptrs+i)->bpm;

    /* Loop for each output row */

    for (jout = 0; jout < nyo; jout++) {
        bufloc_row = (jout % 2 ? 0 : 1);
        bufloc_before = (! bufloc_row);
        yout = (double)(jout+1);
        
        /* Zero the counter for this row */

        (void)memset(nbuf+bufloc_row*nxo,0,nxo*sizeof(int));

        /* Create an input matrix of positions and work out the
           coverage of the output line */

        nin = 2*nxo;
        xyin = cpl_matrix_new((cpl_size)nin,2);
        nin = 0;
        for (i = -1; i <= 1; i+= 2) {
            for (iout = 0; iout < nxo; iout++) {
                cpl_matrix_set(xyin,nin,0,(double)(iout+1));
                cpl_matrix_set(xyin,nin,1,yout+0.5*(double)i);
                nin++;
            }
        }
        linecoverage(outwcs,xyin,&lra1,&lra2,&ldec1,&ldec2);

        /* Loop for each image and find the range of input pixels for
           this image that will affect this output row */

        for (iim = 0; iim < nimages; iim++) {

            /* Initialise a few things */

            dd = fileptrs + iim;
            wt = dd->weight;
            gotit = 0;

            /* If this line has nothing in common with the current
               image, then move to the next image */

            if (ldec2 < dd->decmin || ldec1 > dd->decmax ||
                lra2 < dd->ramin || lra1 > dd->ramax)
                continue;

            /* Get the position matrix */

            inloc(outwcs,xyin,dd->vwcs,dd->trans,&xyout);

            /* Get the xy limits now */

            xmin = 1000000000;
            xmax = -1000000000;
            ymin = 1000000000;
            ymax = -1000000000;
            nin = 0;
            for (i = -1; i <= 1; i += 2) {
                for (iout = 0; iout < nxo; iout++) {
                    xin = cpl_matrix_get(xyout,nin,0);
                    yin = cpl_matrix_get(xyout,nin,1);
                    nin++;
                    ixin = casu_nint(xin);
                    iyin = casu_nint(yin);
                    if (ixin < 1 || iyin < 1 || ixin > dd->nx ||
                        iyin > dd->ny)
                        continue;
                    xmin = min(xmin,ixin);
                    xmax = max(xmax,ixin);
                    ymin = min(ymin,iyin);
                    ymax = max(ymax,iyin);
                    gotit = 1;
                }
            }
            cpl_matrix_delete(xyout);
            
            /* If this makes a contribution, then get the data subset */
            
            if (! gotit) 
                continue;
            im = cpl_image_load_window(casu_fits_get_filename(dd->fname),
                                       CPL_TYPE_FLOAT,0,
                                       (cpl_size)casu_fits_get_nexten(dd->fname),
                                       (cpl_size)xmin,(cpl_size)ymin,
                                       (cpl_size)xmax,(cpl_size)ymax);
            nx = cpl_image_get_size_x(im);
            data = cpl_image_get_data_float(im);
            cim = cpl_image_load_window(casu_fits_get_filename(dd->conf),
                                        CPL_TYPE_INT,0,
                                        (cpl_size)casu_fits_get_nexten(dd->fname),
                                        (cpl_size)xmin,(cpl_size)ymin,
                                        (cpl_size)xmax,(cpl_size)ymax);
            cdata = cpl_image_get_data_int(cim);
            if (outvar != NULL) {
                vim = cpl_image_load_window(casu_fits_get_filename(dd->var),
                                            CPL_TYPE_FLOAT,0,
                                            (cpl_size)casu_fits_get_nexten(dd->fname),
                                            (cpl_size)xmin,(cpl_size)ymin,
                                            (cpl_size)xmax,(cpl_size)ymax);
                vdata = cpl_image_get_data_float(vim);
            }

            /* Get the position matrix */

            nin = (ymax - ymin + 1)*(xmax - xmin + 1);
            xyin2 = cpl_matrix_new((cpl_size)nin,2);
            nin = 0;
            for (j = ymin; j <= ymax; j++) {
                for (i = xmin; i <= xmax; i++) {
                    cpl_matrix_set(xyin2,nin,0,(double)i);
                    cpl_matrix_set(xyin2,nin,1,(double)j);
                    nin++;
                }
            }
            outloc(dd->vwcs,xyin2,outwcs,dd->trans,&xyout);
            cpl_matrix_delete(xyin2);

            /* Ok, distribute the data into the buffer */

            nin = 0;
            for (j = ymin; j <= ymax; j++) {
                ind = (j - ymin)*nx;
                for (i = xmin; i <= xmax; i++) {
                    ind1 = ind + i - xmin;
                    oxf = cpl_matrix_get(xyout,nin,0);
                    oyf = cpl_matrix_get(xyout,nin,1);
                    nin++;
                    ox = casu_nint(oxf) - 1;
                    oy = casu_nint(oyf) - 1;
                    if (ox < 0 || ox >= nxo || oy < 0 || oy >= nyo || 
                        cdata[ind1] == 0 || oy != jout) 
                        continue;
                    bufloc = (bufloc_row*nxo + ox)*nz + 
                        nbuf[ox+bufloc_row*nxo];
                    value = data[ind1]/(dd->expscale) + dd->skydiff;
                    workbuf[bufloc] = value;
                    if (outvar != NULL) 
                        workbufv[bufloc] = vdata[ind1]/powf(dd->expscale,2.0);
                    workbufw[bufloc] = wt;
                    workbufc[bufloc] = (float)cdata[ind1];
                    id[bufloc] = iim;
                    nbuf[ox+bufloc_row*nxo] += 1;
                    iloc[bufloc] = ind1;
                    bloc[bufloc] = (j-1)*(dd->nx) +  i - 1;
                    bpms[iim][bloc[bufloc]] = 0;
                }
            }
            cpl_matrix_delete(xyout);

            /* Do some intermediate tidying */

            freeimage(im);
            freeimage(cim);
            if (outvar != NULL)
                freeimage(vim);
        } /* End of image loop */
        cpl_matrix_delete(xyin);

        /* Loop through the work buffer and find an initial estimate
           for the current output row */

        for (iout = 0; iout < nxo; iout++) {
            clipmap[iout+bufloc_row*nxo] = 0;
            bufloc = (bufloc_row*nxo + iout)*nz;
            ncontrib = nbuf[iout+bufloc_row*nxo];
            if (ncontrib == 0) {
                outdata[jout*nxo + iout] = fileptrs->sky;
                outdatac[jout*nxo + iout] = 0;
                continue;
            } 
            
            /* Put some stuff in the buffers for the averaging routine */

            for (i = 0; i < ncontrib; i++) {
                dbuf[i] = workbuf[bufloc+i];
                wbuf[i] = workbufw[bufloc+i];
                if (outvar != NULL) 
                    vbuf[i] = workbufv[bufloc+i];
                cbuf[i] = workbufc[bufloc+i];
                ibuf[i] = id[bufloc+i];
                lbuf[i] = iloc[bufloc+i];
                bbuf[i] = bloc[bufloc+i];
            }

            /* Do the averages with nominal clipping */

            do_averages(ncontrib,dbuf,vbuf,wbuf,cbuf,ibuf,lclip,hclip,hsig,0.0,
                        fileptrs,sumweights,&clipped_low,&clipped_high,&outval,
                        &outvalc,&outvalv);

            /* Store these away */

            outdata[jout*nxo + iout] = outval;
            confwork[jout*nxo + iout] = outvalc;
            if (outvar != NULL) 
                outdatav[jout*nxo + iout] = outvalv;
            clipmap[bufloc_row*nxo + iout] = (clipped_high >= 0);
            if (clipped_low >= 0) 
                bpms[ibuf[clipped_low]][bbuf[clipped_low]] = 2;
            if (clipped_high >= 0) 
                bpms[ibuf[clipped_high]][bbuf[clipped_high]] = 1;
        }

        /* Look at the non-edge pixels and see if the local variation can 
           show whether any clipping that has been done is justified. NB:
           we are looking at the _previous_ row here */

        if (jout >= 2) {
            for (iout = 1; iout < nxo-1; iout++) {
                if (! clipmap[bufloc_before*nxo + iout])
                    continue;
                ncontrib = nbuf[bufloc_before*nxo+iout];
                if (ncontrib == 0) 
                    continue;
                ind = (jout-1)*nxo + iout;
                bufloc = (bufloc_before*nxo + iout)*nz;
                avlev = 0.0;
                for (j = -1; j <= 1; j++) 
                    for (i = -1; i <= 1; i++) 
                        avlev += outdata[ind + j*nxo + i];
                avlev /= 9.0;
                avvar = 0.0;
                for (j = -1; j <= 1; j++)
                    for (i = -1; i <= 1; i++) 
                        avvar += (float)fabs(avlev - outdata[ind + j*nxo + i]);
                avvar *= scale_avvar;
                if (avlev <= hclip && avvar <= 2.0*(fileptrs->noise))
                    continue;

                /* Put some stuff in the buffers for the averaging routine */

                for (i = 0; i < ncontrib; i++) {
                    dbuf[i] = workbuf[bufloc+i];
                    if (outvar != NULL) 
                        vbuf[i] = workbufv[bufloc+i];
                    wbuf[i] = workbufw[bufloc+i];
                    cbuf[i] = workbufc[bufloc+i];
                    ibuf[i] = id[bufloc+i];
                    lbuf[i] = iloc[bufloc+i];
                    bbuf[i] = bloc[bufloc+i];
                    bpms[ibuf[i]][bbuf[i]] = 0;
                }

                /* Redo the averages with the nominal clipping */
                
                do_averages(ncontrib,dbuf,vbuf,wbuf,cbuf,ibuf,lclip,hclip,hsig,
                            avvar,fileptrs,sumweights,&clipped_low,
                            &clipped_high,&outval,&outvalc,&outvalv);

                /* Store these away */

                outdata[ind] = outval;
                if (outvar != NULL) 
                    outdatav[ind] = outvalv;
                confwork[ind] = outvalc;
                if (clipped_low >= 0) 
                    bpms[ibuf[clipped_low]][bbuf[clipped_low]] = 2;
                if (clipped_high >= 0) 
                    bpms[ibuf[clipped_high]][bbuf[clipped_high]] = 1;
            }
        }
    }

    /* Do we want to do the bi-linear interpolation now? If so,
       the first thing to do is to throw away the NN result but
       keep the information about which input pixels were rejected */

    if (dolin) {
        if (outvar != NULL) {
            workdatav = cpl_malloc(nxo*nyo*sizeof(float));
            workdatav2 = cpl_malloc(nxo*nyo*sizeof(float));
            workdatav3 = cpl_calloc(nxo*nyo,sizeof(float));
        }
        memset(outdata,0,nxo*nyo*sizeof(float));
        memset(confwork,0,nxo*nyo*sizeof(float));
        if (outvar != NULL) 
            memset(outdatav,0,nxo*nyo*sizeof(float));

        /* Get the data for the current input image */

        npts = nxo*nyo;
        for (iim = 0; iim < nimages; iim++) {
            if (outvar != NULL) {
                memset(workdatav,0,nxo*nyo*sizeof(float));
                memset(workdatav2,0,nxo*nyo*sizeof(float));
            }
            dd = fileptrs + iim;
            im = cpl_image_load(casu_fits_get_filename(dd->fname),
                                CPL_TYPE_FLOAT,0,
                                (cpl_size)casu_fits_get_nexten(dd->fname));
            data = cpl_image_get_data_float(im);
            cim = cpl_image_load(casu_fits_get_filename(dd->conf),
                                 CPL_TYPE_INT,0,
                                 (cpl_size)casu_fits_get_nexten(dd->fname));
            cdata = cpl_image_get_data_int(cim);
            if (outvar != NULL) {
                vim = cpl_image_load(casu_fits_get_filename(dd->var),
                                     CPL_TYPE_FLOAT,0,
                                     (cpl_size)casu_fits_get_nexten(dd->fname));
                vdata = cpl_image_get_data_float(vim);
            }

            /* Get position matrix */

            nin = (dd->ny)*(dd->nx);
            xyin = cpl_matrix_new((cpl_size)nin,2);
            nin = 0;
            for (j = 1; j <= dd->ny; j++) {
                for (i = 1; i <= dd->nx; i++) {
                    cpl_matrix_set(xyin,nin,0,(double)i);
                    cpl_matrix_set(xyin,nin,1,(double)j);
                    nin++;
                }
            }
            outloc(dd->vwcs,xyin,outwcs,dd->trans,&xyout);
            cpl_matrix_delete(xyin);            
                
            /* Now distribute the data */

            nin = -1;
            for (j = 1; j <= dd->ny; j++) {
                ind = (j - 1)*(dd->nx);
                for (i = 1; i <= dd->nx; i++) {
                    nin++;
                    ind1 = ind + i - 1;
                    if (bpms[iim][ind1] != 0 || cdata[ind1] == 0)
                        continue;
                    oxf = cpl_matrix_get(xyout,nin,0);
                    oyf = cpl_matrix_get(xyout,nin,1);
                    if (oxf < 0.0 || oyf < 0.0)
                        continue;
                    ox = (int)oxf - 1;
                    oy = (int)oyf - 1;                  
                    if (ox < 0 || ox >= nxo || oy < 0 || oy >= nyo)
                        continue;       
                    dx = oxf - (float)(ox + 1);
                    dy = oyf - (float)(oy + 1);
                    w[0] = (1.0-dx)*(1.0-dy);
                    w[1] = dx*(1.0-dy);
                    w[2] = dy*(1.0-dx);
                    w[3] = dx*dy;
                    m = 0;
                    value = data[ind1]/(dd->expscale) + dd->skydiff;
                    if (outvar != NULL) 
                        vval = vdata[ind1]/powf(dd->expscale,2.0);
                    cval = (float)cdata[ind1];
                    for (jj = 0; jj <= 1; jj++) {
                        if (oy+jj >= nyo)
                            continue;
                        for (ii = 0; ii <= 1; ii++) {
                            nn = (oy+jj)*nxo + ox + ii;
                            if (nn >= npts || ox+ii >= nxo)
                                continue;
                            outdata[nn] += w[m]*value*wt*cval;
                            confwork[nn] += w[m]*wt*cval;
                            if (outvar != NULL) {
                                workdatav[nn] += vval*w[m]*cval;
                                workdatav2[nn] += w[m]*cval;
                            }
                            m++;
                        }
                    }
                }
            }
            cpl_matrix_delete(xyout);
            freeimage(im);
            freeimage(cim);
            if (outvar != NULL) {
                freeimage(vim);
                for (ii = 0; ii < npts; ii++) {
                    if (workdatav2[ii] != 0.0) {
                        outdatav[ii] += wt*wt*workdatav[ii]/workdatav2[ii];
                        workdatav3[ii] += wt;
                    }
                }
            }
        }

        /* Now average the output data */

        for (jout = 0; jout < nyo; jout++) {
            for (iout = 0; iout < nxo; iout++) {
                ind = jout*nxo + iout;
                if (confwork[ind] != 0.0) {
                    outdata[ind] /= confwork[ind];
                    if (outvar != NULL) {
                        if (workdatav3[ind] != 0.0)
                            outdatav[ind] /= powf(workdatav3[ind],2.0);
                        else 
                            outdatav[ind] = 0.0;
                    }
                } else {
                    outdata[ind] = fileptrs->sky;
                    if (outvar != NULL) 
                        outdatav[ind] = 0.0;
                }
                confwork[ind] /= sumweights;
            }
        }
        if (outvar != NULL) {
            freespace(workdatav);
            freespace(workdatav2);
            freespace(workdatav3);
        }
    }

    /* Normalise the confidence */
    
    npts = nxo*nyo;
    bbpm = cpl_calloc(npts,sizeof(*bbpm));
    for (i = 0; i < npts; i++)
        bbpm[i] = (confwork[i] == 0.0);
    med = casu_med(confwork,bbpm,npts);
    freespace(bbpm);
    renorm = 100.0/med;
    for (i = 0; i < npts; i++) {
        confwork[i] *= renorm;
        outdatac[i] = max(0,min(1000,(int)(confwork[i]+0.5)));
    }

    /* Tidy up */

    freespace(bpms);
    freespace(workbuf);
    freespace(workbufw);
    freespace(workbufv);
    freespace(workbufc);
    freespace(confwork);
    freespace(id);
    freespace(nbuf);
    freespace(iloc);
    freespace(bloc);
    freespace(clipmap);
    freespace(dbuf);
    freespace(vbuf);
    freespace(wbuf);
    freespace(cbuf);
    freespace(ibuf);
    freespace(lbuf);
    freespace(bbuf);
}    

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        linecoverage
    \par Purpose:
        Work out the RA/Dec coverage of a line of data
    \par Description:
        X,Y positions of given in a matrix. The RA/Dec coverage (in degrees)
        for those positions is calculated and returned. Deals with 
        wraparound at the equinox by 
    \par Language:
        C
    \param outwcs
        The WCS used to calculate the X/Y -> RA/Dec transformation
    \param xyin
        The input XY position matrix
    \param lra1
        The lower RA limit
    \param lra2
        The upper RA limit
    \param ldec1
        The lower Dec limit
    \param ldec2
        The upper Dec limit
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void linecoverage(cpl_wcs *outwcs, cpl_matrix *xyin, double *lra1,
                         double *lra2, double *ldec1, double *ldec2) {
    int i,first_quad,fourth_quad,nr;
    cpl_matrix *radec;
    cpl_array *status;
    double cur_ra,cur_dec,*data,max_1q,min_4q;
    
    /* Do the physical -> world conversion */

    cpl_wcs_convert(outwcs,xyin,&radec,&status,CPL_WCS_PHYS2WORLD);
    cpl_array_delete(status);

    /* Work out the min and max of the RA and Dec */

    *lra1 = 370.0;
    *lra2 = -370.0;
    *ldec1 = 95;
    *ldec2 = -95.0;
    first_quad = 0;
    fourth_quad = 0;
    data = cpl_matrix_get_data(radec);
    nr = cpl_matrix_get_nrow(radec);
    min_4q = -91.0;
    max_1q = data[0];  /* Added  as max_1q needs to be initialized */
    for (i = 0; i < nr; i++) {
        cur_ra = data[2*i];
        cur_dec = data[2*i+1];
        if (cur_ra >= 0.0 && cur_ra <= 90.0) {
            first_quad = 1;
            max_1q = max(cur_ra,max_1q);
        } else if (cur_ra >= 270.0 && cur_ra < 360.0) {
            fourth_quad = 1;
            min_4q = min((cur_ra-360.0),min_4q);
        }
        *lra1 = min(*lra1,cur_ra);
        *lra2 = max(*lra2,cur_ra);
        *ldec1 = min(*ldec1,cur_dec);
        *ldec2 = max(*ldec2,cur_dec);
    }
    cpl_matrix_delete(radec);

    /* Now look to see if you had RA values in both the first and
       fourth quadrants. If you have, then make the minimum RA a negative
       value. This will be the signal to the caller that you have a
       wraparound... */

    if (first_quad && fourth_quad) {
        *lra1 = min_4q;
        *lra2 = max_1q;
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        stack_nn
    \par Purpose:
        Do a stack with nearest neighbour interpolation
    \par Description:
        Do a stack with nearest neighbour interpolation. Rejection is done 
        here. Any pixels rejected will also be flagged in the input image
        masks so that this information can be used by other stacking
        routines.
    \par Language:
        C
    \param nim
        The number of images
    \param fileptrs
        The input image list structure
    \param lsig
        The lower rejection threshold
    \param hsig
        The upper rejection threshold
    \param sumweights
        The total sum of all the weights
    \param outfits
        The output image FITS structure
    \param outconf
        The output confidence map FITS structure
    \param outvar
        The output variance map FITS structure
    \param outwcs
        The output image WCS structure
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int stack_nn(int nim, dstrct *fileptrs, float lsig, float hsig, 
                    float sumweights, casu_fits *outfits, casu_fits *outconf, 
                    casu_fits *outvar, cpl_wcs *outwcs) {
    float *workbuf,*workbufc,*workbufw,lclip,hclip,*data,wt,med,outvalc;
    float value,*dbuf,*wbuf,*cbuf,outval,avlev,avvar,*confwork,*outdata;
    float scale_avvar,renorm,*vbuf,*outdatav,*workbufv,*vdata=NULL,outvalv;
    double oxf,oyf;
    long *iloc,i,npts,ix,iy,ind1,ind,ox,oy,nn,bufloc,ncontrib,*lbuf;
    long j,nx,ny,nz,nxo,nyo,nall;
    int *outdatac,*cdata,nin;
    short int clipped_low,clipped_high;
    dstrct *dd;
    unsigned char *id,*clipmap,*cc,*cclast,*ibuf,*bbpm;
    short int *nbuf;
    cpl_image *outim,*outimc,*outimv;
    cpl_matrix *xyin,*xyout;

    outdatav = NULL; /* Remove falsepositive compiler warning */

    /* Useful constant */

    scale_avvar = sqrt(CPL_MATH_PI/2.0)/9.0;

    /* Output file info */

    outim = casu_fits_get_image(outfits);
    outimc = casu_fits_get_image(outconf);
    if (outvar != NULL)
        outimv = casu_fits_get_image(outvar);
    nxo = (int)cpl_image_get_size_x(outim);
    nyo = (int)cpl_image_get_size_y(outim);

    /* Get workspace for collecting info over the whole output map */

    nz = nim + 2;
    nall = (long)nz*(long)nxo*(long)nyo;
    workbuf = cpl_calloc(nall,sizeof(*workbuf));
    if (outvar != NULL)
        workbufv = cpl_calloc(nall,sizeof(*workbufv));
    else 
        workbufv = NULL;
    workbufc = cpl_calloc(nall,sizeof(*workbufc));
    workbufw = cpl_calloc(nall,sizeof(*workbufw));
    id = cpl_calloc(nall,sizeof(*id));
    iloc = cpl_calloc(nall,sizeof(*iloc));
    nbuf = cpl_calloc(nxo*nyo,sizeof(*nbuf));
    confwork = cpl_calloc(nxo*nyo,sizeof(*confwork));

    /* Workspace for marking output rows with clipped pixels */

    clipmap = cpl_calloc(2*nxo,sizeof(*clipmap));

    /* Buffers for averaging an individual pixel */

    dbuf = cpl_calloc(2*nz,sizeof(*dbuf));
    if (outvar != NULL)
        vbuf = cpl_calloc(2*nz,sizeof(*vbuf));
    else
        vbuf = NULL;
    wbuf = cpl_calloc(2*nz,sizeof(*wbuf));
    cbuf = cpl_calloc(2*nz,sizeof(*cbuf));
    ibuf = cpl_calloc(2*nz,sizeof(*ibuf));
    lbuf = cpl_calloc(2*nz,sizeof(*lbuf));

    /* Get output buffers */

    outdata = cpl_image_get_data_float(outim);
    outdatac = cpl_image_get_data_int(outimc);
    if (outvar != NULL)
        outdatav = cpl_image_get_data_float(outimv);

    /* Rejection thresholds */

    lclip = fileptrs->sky - lsig*(fileptrs->noise);
    hclip = fileptrs->sky + hsig*(fileptrs->noise);

    /* Ok, loop for each of the input files and collect all the information
       for each pixel */

    for (i = 0; i < nim; i++) {
        dd = fileptrs + i;
        nx = dd->nx;
        ny = dd->ny;
        npts = nx*ny;
        data = cpl_image_get_data_float(casu_fits_get_image(dd->fname));
        cdata = cpl_image_get_data_int(casu_fits_get_image(dd->conf));
        if (outvar != NULL)
            vdata = cpl_image_get_data_float(casu_fits_get_image(dd->var));
        wt = dd->weight;

        /* Get the position matrix */

        nin = nx*ny;
        xyin = cpl_matrix_new((cpl_size)nin,2);
        nin = 0;
        for (iy = 1; iy <= ny; iy++) {
            for (ix = 1; ix <= nx; ix++) {
                cpl_matrix_set(xyin,nin,0,(double)ix);
                cpl_matrix_set(xyin,nin,1,(double)iy);
                nin++;
            }
        }
        outloc(dd->vwcs,xyin,outwcs,dd->trans,&xyout);
        cpl_matrix_delete(xyin);            

        /* Loop for each pixel in the data array and work out where it belongs
           in the output array */
        
        nin = 0;
        for (iy = 0; iy < ny; iy++) {
            ind1 = iy*nx;
            for (ix = 0; ix < nx; ix++) {
                ind = ind1 + ix;
                oxf = cpl_matrix_get(xyout,nin,0);
                oyf = cpl_matrix_get(xyout,nin,1);
                nin++;
                ox = casu_nint(oxf) - 1;
                oy = casu_nint(oyf) - 1;
                if (ox < 0 || ox >= nxo || oy < 0 || oy >= nyo || 
                    cdata[ind] == 0)
                    continue;
                nn = oy*nxo + ox;
                bufloc = oy*nxo*nz + ox*nz + nbuf[nn];
                value = data[ind]/(dd->expscale) + dd->skydiff;
                workbuf[bufloc] = value;
                if (outvar != NULL && vdata != NULL)
                    workbufv[bufloc] = vdata[ind]/((dd->expscale)*(dd->expscale));
                workbufw[bufloc] = wt;
                workbufc[bufloc] = (float)cdata[ind];
                id[bufloc] = i;
                nbuf[nn] += 1;
                iloc[bufloc] = ind;
            }
        }
        cpl_matrix_delete(xyout);
    }

    /* Now loop through the output arrays and form an initial estimate */

    for (iy = 0; iy < nyo; iy++) {
        ind1 = iy*nxo;
        cc = clipmap + (iy % 2)*nxo;
        cclast = clipmap + ((iy-1) % 2)*nxo;
        for (ix = 0; ix < nxo; ix++) {
            ind = ind1 + ix;
            bufloc = iy*nxo*nz + ix*nz;
            ncontrib = nbuf[ind];
            if (ncontrib == 0) {
                outdata[ind] = fileptrs->sky;
                outdatac[ind] = 0;
                if (outvar != NULL)
                    outdatav[ind] = 0.0;
                continue;
            } 
            cc[ix] = 0;

            /* Put some stuff in buffers for the averaging routine */
            
            for (i = 0; i < ncontrib; i++) {
                dbuf[i] = workbuf[bufloc+i];
                wbuf[i] = workbufw[bufloc+i];
                cbuf[i] = workbufc[bufloc+i];
                if (outvar != NULL)
                    vbuf[i] = workbufv[bufloc+i];
                ibuf[i] = id[bufloc+i];
                lbuf[i] = iloc[bufloc+i];
            }

            /* Do the averages with the nominal clipping */

            do_averages(ncontrib,dbuf,vbuf,wbuf,cbuf,ibuf,lclip,hclip,hsig,0.0,
                        fileptrs,sumweights,&clipped_low,&clipped_high,&outval,
                        &outvalc,&outvalv);

            /* Store these away */

            outdata[ind] = outval;
            confwork[ind] = outvalc;
            if (outvar != NULL)
                outdatav[ind] = outvalv;
            cc[ix] = (clipped_high >= 0);
            if (clipped_low >= 0) 
                ((fileptrs+ibuf[clipped_low])->bpm)[lbuf[clipped_low]] = 2;
            if (clipped_high >= 0) 
                ((fileptrs+ibuf[clipped_high])->bpm)[lbuf[clipped_high]] = 1;
        }

        /* Look at the non-edge pixels and see if the local variation can
           show whether any clipping that has been done is justified. NB:
           we are looking at the _previous_ row here */

        if (iy < 2)
            continue;
        for (ix = 1; ix < nxo-1; ix++) {
            if (! cclast[ix])
                continue;
            ind = (iy-1)*nxo + ix;
            bufloc = (iy-1)*nxo*nz + ix*nz;
            ncontrib = nbuf[ind];
            if (ncontrib == 0)
                continue;
            avlev = 0.0;
            for (i = -1; i <= 1; i++) 
                for (j = -1; j <= 1; j++) 
                    avlev += outdata[ind + i*nxo + j];
            avlev /= 9.0;
            avvar = 0.0;
            for (i = -1; i <= 1; i++) 
                for (j = -1; j <= 1; j++) 
                    avvar += (float)fabs(avlev - outdata[ind + i*nxo + j]);
            avvar *= scale_avvar;
            if (avlev <= hclip && avvar <= 2.0*(fileptrs->noise))
                continue;

            /* Put some stuff in buffers for the averaging routine */
            
            for (i = 0; i < ncontrib; i++) {
                dbuf[i] = workbuf[bufloc+i];
                if (outvar != NULL)
                    vbuf[i] = workbufv[bufloc+i];
                wbuf[i] = workbufw[bufloc+i];
                cbuf[i] = workbufc[bufloc+i];
                ibuf[i] = id[bufloc+i];
                lbuf[i] = iloc[bufloc+i];
                (fileptrs+ibuf[i])->bpm[lbuf[i]] = 0;
            }

            /* Redo the averages with the nominal clipping */

            do_averages(ncontrib,dbuf,vbuf,wbuf,cbuf,ibuf,lclip,hclip,hsig,avvar,
                        fileptrs,sumweights,&clipped_low,&clipped_high,&outval,
                        &outvalc,&outvalv);

            /* Store these away */

            outdata[ind] = outval;
            if (outvar != NULL)
                outdatav[ind] = outvalv;
            confwork[ind] = outvalc;
            if (clipped_low >= 0) 
                ((fileptrs+ibuf[clipped_low])->bpm)[lbuf[clipped_low]] = 2;
            if (clipped_high >= 0) 
                ((fileptrs+ibuf[clipped_high])->bpm)[lbuf[clipped_high]] = 1;
        }    
    }

    /* Now renormalise the confidence map */

    npts = nxo*nyo;
    bbpm = cpl_calloc(npts,sizeof(*bbpm));
    for (i = 0; i < npts; i++) 
        bbpm[i] = (confwork[i] == 0.0);
    med = casu_med(confwork,bbpm,npts);
    freespace(bbpm);
    renorm = 100.0/med;
    for (i = 0; i < npts; i++) {
        confwork[i] *= renorm;
        outdatac[i] = max(0,min(1000,(int)(confwork[i]+0.5)));
    }

    /* Free up some workspace */

    freespace(workbuf);
    freespace(workbufc);
    freespace(workbufv);
    freespace(workbufw);
    freespace(confwork);
    freespace(id);
    freespace(iloc);
    freespace(nbuf);
    freespace(clipmap);
    freespace(dbuf);
    freespace(vbuf);
    freespace(wbuf);
    freespace(cbuf);
    freespace(ibuf);
    freespace(lbuf);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        stack_lin
    \par Purpose:
        Do a stack with bi-linear interpolation
    \par Description:
        Do a stack with bi-linear interpolation. No rejection is done 
        here as it is assumed that this has been done with the nearest
        neighbour algorithm and any bad pixels have been flagged in the
        image masks
    \par Language:
        C
    \param nim
        The number of images
    \param fileptrs
        The input image list structure
    \param sumweights
        The total sum of the input image weights
    \param outfits
        The output image FITS structure
    \param outconf
        The output confidence map FITS structure
    \param outvar
        The output variance map FITS structure
    \param outwcs
        The output image WCS structure
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int stack_lin(int nim, dstrct *fileptrs, float sumweights, 
                     casu_fits *outfits, casu_fits *outconf, casu_fits *outvar,
                     cpl_wcs *outwcs) {
    cpl_image *outim,*outimc,*outimv;
    double oxf,oyf;
    long npts,i,ix,iy,ind1,ind,ox,oy,m,jj,ii,nn,nx,ny,nxo,nyo;
    float *workbufw,*data,wt,dx,dy,w[4],value,cval,*vdata,*workdatav3;
    float med,*outdata,renorm,vval,*outdatav,*workdatav,*workdatav2;
    int *cdata,*outdatac,nin;
    dstrct *dd;
    unsigned char *bbpm;
    cpl_matrix *xyin,*xyout;

    vdata = NULL; /* return value for cpl_image_get_data_float on error */
    workdatav = NULL; /* Remove falsepositive compiler warning from freespace macro*/
    workdatav2 = NULL; /* Remove falsepositive compiler warning from freespace macro*/
    workdatav3 = NULL; /* Remove falsepositive compiler warning from freespace macro*/
    outdatav = NULL;
    /* Output file info */

    outim = casu_fits_get_image(outfits);
    outimc = casu_fits_get_image(outconf);
    if (outvar != NULL) 
        outimv = casu_fits_get_image(outvar);
    nxo = (long)cpl_image_get_size_x(outim);
    nyo = (long)cpl_image_get_size_y(outim);
    npts = nxo*nyo;

    /* Get workspace for collecting info over the whole output map */

    workbufw = cpl_calloc(nxo*nyo,sizeof(*workbufw));

    /* Get output buffers */

    outdata = cpl_image_get_data_float(outim);
    outdatac = cpl_image_get_data_int(outimc);
    if (outvar != NULL) {
        workdatav = cpl_malloc(nxo*nyo*sizeof(float));
        workdatav2 = cpl_malloc(nxo*nyo*sizeof(float));
        workdatav3 = cpl_malloc(nxo*nyo*sizeof(float));
        outdatav = cpl_image_get_data_float(outimv);
    }

    /* Initialise the output map. This is because the output data array 
       probably still has the results of the nearest neighbour stack */

    for (i = 0; i < npts; i++) {
        outdata[i] = 0.0;
        outdatac[i] = 0;
        if (outvar != NULL) {
            outdatav[i] = 0.0;
            workdatav3[i] = 0.0;
        }
    }

    /* Ok, loop for each of the input files and collect all the information
       for each pixel */

    for (i = 0; i < nim; i++) {
        if (outvar != NULL) {
            memset(workdatav,0,nxo*nyo*sizeof(float));
            memset(workdatav2,0,nxo*nyo*sizeof(float));
        }
        dd = fileptrs + i;
        data = cpl_image_get_data_float(casu_fits_get_image(dd->fname));
        cdata = cpl_image_get_data_int(casu_fits_get_image(dd->conf));
        if (outvar != NULL) 
            vdata = cpl_image_get_data_float(casu_fits_get_image(dd->var));
        wt = dd->weight;
        nx = dd->nx;
        ny = dd->ny;

        /* Get the position matrix */

        nin = nx*ny;
        xyin = cpl_matrix_new((cpl_size)nin,2);
        nin = 0;
        for (iy = 1; iy <= ny; iy++) {
            for (ix = 1; ix <= nx; ix++) {
                cpl_matrix_set(xyin,nin,0,(double)ix);
                cpl_matrix_set(xyin,nin,1,(double)iy);
                nin++;
            }
        }
        outloc(dd->vwcs,xyin,outwcs,dd->trans,&xyout);
        cpl_matrix_delete(xyin);            

        /* Loop for each pixel in the data array and work out where it belongs
           in the output array */

        nin = 0;
        for (iy = 0; iy < ny; iy++) {
            ind1 = iy*nx;
            for (ix = 0; ix < nx; ix++) {
                ind = ind1 + ix;
                oxf = cpl_matrix_get(xyout,nin,0);
                oyf = cpl_matrix_get(xyout,nin,1);
                nin++;
                if (oxf < 0.0 || oyf < 0.0)
                    continue;
                ox = (long)oxf - 1;
                oy = (long)oyf - 1;
                if ((dd->bpm)[ind] != 0)
                    continue;
                if (ox < 0 || ox >= nxo || oy < 0 || oy >= nyo ||
                    cdata[ind] == 0) 
                    continue;
                dx = oxf - (float)(ox + 1);
                dy = oyf - (float)(oy + 1);
                w[0] = (1.0-dx)*(1.0-dy);
                w[1] = dx*(1.0-dy);
                w[2] = dy*(1.0-dx);
                w[3] = dx*dy;
                m = 0;
                value = data[ind]/(dd->expscale) + dd->skydiff;
                if (outvar != NULL) 
                    vval = vdata[ind]/((dd->expscale)*(dd->expscale));
                cval = (float)cdata[ind];
                for (jj = 0; jj <= 1; jj++) {
                    for (ii = 0; ii <= 1; ii++) {
                        nn = (oy+jj)*nxo + ox + ii;
                        if (nn >= npts || ox+ii >= nxo || oy+jj >= nyo)
                            continue;
                        outdata[nn] += w[m]*value*wt*cval;
                        workbufw[nn] += w[m]*wt*cval;
                        if (outvar != NULL) {
                            workdatav[nn] += vval*w[m]*cval;
                            workdatav2[nn] += w[m]*cval;
                        }
                        m++;
                    }
                }
            }
        }
        cpl_matrix_delete(xyout);
        if (outvar != NULL) {
            for (ii = 0; ii < npts; ii++) {
                if (workdatav2[ii] != 0.0) {
                    outdatav[ii] += wt*wt*workdatav[ii]/workdatav2[ii];
                    workdatav3[ii] += wt;
                }
            }
        }
    }

    /* Now do the final averaging */

    for (i = 0; i < npts; i++) {
        if (workbufw[i] != 0.0) {
            outdata[i] /= workbufw[i];
            if (outvar != NULL) {
                if (workdatav3[i] != 0.0)
                    outdatav[i] /= powf(workdatav3[i],2.0);
                else
                    outdatav[i] = 0.0;
            }
            workbufw[i] = workbufw[i]/sumweights;
        } else {
            outdata[i] = fileptrs->sky;
            if (outvar != NULL) 
                outdatav[i] = 0.0;
        }
    }

    /* Now renormalise the confidence map */

    bbpm = cpl_calloc(npts,sizeof(*bbpm));
    for (i = 0; i < npts; i++)
        bbpm[i] = (workbufw[i] == 0.0);
    med = casu_med(workbufw,bbpm,npts);
    freespace(bbpm);
    renorm = 100.0/med;
    for (i = 0; i < npts; i++) {
        workbufw[i] *= renorm;
        outdatac[i] = max(0,min(1000,(int)(workbufw[i]+0.5)));
    }
    freespace(workbufw);
    if (outvar != NULL) {
        freespace(workdatav);
        freespace(workdatav2);
        freespace(workdatav3);
    }
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        seeing_wt
    \par Purpose:
        Create weights to take image seeing into account
    \par Description:
        The image headers are searched for a seeing estimate with 
        the keyword ESO DRS SEEING. If it exists in all the input 
        images, then the weight for an image will be modified by the
        ratio of the image seeing divided by the seeing of the first
        image. If the seeing estimate does not exist in all headers, then
        no weighting will be done and a warning will be issued.
    \par Language:
        C
    \param nim
        The number of images
    \param fileptrs
        The input file list structure
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
                        
static void seeing_wt(int nim, dstrct *fileptrs) {
    float *seeing;
    int ierr,i;
    cpl_propertylist *plist;
    dstrct *dd;
    const char *fctid = "seeing_wt";

    /* Get an array for the seeing estimates */
    
    seeing = cpl_malloc(nim*sizeof(*seeing));

    /* Read the seeing keyword. Get out of here if one is missing and
       just use the background weights that we already have */

    ierr = 0;
    for (i = 0; i < nim; i++) {
        dd = fileptrs+i;
        plist = casu_fits_get_ehu(dd->fname);
        if (cpl_propertylist_has(plist,"ESO DRS SEEING")) {
            seeing[i] = cpl_propertylist_get_float(plist,"ESO DRS SEEING");
            if (seeing[i] <= 0.0) {
                ierr = 1;
                break;
            }
        } else {
            ierr = 1;
            break;
        }
    }

    /* If there were no errors, then modify the background weights by the
       seeing ratio */

    if (! ierr) {
        for (i = 1; i < nim; i++) {
            dd = fileptrs + i;
            dd->weight *= seeing[0]/seeing[i];
        }
    } else {
        cpl_msg_warning(fctid,"No seeing weighting will be done");
    }
    freespace(seeing);
}   

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        output_files
    \par Purpose:
        Set up the output files
    \par Description:
        Work out the size of the output files given the coverage of each
        of the input files. Set up the output files
    \par Language:
        C
    \param nim
        The number of images
    \param fileptrs
        The input file list structure
    \param outfits
        The output image fits structure
    \param outconf
        The output confidence map fits structure
    \param outvar
        The output variance map fits structure
    \param
        The output WCS structure
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void output_files(int nim, dstrct *fileptrs, casu_fits **outfits, 
                         casu_fits **outconf, casu_fits **outvar, 
                         cpl_wcs **outwcs) {
    cpl_image *newim,*newconf,*newvar;
    double lowerleft[2],upperleft[2],lowerright[2],upperright[2];
    double xmin,ymin,sh[2],ra,dec;
    dstrct *dd;
    int i,ixo,iyo,isnull;
    cpl_wcs *refwcs;
    cpl_matrix *xyin,*xyout;
    cpl_propertylist *refp;

    /* Initialise the first element of the results arrays since we're 
       doing all this relative to the first image */

    lowerleft[0] = 1.0;
    lowerleft[1] = 1.0;
    upperleft[0] = 1.0;
    upperleft[1] = (double)fileptrs->ny;
    lowerright[0] = (double)fileptrs->nx;
    lowerright[1] = 1.0;
    upperright[0] = (double)fileptrs->nx;
    upperright[1] = (double)fileptrs->ny;

    /* Get a matrix to use for the coordinate transformation of the
       corner positions */

    xyin = cpl_matrix_new(4,2);

    /* Loop for all images relative to the first one */

    refwcs = NULL;
    for (i = 1; i < nim; i++) {
        if (i == 1) {
            refp = cpl_propertylist_duplicate(casu_fits_get_ehu(fileptrs->fname));
            if (! strncmp(cpl_propertylist_get_string(refp,"CTYPE1"),"RA",2)) 
                cpl_propertylist_update_string(refp,"CTYPE1","RA---TAN");
            else
                cpl_propertylist_update_string(refp,"CTYPE2","RA---TAN");
            if (! strncmp(cpl_propertylist_get_string(refp,"CTYPE1"),"DEC",3)) 
                cpl_propertylist_update_string(refp,"CTYPE1","DEC--TAN");
            else
                cpl_propertylist_update_string(refp,"CTYPE2","DEC--TAN");
            refwcs = cpl_wcs_new_from_propertylist(refp);
            cpl_propertylist_delete(refp);
        }
        dd = fileptrs + i;
        cpl_matrix_set(xyin,0,0,(double)1.0);
        cpl_matrix_set(xyin,0,1,(double)1.0);
        cpl_matrix_set(xyin,1,0,(double)1.0);
        cpl_matrix_set(xyin,1,1,(double)(dd->ny));
        cpl_matrix_set(xyin,2,0,(double)(dd->nx));
        cpl_matrix_set(xyin,2,1,(double)1.0);
        cpl_matrix_set(xyin,3,0,(double)(dd->nx));
        cpl_matrix_set(xyin,3,1,(double)(dd->ny));
        outloc(dd->vwcs,xyin,refwcs,dd->trans,&xyout);
        lowerleft[0] = min(lowerleft[0],cpl_matrix_get(xyout,0,0));
        lowerleft[1] = min(lowerleft[1],cpl_matrix_get(xyout,0,1));
        upperleft[0] = min(upperleft[0],cpl_matrix_get(xyout,1,0));
        upperleft[1] = max(upperleft[1],cpl_matrix_get(xyout,1,1));
        lowerright[0] = max(lowerright[0],cpl_matrix_get(xyout,2,0));
        lowerright[1] = min(lowerright[1],cpl_matrix_get(xyout,2,1));
        upperright[0] = max(upperright[0],cpl_matrix_get(xyout,3,0));
        upperright[1] = max(upperright[1],cpl_matrix_get(xyout,3,1));
        cpl_matrix_delete(xyout);
    }
    freewcs(refwcs);
    cpl_matrix_delete(xyin);

    /* Ok, what are the limits? */

    ixo = casu_nint(max(lowerright[0]-lowerleft[0],upperright[0]-upperleft[0])) + 1;
    iyo = casu_nint(max(upperright[1]-lowerright[1],upperleft[1]-lowerleft[1])) + 1;
    xmin = min(lowerleft[0],upperleft[0]);
    ymin = min(lowerleft[1],lowerright[1]);
    
    /* Create the image */

    newim = cpl_image_new((cpl_size)ixo,(cpl_size)iyo,CPL_TYPE_FLOAT);
    *outfits = casu_fits_wrap(newim,fileptrs->fname,NULL,NULL);
    cpl_propertylist_update_int(casu_fits_get_ehu(*outfits),"NAXIS1",ixo);
    cpl_propertylist_update_int(casu_fits_get_ehu(*outfits),"NAXIS2",iyo);

    /* Output file is always TAN projection */

    refp = casu_fits_get_ehu(*outfits);
    cpl_propertylist_update_string(refp,"CTYPE1","RA---TAN");
    cpl_propertylist_update_string(refp,"CTYPE2","DEC--TAN");
    cpl_propertylist_erase_regexp(refp,"^PV[0-9]*_[0-9]*$",0);

    /* Update the reference point for the WCS to take the jitter
       offsets into account */

    sh[0] = xmin;
    sh[1] = ymin;
    (void)casu_crpixshift(refp,1.0,sh);
    *outwcs = cpl_wcs_new_from_propertylist(casu_fits_get_ehu(*outfits));
    ra = cpl_array_get(cpl_wcs_get_crval(*outwcs),0,&isnull);
    dec = cpl_array_get(cpl_wcs_get_crval(*outwcs),1,&isnull);
    casu_radectoxy(*outwcs,ra,dec,&xmin,&ymin);
    cpl_wcs_delete(*outwcs);
    casu_propertylist_update_float(refp,"CRPIX1",(float)xmin);
    casu_propertylist_update_float(refp,"CRPIX2",(float)ymin);
    *outwcs = cpl_wcs_new_from_propertylist(casu_fits_get_ehu(*outfits));

    /* Now create the confidence map image */

    newconf = cpl_image_new((cpl_size)ixo,(cpl_size)iyo,CPL_TYPE_INT);
    *outconf = casu_fits_wrap(newconf,*outfits,NULL,NULL);

    /* Create the output variance map if required */

    if (fileptrs->var != NULL) {
        newvar = cpl_image_new((cpl_size)ixo,(cpl_size)iyo,CPL_TYPE_FLOAT);
        *outvar = casu_fits_wrap(newvar,*outfits,NULL,NULL);
    } else {
        *outvar = NULL;
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        diffxy
    \par Purpose:
        Work out the cartesian offset between two images
    \par Description:
        Work out the cartesian offset between two images by using the
        WCS information read from the input headers. The result is in the
        sense of reference - programme.
    \par Language:
        C
    \param wref
        The WCS descriptor for the reference image
    \param wprog
        The WCS descriptor for the programme image
    \param dx
        The output X offset
    \param dy
        The output Y offset
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void diffxy(cpl_wcs *wref, cpl_wcs *wprog, float *dx, float *dy) {
    cpl_matrix *xyin,*xyout;
    double xref,yref;
    const cpl_array *arr;
    int isnull;

    /* Find the middle of the reference frame */

    arr = cpl_wcs_get_image_dims(wref);
    xref = 0.5*cpl_array_get(arr,0,&isnull);
    yref = 0.5*cpl_array_get(arr,1,&isnull);
    xyin = cpl_matrix_new(1,2);
    cpl_matrix_set(xyin,0,0,xref);
    cpl_matrix_set(xyin,0,1,yref);

    /* Work out the output position */

    outloc(wref,xyin,wprog,NULL,&xyout);

    /* Now the offsets */

    *dx = (float)(xref - cpl_matrix_get(xyout,0,0));
    *dy = (float)(yref - cpl_matrix_get(xyout,0,1));
    cpl_matrix_delete(xyout);
    cpl_matrix_delete(xyin);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        outloc
    \par Purpose:
        Work the output (x,y) locations of input pixels
    \par Description:
        Given a list of  input pixel positions and input and output WCS 
        descriptors, work out the output pixel positions. If a coordinate 
        transformation refinement has been done, the apply it here.
    \par Language:
        C
    \param win
        The input WCS descriptor
    \param in
        The input pixel positions
    \param wout
        The output WCS descriptor
    \param trans
        The coordinate transformation refinement coefficients
    \param out
        The output pixel positions
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void outloc(cpl_wcs *win, cpl_matrix *in, cpl_wcs *wout, 
                   cpl_array *trans, cpl_matrix **out) {
    double *outdata,*tdata,xt,yt,xout,yout;
    cpl_size n;
    int i;
    cpl_array *status;
    cpl_matrix *radec;

    /* First do the conversion of (x1,y1) => (ra,dec) */

    cpl_wcs_convert(win,in,&radec,&status,CPL_WCS_PHYS2WORLD);
    cpl_array_delete(status);

    /* Now do the conversion of (ra,dec) => (x2,y2) */

    cpl_wcs_convert(wout,radec,out,&status,CPL_WCS_WORLD2PHYS);
    cpl_array_delete(status);
    cpl_matrix_delete(radec);

    /* If there is a plate transformation, then we need to apply that
       here */

    if (trans != NULL) {
        tdata = cpl_array_get_data_double(trans);
        n = cpl_matrix_get_nrow(*out);
        outdata = cpl_matrix_get_data(*out);
        for (i = 0; i < n; i++) {
            xt = outdata[2*i];
            yt = outdata[2*i+1];
            xout = xt*tdata[0] + yt*tdata[1] + tdata[2];
            yout = xt*tdata[3] + yt*tdata[4] + tdata[5];
            outdata[2*i] = xout;
            outdata[2*i+1] = yout;
        }
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        inloc
    \par Purpose:
        Work the input (x,y) locations of output pixels
    \par Description:
        Given a list of output pixel positions and input and output WCS 
        descriptors, work out the input pixel positions. If a coordinate 
        transformation refinement has been done, the apply it here.
    \par Language:
        C
    \param wout
        The output WCS descriptor
    \param out
        The output pixel positions
    \param win
        The input WCS descriptor
    \param trans
        The coordinate transformation refinement coefficients
    \param in
        The input pixel positions
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void inloc(cpl_wcs *wout, cpl_matrix *out, cpl_wcs *win, 
                  cpl_array *trans, cpl_matrix **in) {
    double *outdata,*tdata,xt,yt,xout,yout;
    cpl_size n;
    int i;
    cpl_matrix *radec;
    cpl_array *status;

     /* If there is a plate transformation, then we need to apply that
       here */

    if (trans != NULL) {
        tdata = cpl_array_get_data_double(trans);
        n = cpl_matrix_get_nrow(out);
        outdata = cpl_matrix_get_data(out);
        for (i = 0; i < n; i++) {
            xt = outdata[2*i];
            yt = outdata[2*i+1];
            xout = xt*tdata[0] + yt*tdata[1] + tdata[2];
            yout = xt*tdata[3] + yt*tdata[4] + tdata[5];
            outdata[2*i] = xout;
            outdata[2*i+1] = yout;
        }
    }

    /* Now do the conversion of (x2,y2) => (ra,dec) */

    cpl_wcs_convert(wout,out,&radec,&status,CPL_WCS_PHYS2WORLD);
    cpl_array_delete(status);

    /* Now do the conversion of (ra,dec) => (x2,y2) */

    cpl_wcs_convert(win,radec,in,&status,CPL_WCS_WORLD2PHYS);
    cpl_array_delete(status);
    cpl_matrix_delete(radec);

}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        backgrnd_ov
    \par Purpose:
        Work out the background correction between two images
    \par Description:
        Work out the background correction between two images by examining
        the region of common coverage between them. If this common region is
        less than half of an image, then the whole of each image to work out
        a background estimate.
    \par Language:
        C
    \param nim
        The number of images
    \param fileptrs
        The input image list structure
    \param unmap
        True if we can unmap the images
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void backgrnd_ov(int nim, dstrct *fileptrs, int unmap) {
    int i,dx,dy,jj,nx,ii,*cdata1,*cdata,*cdata2;
    long npts1,npts,start[2],end[2],ind1,ind,n;
    dstrct *dd1,*dd2;
    float *data,sky1,skynoise1,sky2,skynoise2,frac,*data1,*data2;

    /* Initialise a few things */

    dd1 = fileptrs;
    npts1 = dd1->nx*dd1->ny;

    /* Start by working out the full background of the first frame */

    data1 = cpl_image_get_data_float(casu_fits_get_image(dd1->fname));
    cdata1 = cpl_image_get_data_int(casu_fits_get_image(dd1->conf));
    skyest(data1,cdata1,npts1,3.0,&sky1,&skynoise1);
    dd1->sky = sky1;
    dd1->noise = skynoise1;
    dd1->sky /= dd1->expscale;
    dd1->skydiff = 0.0;
    dd1->weight = 1.0;

    /* Now loop for all the others */    

    for (i = 1; i < nim; i++) {
        dd2 = fileptrs + i;
        data2 = cpl_image_get_data_float(casu_fits_get_image(dd2->fname));
        cdata2 = cpl_image_get_data_int(casu_fits_get_image(dd2->conf));

        /* Offset differences */

        dx = casu_nint(dd1->xoff - dd2->xoff);
        dy = casu_nint(dd1->yoff - dd2->yoff);

        /* Ok, here's the tricky bit. Work out the range of x and y for
           each of the images. The shift moves an image to the left and
           and down. Hence positive values of dx or dy moves the second
           frame further to the left of down compared to the first frame.
           Here are the coordinate ranges for the first image */

        /* start[0] = max(0,-dx); */
        /* start[1] = max(0,-dy); */
        /* end[0] = min((dd1->nx-1),((dd1->nx)-dx-1)); */
        /* end[1] = min((dd1->ny-1),((dd1->ny)-dy-1)); */
        nx = dd1->nx;
        if (abs(dx) > dd1->nx || abs(dy) > dd1->ny) {
            frac = 0.0;
        } else {
            if (dx >= 0.0) {
                start[0] = 0;
                end[0] = max(0,dd1->nx-dx-1);
            } else {
                start[0] = abs(dx);
                end[0] = dd1->nx-1;
            }
            if (dy >= 0.0) {
                start[1] = 0;
                end[1] = max(0,dd1->ny-dy-1);
            } else {
                start[1] = abs(dy);
                end[1] = dd1->ny-1;
            }

            /* How much does this cover the first image? */

            npts = (end[0]-start[0]+1)*(end[1]-start[1]+1);
            frac = (float)npts/(float)npts1;
        }

        /* If the coverage is more than 50% then calculate the background
           in the coverage region for the first image */

        if (frac >= 0.5) {
            data = cpl_malloc(npts*sizeof(*data));
            cdata = cpl_malloc(npts*sizeof(*cdata));
            n = 0;
            for (jj = start[1]; jj <= end[1]; jj++) {
                ind1 = jj*nx;
                for (ii = start[0]; ii <= end[0]; ii++) {
                    ind = ind1 + ii;
                    data[n] = data1[ind];
                    cdata[n++] = cdata1[ind];
                }
            }
            skyest(data,cdata,n,3.0,&sky1,&skynoise1);
            freespace(data);
            freespace(cdata);
        } else {
            sky1 = dd1->sky;
        }

        /* And here are the coordinate ranges for the second image if 
           the coverage is more than 50%. Get the subset you need. */

        if (frac > 0.5) {
            start[0] = max(0,dx);
            start[1] = max(0,dy);
            end[0] = min((dd2->nx-1),((dd2->nx)+dx-1));
            end[1] = min((dd2->ny-1),((dd2->ny)+dy-1));
            nx = dd2->nx;
            npts = (end[0]-start[0]+1)*(end[1]-start[1]+1);
            data = cpl_malloc(npts*sizeof(*data));
            cdata = cpl_malloc(npts*sizeof(*cdata));
            n = 0;
            for (jj = start[1]; jj <= end[1]; jj++) {
                ind1 = jj*nx;
                for (ii = start[0]; ii <= end[0]; ii++) {
                    ind = ind1 + ii;
                    data[n] = data2[ind];
                    cdata[n++] = cdata2[ind];
                }
            }
            skyest(data,cdata,n,3.0,&sky2,&skynoise2);
            freespace(data);
            freespace(cdata);
        } else {                
            npts = (dd2->nx)*(dd2->ny);
            skyest(data2,cdata2,npts,3.0,&sky2,&skynoise2);
        }

        /* Store info away */

        dd2->sky = sky2/dd2->expscale;
        dd2->skydiff = sky1 - sky2;
        dd2->noise = skynoise2/dd2->expscale;
        if (dd2->noise != 0.0 && dd2->sky != 0.0) 
            dd2->weight = (float)(pow(dd1->noise,2.0)/pow(dd2->noise,2.0)); 
        else 
            dd2->weight = 0.0;
        if (unmap) {
            casu_fits_unload_im(dd2->fname);
            if (! dd2->confcopy)
                casu_fits_unload_im(dd2->conf);
        }
    }
    if (unmap) {
        casu_fits_unload_im(dd1->fname);
        casu_fits_unload_im(dd1->conf);
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        do_averages
    \par Purpose:
        Work out the average for a given pixel.
    \par Description:
        Work out the average for a given pixel. Keep track of which pixels
        have been clipped.
    \par Language:
        C
    \param ncontrib
        The number of contributing pixels
    \param data
        The data array of input pixels
    \param vdata
        The data array of input variances
    \param wbuf
        The data array of weights
    \param conf
        The data array of confidence
    \param id
        The data array of frame ids
    \param lclip
        The lower clipping level
    \param hclip
        The upper clipping level
    \param hsig
        The high clip threshold in sigma
    \param extra
        An extra upper clipping level
    \param fileptrs
        The input image list structure
    \param sumweights
        The sum of the input image weights
    \param lowcl
        The location in the data arrays of a low clipped pixel
    \param highcl
        The location in the data arrays of a high clipped pixel
    \param outval
        The output value for the pixel
    \param outvalc
        The output confidence value
    \param outvalv
        The output variance value
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
    
static void do_averages(int ncontrib, float *data, float *vdata, float *wbuf, 
                        float *conf, unsigned char *id, float lclip, float hclip, 
                        float hsig, float extra, dstrct *fileptrs, float sumweights,
                        short int *lowcl, short int *highcl, float *outval, 
                        float *outvalc, float *outvalv) {
    float minval,maxval,sum,wconfsum,reflev,clipval,clipscale,scl;
    float csum,vsum;
    int imax,imin,i;

    /* Do the summation and keep track of the minimum and
       maximum values */

    minval = 1.0e10;
    maxval = -1.0e10;
    sum = 0.0;
    vsum = 0.0;
    wconfsum = 0.0;
    csum = 0.0;
    imin = -1;
    imax = -1;
    for (i = 0; i < ncontrib; i++) {
        sum += data[i]*wbuf[i]*conf[i];
        wconfsum += wbuf[i]*conf[i];
        if (vdata != NULL) 
            vsum += vdata[i]*wbuf[i]*wbuf[i]*conf[i]*conf[i];
        csum += conf[i];
        if (data[i] > maxval) {
            maxval = data[i];
            imax = i;
        }
        if (data[i] < minval) {
            minval = data[i];
            imin = i;
        }
    }

    /* First crack at output value */

    if (wconfsum > 0.0) {
        *outval = sum/wconfsum;
    } else {
        *outval = fileptrs->sky;
    }

    /* Look at high clipping for of cosmic rays */

    *highcl = -1;
    if (maxval > hclip && wconfsum > 150.0 && csum > 150.0 && 
        imin != -1 && imax != -1) {
        scl = wbuf[imax]*conf[imax];
        reflev = (sum - data[imax]*scl)/(wconfsum - scl);
        clipscale = max(1.0,reflev)/max(1.0,(fileptrs+id[imax])->sky);
        clipval = reflev + clipscale*hsig*(fileptrs+id[imax])->noise;
        clipval += hsig*extra;

        /* Clip the maximum point */

        if (maxval > clipval) {
            sum -= data[imax]*scl;
            wconfsum -= scl;
            if (vdata != NULL) 
                vsum -= vdata[imax]*scl*scl;
            *outval = reflev;
            *highcl = imax;
        }
    }

    /* Clip low points (presumably serious cosmetic issues on detectors) */

    *lowcl = -1;
    if (minval < lclip && wconfsum > 150.0 && csum > 150.0 &&
        imin != -1 && imax != -1) {
        scl = wbuf[imin]*conf[imin];
        reflev = (sum - data[imin]*scl)/(wconfsum - scl);
        clipscale = max(1.0,reflev)/max(1.0,(fileptrs+id[imin])->sky);
        clipval = reflev + clipscale*hsig*(fileptrs+id[imin])->noise;
        if (minval < clipval) {
            sum -= data[imin]*scl;
            wconfsum -= scl;
            if (vdata != NULL) 
                vsum -= vdata[imin]*scl*scl;
            *outval = reflev;
            *lowcl = imin;
        }
    }

    /* Output confidence (not normalised) */

    *outvalc = wconfsum/sumweights;
    if (vdata != NULL) 
        *outvalv = vsum/powf(wconfsum,2.0);
    else 
        *outvalv = 0.0;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        skyest
    \par Purpose:
        Work out a sky estimate
    \par Description:
        Work out a sky estimate given a data array and confidence map.
        Clipping is done at a given threshold.
    \par Language:
        C
    \param data
        The input data array
    \param cdata
        The input confidence map array
    \param npts
        The number of entries in the data and confidence arrays
    \param thresh
        The clipping threshold in units of background noise
    \param skymed
        The background median
    \param skynoise
        The background noise
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void skyest(float *data, int *cdata, long npts, float thresh, 
                   float *skymed, float *skynoise) {
    unsigned char *bpm;
    long i;

    /* Get a dummy bad pixel mask */

    bpm = cpl_calloc(npts,sizeof(*bpm));
    for (i = 0; i < npts; i++)
        bpm[i] = (cdata[i] == 0);

    /* Get the stats */

    casu_qmedsig(data,bpm,npts,thresh,2,DATAMIN,DATAMAX,skymed,skynoise);

    /* Clean up */

    freespace(bpm);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        mknewtab
    \par Purpose:
        Create a new catalogue table with update x,y coordinates
    \par Description:
        Create a new catalogue table. The x,y coordinates are updated
        to a new coordinate system defined by an input wcs descriptor.
    \par Language:
        C
    \param cat
        The input catalogue of the current image
    \param ddcur
        The current file structure
    \param ddref
        The file structure for the reference frame
    \returns 
        A copy of the input catalogue, but with the x,y coordinates now 
        in the reference frame of another image
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_table *mknewtab(casu_tfits *cat, dstrct *ddcur, dstrct *ddref) {
    cpl_table *newtab;
    int nr,i;
    float *x,*y;
    cpl_matrix *xyin,*xyout;

    /* Make a copy of the input table */

    newtab = cpl_table_duplicate(casu_tfits_get_table(cat));
    nr = (int)cpl_table_get_nrow(newtab);

    /* Get the columns of interest */

    x = cpl_table_get_data_float(newtab,"X_coordinate");
    y = cpl_table_get_data_float(newtab,"Y_coordinate");
    xyin = cpl_matrix_new((cpl_size)nr,2);
    for (i = 0; i < nr; i++) {
        cpl_matrix_set(xyin,i,0,x[i]);
        cpl_matrix_set(xyin,i,1,y[i]);
    }

    /* Do the conversion */

    outloc(ddcur->vwcs,xyin,ddref->vwcs,NULL,&xyout);
    cpl_matrix_delete(xyin);

    /* Loop for each object */

    for (i = 0; i < nr; i++) {
        x[i] = (float)cpl_matrix_get(xyout,i,0);
        y[i] = (float)cpl_matrix_get(xyout,i,1);
    }
    cpl_matrix_delete(xyout);
    return(newtab);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        transinit
    \par Purpose:
        Initialise transformation array
    \par Description:
        Initialise the transformation array
    \par Language:
        C
    \returns 
        A transformation array with no offsets, unit scale and no rotation.
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
            
static cpl_array *transinit(void) {
    double *td;
    cpl_array *t;

    t = cpl_array_new(6,CPL_TYPE_DOUBLE);
    td = cpl_array_get_data_double(t);
    td[0] = 1.0;
    td[1] = 0.0;
    td[2] = 0.0;
    td[3] = 0.0;
    td[4] = 1.0;
    td[5] = 0.0;
    return(t);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        fileptrs_close
    \par Purpose:
        Clean up and free fileptrs array
    \par Description:
        Clean up and free fileptrs array
    \par Language:
        C
    \param nim
        The number of images
    \param fileptrs
        The input image list structure
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void fileptrs_close(int nim, dstrct *fileptrs) {
    int i;

    if (fileptrs == NULL)
        return;
    for (i = 0; i < nim; i++) {
        freewcs((fileptrs+i)->vwcs);
        freespace((fileptrs+i)->bpm);
        freearray((fileptrs+i)->trans);
        freearray((fileptrs+i)->invtrans);
    }
    cpl_free(fileptrs);
}

static void tidy(int nim, dstrct *fileptrs, cpl_wcs *outwcs) {
    fileptrs_close(nim,fileptrs);
    if (outwcs != NULL)
        cpl_wcs_delete(outwcs);
}

/* 

$Log: casu_imstack.c,v $
Revision 1.10  2015/11/25 10:27:19  jim
Modified to allow for unmapping

Revision 1.9  2015/10/22 19:49:02  jim
Can't unload images just in they don't exist on disc to start with (because
then you can't reload them)

Revision 1.8  2015/10/22 10:59:45  jim
Modified for speed and memory usage

Revision 1.7  2015/09/14 18:46:26  jim
replaced a call to free with cpl_free

Revision 1.6  2015/08/12 11:20:47  jim
removed some unnecessary variables

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

Revision 1.4  2015/08/06 05:34:02  jim
Fixes to get rid of compiler moans

Revision 1.3  2015/08/03 12:44:05  jim
Fixed bug causing memory leak

Revision 1.2  2015/06/25 11:53:27  jim
Fixed bug in overlap routine where the wrong section gets analysed

Revision 1.1.1.1  2015/06/12 10:44:32  jim
Initial import

Revision 1.24  2015/06/09 18:34:25  jim
Fixed bug in return value of variance for n=1 input

Revision 1.23  2015/05/20 11:59:02  jim
Removed a redundant memory free

Revision 1.22  2015/05/14 11:46:57  jim
Fixed bug in slow routine affecting the coordinate conversion

Revision 1.21  2015/05/13 11:44:52  jim
Removed homemade WCS routines and replaced them with cpl standard ones

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

Revision 1.19  2015/01/29 11:51:56  jim
modified comments

Revision 1.18  2015/01/09 12:13:15  jim
*** empty log message ***

Revision 1.17  2014/12/14 21:14:53  jim
Fixed a little bug

Revision 1.16  2014/12/11 17:14:31  jim
Fixed issue when only 1 file is being stacked

Revision 1.15  2014/12/11 12:23:33  jim
new version

Revision 1.14  2014/04/26 18:41:22  jim
fixed lots of little bugs

Revision 1.13  2014/04/24 10:26:51  jim
Fixed potential zero divide problem

Revision 1.12  2014/04/23 05:40:46  jim
superficial modifications

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

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

Revision 1.9  2014/04/04 08:42:40  jim
Fixed scaling of output confidence

Revision 1.8  2014/04/03 11:59:29  jim
Removed diagnostics

Revision 1.7  2014/04/03 11:55:50  jim
Fixed scaling problem in rejection

Revision 1.6  2014/03/26 15:48:07  jim
Modified to remove globals. Also augmented to put in a slower algorithm that
takes much less memory. First crack at doing error propagation.

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

Revision 1.4  2013/11/21 08:51:35  jim
Removed some calls to casu_fits_unload_im

Revision 1.3  2013/11/08 06:53:41  jim
Modified to try and minimise the amount of memory required

Revision 1.2  2013-10-24 09:21:18  jim
modified the way the WCS is done

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



*/
