/* $Id: casu_imcombine.c,v 1.3 2015/11/25 10:26:31 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:26:31 $
 * $Revision: 1.3 $
 * $Name:  $
 */

/* Includes */

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

#include <math.h>
#include <string.h>
#include <cpl.h>

#include "casu_utils.h"
#include "casu_stats.h"
#include "casu_fits.h"
#include "casu_mods.h"

/* Macro definitions */

#define FATAL_ERR(_f,_a) {cpl_msg_error(_f,"%s",_a); tidy(&uinfo); *status = CASU_FATAL; return(*status);}
#define WARN_ERR(_f,_a) {cpl_msg_error(_f,"%s",_a); tidy(&uinfo); *status = CASU_WARN; return(CASU_WARN);}
#define INFO_ERR(_f,_a) {cpl_msg_info(_f,"%s",_a);}
#define MEDIANCALC 1
#define MEANCALC 2
#define SZBUF 1024
#define DATAMIN -65535.0
#define DATAMAX 65535.0

/* Definition of litestruct which is used to hold useful information about
   each of the input frames and their associated images */

typedef struct {
    casu_fits        *frame;
    float            exptime;
    float            expfudge;
    float            skylevel;
    float            skynoise;
    float            skyfudge;
    float            skyrenorm;
} litestruct;

/* Structure for carrying around useful information */

typedef struct {
    litestruct *fileptrs;
    float      **datas;
    float      **vars;
    cpl_binary **masks;
    float      *odata;
    float      *ovdata;
    cpl_binary *omask;
    long       npts;
    int        nf;
    float      oskylevel;
    unsigned char *rmask;
    unsigned char *rplus;
} usefulinfo;

/* Static subroutine prototypes */

static void skyest(long npts, float *data, cpl_binary *bpm, float thresh, 
                   float *skymed, float *skynoise);
static void medcalc(usefulinfo *uinfo, float, float, int);
static void meancalc(usefulinfo *uinfo, float, float, int);
static void xclip_med(usefulinfo *uinfo, float thresh, int scaletype);
static void xclip_mean(usefulinfo *uinfo, float thresh, int scaletype);

static void tidy(usefulinfo *uinfo);

/**@{*/

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Stack images into a mean or median image with rejection

    \par Name:
        casu_imcombine
    \par Purpose:
        Stack images into a mean or median image with rejection
    \par Description:
        A list of frames is given. The images can be stacked by doing pixel
        by pixel means or medians. Images can be scaled or biassed to a common
        background value before any combination or rejection is done. The
        rejection algorithm has the option of doing an extra cycle which looks
        at the area surrounding a rejected pixel to see if the rejection is
        really justified. The output consists of a mean/median image, a 
        rejection mask and a second rejection mask that only marks positive
        rejections. The latter could be useful if trying to assess the number
        of cosmic ray hits.
    \par Language:
        C
    \param fset        
        Input image list
    \param fsetv        
        Input image variance list (NULL if not using)
    \param nfits
        The number of input images
    \param combtype    
        Combination type: 1 == Median, 2 == Mean
    \param scaletype   
        Scaling type: 0 == none, 1 == additive, 2 == multiplicative,
        3 == multiplicative by exp time then additive by flux
    \param xrej        
        Extra rejection cycle flag
    \param thresh
        Rejection threshold in sigma above background
    \param expkey
        The header keyword with the exposure time
    \param outimage
        Output image
    \param outvimage
        Output variance image (if input variances are included) 
    \param rejmask
        Output rejection mask
    \param rejplus
        Output mask of rejected pixels with positive residual
    \param drs
        A propertylist to be used to write DRS info
    \param status
        An input/output status that is the same as the returned values below
    \retval CASU_OK 
        if everything is ok
    \retval CASU_WARN 
        if fset has zero length 
    \retval CASU_FATAL 
        for all other conditions
    \par QC headers:
        None
    \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_imcombine(casu_fits **fset, casu_fits **fsetv, int nfits, 
                          int combtype, int scaletype, int xrej, float thresh, 
                          const char *expkey, cpl_image **outimage, 
                          cpl_image **outvimage, unsigned char **rejmask, 
                          unsigned char **rejplus, cpl_propertylist **drs, 
                          int *status) {
    int i,k,j,gotit;
    long npts = 0, nx = -1, ny = -1; /* return value for cpl_image_get_size_x on error */
    char msg[SZBUF];
    float sumsky,sumsig,texp1,texp2,expfudge,skylevel,skynoise,oskynoise;
    float *dat,*work,**datas,**vars,*odata,*ovdata,oskylevel;
    litestruct *ff,*fileptrs;
    cpl_image *im;
    cpl_propertylist *plist_p;
    cpl_binary **masks,*omask;
    usefulinfo uinfo;
    const char *ic_fctid = "casu_imcombine";

    /* Inherited status */

    *rejmask = NULL;
    *rejplus = NULL;
    *drs = NULL;
    *outimage = NULL;
    if (outvimage != NULL)
        *outvimage = NULL;
    if (*status != CASU_OK) 
        return(*status);

    /* Check that there are any files in the first place...*/ 

    if (nfits == 0) 
        WARN_ERR(ic_fctid,"No files to combine")

    /* Initialise the useful info structure */
  
    uinfo.fileptrs = NULL;
    uinfo.datas = NULL;
    uinfo.vars = NULL;
    uinfo.masks = NULL;
    uinfo.rmask = NULL;
    uinfo.rplus = NULL;
    uinfo.nf = nfits;
            
    /* Get some file structures */

    fileptrs = cpl_calloc(nfits,sizeof(litestruct));
    uinfo.fileptrs = fileptrs;

    /* Get workspace for convenience arrays */

    datas = cpl_malloc(nfits*sizeof(float *));
    uinfo.datas = datas;
    if (fsetv != NULL) {
        vars = cpl_malloc(nfits*sizeof(float *));
        uinfo.vars = vars;
    } else 
        vars = NULL;
    npts = casu_getnpts(casu_fits_get_image(fset[0]));
    uinfo.npts = npts;
    for (k = 0; k < nfits; k++) {
        datas[k] = cpl_malloc(npts*sizeof(float));
        if (fsetv != NULL) 
            vars[k] = cpl_malloc(npts*sizeof(float));
    }
    masks = cpl_malloc(nfits*sizeof(cpl_binary *));
    uinfo.masks = masks;

    /* Get pointers to the data arrays */

    for (k = 0; k < nfits; k++) {
        im = casu_fits_get_image(fset[k]);
        dat = cpl_image_get_data_float(im);
        if (dat == NULL) {
            snprintf(msg,SZBUF,"Failed to load data from extension %d in %s",
                     casu_fits_get_nexten(fset[k]),
                     casu_fits_get_filename(fset[k]));
            FATAL_ERR(ic_fctid,msg)
        }
        for (i = 0; i < npts; i++)
            datas[k][i] = dat[i];
        masks[k] = cpl_mask_get_data(cpl_image_get_bpm(im));
        if (fsetv != NULL) {
            im = casu_fits_get_image(fsetv[k]);
            dat = cpl_image_get_data_float(im);
            if (dat == NULL) {
                snprintf(msg,SZBUF,"Failed to load data from extension %d in %s",
                         casu_fits_get_nexten(fsetv[k]),
                         casu_fits_get_filename(fsetv[k]));
                FATAL_ERR(ic_fctid,msg)
            }
            for (i = 0; i < npts; i++)
                vars[k][i] = dat[i];
        }
    }

    /* Open each file in turn and fill in the necessary information. Start with
       the file name...*/

    for (i = 0; i < nfits; i++) {
        ff = fileptrs + i;
        ff->frame = fset[i];

        /* If this is the first frame, then keep the size of the data
           array for future reference */

        if (i == 0) {
            nx = (long)cpl_image_get_size_x(casu_fits_get_image(fset[0]));
            ny = (long)cpl_image_get_size_y(casu_fits_get_image(fset[0]));
            npts = nx*ny;
        }

        /* Get the header and the exposure time */

        if (strlen(expkey)) {
            gotit = 0;
            for (k = 0; k < 2; k++) {
                if (k == 0) 
                    plist_p = casu_fits_get_phu(ff->frame);
                else
                    plist_p = casu_fits_get_ehu(ff->frame);
                if (plist_p != NULL && cpl_propertylist_has(plist_p,expkey)) {
                    gotit = 1;
                    break;
                }
            }
            if (gotit) {
                texp2 = (float)cpl_propertylist_get_double(plist_p,expkey);
            } else {
                snprintf(msg,SZBUF,
                         "Couldn't get exposure time for %s - expkey = %s",
                         casu_fits_get_filename(ff->frame),expkey);
                INFO_ERR(ic_fctid,msg);
                texp2 = 1.0;    
            }
        } else {
            texp2 = 1.0;
        }

        /* Set a few properties */

        ff->exptime = texp2;
        texp1 = fileptrs->exptime;
        expfudge = texp1/texp2;
        ff->expfudge = expfudge;

        /* If scaling by relative exposure time, then do it now. NB: This
           isn't necessary for the first file as all the others are scaled
           relative to it */

        if (scaletype == 3 && i > 0) {
            for (j = 0; j < npts; j++) {
                datas[i][j] *= ff->expfudge;
                if (vars != NULL) 
                    vars[i][j] *= powf(ff->expfudge,2.0);
            }
        }

        /* Get the background estimate and noise */

        skyest(npts,datas[i],masks[i],thresh,&skylevel,&skynoise);
        ff->skylevel = skylevel;
        ff->skynoise = skynoise;
    }

    /* Work out average background and noise. Then create background zeropoint
       or scale factor, depending upon which was requested in the call */

    work = cpl_malloc(nfits*sizeof(float));
    for (i = 0; i < nfits; i++)
        work[i] = (fileptrs+i)->skylevel;
    sumsky = casu_med(work,NULL,(long)nfits);
    for (i = 0; i < nfits; i++)
        work[i] = (fileptrs+i)->skynoise;
    sumsig = casu_med(work,NULL,(long)nfits);
    cpl_free(work);
    switch (scaletype) {
    case 1:
        for (i = 0; i < nfits; i++)
            (fileptrs+i)->skyfudge = sumsky - (fileptrs+i)->skylevel;
        break;
    case 2:
        for (i = 0; i < nfits; i++) 
            (fileptrs+i)->skyfudge = sumsky/(fileptrs+i)->skylevel;
        break;      
    case 3:
        for (i = 0; i < nfits; i++)
            (fileptrs+i)->skyfudge = sumsky - (fileptrs+i)->skylevel;
        break;
    default:
        for (i = 0; i < nfits; i++) 
            (fileptrs+i)->skyfudge = 0.0;
        break;
    }

    /* Open an output image based on the first frame in the frameset */

    *outimage = cpl_image_new((cpl_size)nx,(cpl_size)ny,CPL_TYPE_FLOAT);
    odata = cpl_image_get_data_float(*outimage);
    uinfo.odata = odata;
    if (fsetv != NULL) {
        *outvimage = cpl_image_new((cpl_size)nx,(cpl_size)ny,CPL_TYPE_FLOAT);
        ovdata = cpl_image_get_data_float(*outvimage);
    } else {
        ovdata = NULL;
    }
    uinfo.ovdata = ovdata;
    omask = cpl_mask_get_data(cpl_image_get_bpm(*outimage));
    uinfo.omask = omask;
    if (*outimage == NULL || odata == NULL) 
        FATAL_ERR(ic_fctid,"Couldn't create output image")
    *rejmask = cpl_calloc(npts,sizeof(*rejmask));
    uinfo.rmask = *rejmask;
    *rejplus = cpl_calloc(npts,sizeof(*rejplus));
    uinfo.rplus = *rejplus;

    /* Now do the averaging/medianing */

    switch (combtype) {
    case MEDIANCALC:
        medcalc(&uinfo,thresh,sumsig,scaletype);
        break;
    case MEANCALC:
        meancalc(&uinfo,thresh,sumsig,scaletype);
        break;
    }

    /* Do the extra clipping here if you want it */

    if (xrej) {
        
        /* First get sky background and sigma from output data */

        skyest(npts,odata,omask,thresh,&oskylevel,&oskynoise);
        uinfo.oskylevel = oskylevel;

        /* Now loop for all the files subtract off the mean frame (suitably
           scaled and zero pointed depending on what was done in the first 
           place) */

        for (i = 0; i < nfits; i++) {
            ff = fileptrs + i;
            ff->skyrenorm = ff->skylevel/oskylevel;
            switch (scaletype) {
            case 1:
                for (k = 0; k < npts; k++) 
                    datas[i][k] -= (odata[k] - oskylevel);
                break;
            case 2:
                for (k = 0; k < npts; k++)
                    datas[i][k] -= (odata[k] - oskylevel)*ff->skyrenorm;
                break;
            case 3:
                for (k = 0; k < npts; k++) 
                    datas[i][k] -= (odata[k] - oskylevel);
                break;
            case 0:
                for (k = 0; k < npts; k++) 
                    datas[i][k] -= (odata[k] - oskylevel);
                break;
            }

            /* Re-estimate the noise for this image */

            skyest(npts,datas[i],masks[i],thresh,&skylevel,&skynoise);
            ff->skynoise = skynoise;
        }

        /* Now do the extra clip... */

        switch (combtype) {
        case MEDIANCALC:
            xclip_med(&uinfo,thresh,scaletype);
            break;
        case MEANCALC:
            xclip_mean(&uinfo,thresh,scaletype);
            break;
        }
    }

    /* Write provenance keywords */

    *drs = cpl_propertylist_new();
    casu_prov(*drs,fset,nfits,1);

    /* Right, tidy and get out of here */

    tidy(&uinfo);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        xclip_med
    \par Purpose:
        Do an extra clipping cycle for median combined data.
    \par Description:
        The median is recalculated by looking at the pixels that have been 
        rejected and making sure that they really deserve to be rejected 
        based on what the noise properties of the area around the pixel.
        A mask of rejected pixels is set along with a mask of pixels rejected
        with positive residuals.
    \par Language:
        C
    \param uinfo
        Structure with bundle of useful information 
    \param thresh
        The threshold for clipping in units of background noise
    \param scaletype
        The type of scaling to be done before combination
    \return Nothing
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void xclip_med(usefulinfo *uinfo, float thresh, int scaletype) {
    int nf1,nf2,nfm,nrejmax,is_even,k,is_even2,nrej,nremain,nm,nmm,nplus;
    int nminus,nn,j,nf;
    cpl_binary **masks,*omask;
    unsigned char *rmask,*rplus;
    long npts,i;
    float **work,**dork,value,cliplev,**datas,*odata,oskylevel,**vars;
    float valv = 0.0 ,*ovdata;
    litestruct *ff,*fileptrs;

    /* Dereference the information you need */

    fileptrs = uinfo->fileptrs;
    datas = uinfo->datas;
    vars = uinfo->vars;
    masks = uinfo->masks;
    odata = uinfo->odata;
    ovdata = uinfo->ovdata;
    omask = uinfo->omask;
    npts = uinfo->npts;
    nf = uinfo->nf;
    oskylevel = uinfo->oskylevel;
    rmask = uinfo->rmask;
    rplus = uinfo->rplus;

    /* Get some workspace */

    work = cpl_malloc(4*sizeof(float *));
    for (i = 0; i < 4; i++)
        work[i] = cpl_malloc(nf*sizeof(float));
    dork = cpl_malloc(2*sizeof(float *));
    for (i = 0; i < 2; i++)
        dork[i] = cpl_malloc(nf*sizeof(float));

    /* Loop for each input pixel now... */

    for (i = 0; i < npts; i++) {
        if (omask[i])
            continue;

        /* Scale or shift data */

        nn = 0;
        switch (scaletype) {
        case 0:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i];
                work[1][nn] = ff->skynoise;
                work[2][nn] = datas[k][i] + odata[i] - oskylevel;
                if (vars != NULL)
                    work[3][nn] = vars[k][i];
                nn++;
            }
            break;
        case 1:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i] + ff->skyfudge;
                work[1][nn] = ff->skynoise;
                work[2][nn] = datas[k][i] + odata[i] - oskylevel + 
                    ff->skyfudge;
                if (vars != NULL)
                    work[3][nn] = vars[k][i];
                nn++;
            }
            break;
        case 2:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i]*ff->skyfudge;
                work[1][nn] = ff->skynoise*ff->skyfudge;
                work[2][nn] = (datas[k][i] + odata[i]*ff->skyrenorm - 
                                 ff->skylevel)*ff->skyfudge;
                if (vars != NULL)
                    work[3][nn] = vars[k][i]*powf(ff->skyfudge,2.0);
                nn++;
            }
            break;
        case 3:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i] + ff->skyfudge;
                work[1][nn] = ff->skynoise;
                work[2][nn] = datas[k][i] + odata[i] - oskylevel + 
                    ff->skyfudge;
                if (vars != NULL)
                    work[3][nn] = vars[k][i];
                nn++;
            }
            break;
        }       

        /* Set up a few useful variables */

        nf1 = nn/2 - 1;
        nf2 = nf1 + 1;
        nfm = (nn + 1)/2 - 1;
        nrejmax = nn/2;
        is_even = !(nn & 1);

        /* Sort and get a first pass median */

        casu_sort(work,nn,4);
        if (is_even)
            value = 0.5*(work[0][nf1] + work[0][nf2]);
        else 
            if (nn < 5) 
                value = work[0][nfm];
            else 
                value = 0.25*(work[0][nfm-1] + work[0][nfm+1]) + 0.5*work[0][nfm];
        if (vars != NULL) {
            valv = 0.0;
            for (j = 0; j < nn; j++)
                valv += work[3][j];
            valv *= CPL_MATH_PI_2/powf((float)nn,2.0);
        }
        
        /* Do clipping */

        nplus = 0;
        cliplev = value + thresh*work[1][nn-1];
        while (nplus < nrejmax && work[0][nn-nplus-1] > cliplev)
            nplus++;
        nminus = 0;
        cliplev = value - thresh*work[1][nn-1];
        while ((nplus+nminus) < nrejmax && work[0][nminus] < cliplev)
            nminus++;
        nrej = nplus + nminus;

        /* If there were any clipped out, the re-estimate the value */

        if (nrej > 0) {
            nremain = nn - nrej;
            if (nremain != 0) {
                nm = nremain/2 - 1;
                for (j = 0; j < nremain; j++) {
                    dork[0][j] = work[2][j+nminus];
                    dork[1][j] = work[3][j+nminus];
                }
                nmm = (nremain + 1)/2 - 1;
                is_even2 = !(nremain & 1);
                casu_sort(dork,nm,2);
                if (is_even2) 
                    value = 0.5*(dork[0][nm] + dork[0][nm+1]);
                else 
                    if (nremain < 3) 
                        value = dork[0][nmm];
                    else 
                        value = 0.5*dork[0][nmm] + 0.25*(dork[0][nmm-1] + 
                                                         dork[0][nmm+1]);
                if (vars != NULL) {
                    valv = 0.0;
                    for (j = 0; j < nremain; j++)
                        valv += dork[1][j];
                    valv *= CPL_MATH_PI_2/powf((float)nremain,2.0);
                }
            }
        
            /* Store the result away */

            odata[i] = value;
            if (vars != NULL) 
                ovdata[i] = valv;
            rmask[i] = min(255,nrej);
            rplus[i] = min(255,nplus);
        } else {
            rmask[i] = 0;
            rplus[i] = 0;
        }
    }

    /* Ditch workspace and get out of here */

    for (i = 0; i < 4; i++)
        cpl_free(work[i]);
    cpl_free(work);
    for (i = 0; i < 2; i++)
        cpl_free(dork[i]);
    cpl_free(dork);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        xclip_mean
    \par Purpose:
        Do an extra clipping cycle for mean combined data.
    \par Description:
        The mean is recalculated by looking at the pixels that have been 
        rejected and making sure that they really deserve to be rejected 
        based on what the noise properties of the area around the pixel.
        A mask of rejected pixels is set along with a mask of pixels rejected
        with positive residuals.
    \par Language:
        C
    \param uinfo
        Structure with bundle of useful information 
    \param thresh
        The threshold for clipping in units of background noise
    \param scaletype
        The type of scaling to be done before combination
    \return Nothing
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void xclip_mean(usefulinfo *uinfo, float thresh, int scaletype) {
    int k,nf2,nrej,nplus,kk,krem,nf;
    float *work[4],value,value2,nrejmax,resid,maxresid,**datas,*odata;
    float oskylevel,**vars,valv = 0.0 ,valv2,*ovdata;
    cpl_binary **masks,*omask;
    long i,nn,npts;
    litestruct *ff,*fileptrs;
    unsigned char *iflag,*rmask,*rplus;

    /* Dereference the information you need */

    fileptrs = uinfo->fileptrs;
    datas = uinfo->datas;
    vars = uinfo->vars;
    masks = uinfo->masks;
    odata = uinfo->odata;
    ovdata = uinfo->ovdata;
    omask = uinfo->omask;
    npts = uinfo->npts;
    nf = uinfo->nf;
    oskylevel = uinfo->oskylevel;
    rmask = uinfo->rmask;
    rplus = uinfo->rplus;

    /* Get some workspace */

    for (i = 0; i < 4; i++)
        work[i] = cpl_malloc(nf*sizeof(float));
    iflag = cpl_malloc(nf*sizeof(unsigned char));

    /* Loop for each input pixel now... */

    nrejmax = nf/2;
    for (i = 0; i < npts; i++) {
        if (omask[i])
            continue;

        /* Scale or shift data */

        nn = 0;
        switch (scaletype) {
        case 0:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i];
                work[1][nn] = ff->skynoise;
                work[2][nn] = datas[k][i] + odata[i] - oskylevel;
                if (vars != NULL) 
                    work[3][nn] = vars[k][i];
                iflag[nn++] = 0;
            }
            break;
        case 1:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i] + ff->skyfudge;
                work[1][nn] = ff->skynoise;
                work[2][nn] = datas[k][i] + odata[i] - oskylevel + ff->skyfudge;
                if (vars != NULL) 
                    work[3][nn] = vars[k][i];
                iflag[nn++] = 0;
            }
            break;
        case 2:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i]*ff->skyfudge;
                work[1][nn] = ff->skynoise*ff->skyfudge;
                work[2][nn] = (datas[k][i] + odata[i]*ff->skyrenorm - 
                               ff->skylevel)*ff->skyfudge;
                if (vars != NULL) 
                    work[3][nn] = vars[k][i]*powf(ff->skyfudge,2.0);
                iflag[nn++] = 0;
            }
            break;
        case 3:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                ff = fileptrs + k;
                work[0][nn] = datas[k][i] + ff->skyfudge;
                work[1][nn] = ff->skynoise;
                work[2][nn] = datas[k][i] + odata[i] - oskylevel + ff->skyfudge;
                if (vars != NULL) 
                    work[3][nn] = vars[k][i];
                iflag[nn++] = 0;
            }
            break;
        }       

        /* Get a first pass mean */

        value = 0.0;
        for (k = 0; k < nn; k++) 
            value += work[0][k];
        value /= (float)nn;
        if (vars != NULL) {
            valv = 0.0;
            for (k = 0; k < nn; k++) 
                valv += work[3][k];
            valv /= powf((float)nn,2.0);
        }
        
        /* Enter a rejection loop. Reject pixels one at a time  */

        nplus = 0;
        nrej = 0;
        for (kk = 0; kk < nrejmax; kk++) {
            maxresid = -1.0e30;
            krem = -1;
            for (k = 0; k < nn; k++) {
                if (iflag[k] == 1)
                    continue;
                resid = fabs(work[0][k] - value);
                if (resid > thresh*work[1][k]) {
                    if (nf <= 2)
                        resid = work[0][k] - value;
                    if (resid > maxresid) {
                        krem = k;
                        maxresid = resid;
                    }
                }
            }
            
            /* No further rejections */

            if (krem == -1)
                break;

            /* Another pixel is rejected. If it's positive count it */

            iflag[krem] = 1;
            if ((work[0][krem] - value) > 0.0)
                nplus++;
            nrej++;

            /* Re-evaluate the mean */

            nf2 = 0;
            value2 = 0.0;
            valv2 = 0.0;
            for (k = 0; k < nn; k++) {
                if (iflag[k] == 0) {
                    value2 += work[0][k];
                    if (vars != NULL) 
                        valv2 += work[3][k];
                    nf2++;
                }
            }
            if (nf2 != 0) {
                value = value2/(float)nf2;
                if (vars != NULL) 
                    valv = valv2/powf((float)nf2,2.0);
            } else 
                break;
        }

        /* If there were any clipped out store the new value */

        if (nrej > 0) {
            odata[i] = value;
            if (vars != NULL)
                ovdata[i] = valv;
        }
        rmask[i] = min(255,nrej);       
        rplus[i] = min(255,nplus);
    }

    /* Ditch workspace and get out of here */

    for (k = 0; k < 4; k++)
        cpl_free(work[k]);
    cpl_free(iflag);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        medcalc
    \par Purpose:
        Calculate the median of an array of values and clip deviant points
    \par Description:
        An input data array is scaled according to the requested scaling
        algorithm. A median is calculated. A clipping threshold is defined
        from the average sky noise and the median is recalculated if any
        of the scaled pixels fall outside this threshold
    \par Language:
        C
    \param uinfo
        Structure with bundle of useful information 
    \param thresh
        The threshold for clipping in units of background noise
    \param avskynoise
        The average noise on the sky
    \param scaletype
        The type of scaling to be done before combination
    \return Nothing
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void medcalc(usefulinfo *uinfo, float thresh, float avskynoise, 
                    int scaletype) {
    int nf1,nf2,nfm,nrejmax,is_even,nrej,nremain,nm,nmm,is_even2,k,nminus;
    int nplus,nn,nf;
    long i,npts,j;
    float value,cliplev,*work[2],**datas,*odata,**vars, valv = 0.0,*ovdata;
    litestruct *fileptrs;
    cpl_binary **masks,*omask;
    unsigned char *rmask,*rplus;

    /* Dereference the information you need */

    fileptrs = uinfo->fileptrs;
    datas = uinfo->datas;
    vars = uinfo->vars;
    masks = uinfo->masks;
    odata = uinfo->odata;
    ovdata = uinfo->ovdata;
    omask = uinfo->omask;
    npts = uinfo->npts;
    nf = uinfo->nf;
    rmask = uinfo->rmask;
    rplus = uinfo->rplus;

    /* Get a workspace */

    work[0] = cpl_malloc(nf*sizeof(float));
    work[1] = cpl_malloc(nf*sizeof(float));

    /* Ok, loop for each pixel... */

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

        /* Scale or shift data */

        nn = 0;
        switch (scaletype) {
        case 0:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i];
                if (vars != NULL)
                    work[1][nn] = vars[k][i];
                nn++;
            }
            break;
        case 1:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i] + (fileptrs+k)->skyfudge;
                if (vars != NULL)
                    work[1][nn] = vars[k][i];
                nn++;
            }
            break;
        case 2:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i]*(fileptrs+k)->skyfudge;
                if (vars != NULL)
                    work[1][nn] = vars[k][i]*pow((fileptrs+k)->skyfudge,2.0);
                nn++;
            }
            break;
        case 3:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i] + (fileptrs+k)->skyfudge;
                if (vars != NULL)
                    work[1][nn] = vars[k][i];
                nn++;
            }
            break;
        }
        
        /* If nothing is available, then flag the output pixel */

        if (nn == 0) {
            odata[i] = 0.0;
            if (vars != NULL) 
                ovdata[i] = 0.0;
            omask[i] = 1;
            rmask[i] = 0;
            rplus[i] = 0;
            continue;
        }
            
        /* Set up a few useful variables */

        nf1 = nn/2 - 1;
        nf2 = nf1 + 1;
        nfm = (nn + 1)/2 - 1;
        nrejmax = nn/2;
        is_even = !(nn & 1);

        /* Sort data and get the median */

        casu_sort(work,nn,2);
        if (is_even)
            value = 0.5*(work[0][nf1] + work[0][nf2]);
        else 
            if (nn < 5) 
                value = work[0][nfm];
            else 
                value = 0.25*(work[0][nfm-1] + work[0][nfm+1]) + 
                    0.5*work[0][nfm];
        if (vars != NULL) {
            valv = 0.0;
            for (j = 0; j < nn; j++)
                valv += work[1][j];
            valv = CPL_MATH_PI_2*valv/powf((float)nn,2);
        }

        /* Enter a rejection loop */

        nplus = 0;
        cliplev = value + thresh*avskynoise;
        while (nplus < nrejmax && work[0][nn-nplus-1] > cliplev)
            nplus++;
        nminus = 0;
        cliplev = value - thresh*avskynoise;
        while ((nplus+nminus) < nrejmax && work[0][nminus] < cliplev)
            nminus++;
        nrej = nplus + nminus;

        /* If you've clipped any, then recalculate the median...*/

        if (nrej > 0) {
            nremain = nn - nrej;
            nm = nremain/2 - 1 + nminus;
            nmm = (nremain + 1)/2 - 1 + nminus;
            is_even2 = !(nremain & 1);
            if (is_even2) 
                value = 0.5*(work[0][nm] + work[0][nm+1]);
            else 
                if (nremain < 3) 
                    value = work[0][nmm];
                else 
                    value = 0.5*work[0][nmm] + 0.25*(work[0][nmm-1] + 
                                                     work[0][nmm+1]);
            if (vars != NULL) {
                valv = 0.0;
                for (j = 0; j < nremain; j++)
                    valv += work[1][j];
                valv = CPL_MATH_PI_2*valv/powf((float)nn,2);
            }
        }

        /* Store the result away */

        odata[i] = value;
        if (vars != NULL)
            ovdata[i] = valv;
        omask[i] = 0;
        rmask[i] = min(255,nrej);
        rplus[i] = min(255,nplus);
    }

    /* Get rid of workspace */

    cpl_free(work[0]);
    cpl_free(work[1]);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        meancalc
    \par Purpose:
        Calculate the mean of an array of values and clip deviant points
    \par Description:
        An input data array is scaled according to the requested scaling
        algorithm. A mean is calculated. A clipping threshold is defined
        from the average sky noise and the mean is recalculated if any
        of the scaled pixels fall outside this threshold
    \par Language:
        C
    \param uinfo
        Structure with bundle of useful information 
    \param thresh
        The threshold for clipping in units of background noise
    \param avskynoise
        The average noise on the sky
    \param scaletype
        The type of scaling to be done before combination
    \return Nothing
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void meancalc(usefulinfo *uinfo, float thresh, float avskynoise, 
                     int scaletype) {
    int nf2,k,nrej,nplus,nrejmax,kk,krem,nf;
    long i,nn,npts;
    float *work[2],value,value2,maxresid,resid,fresid,cliplev,**datas,*odata;
    float **vars,valv,valv2,*ovdata;
    cpl_binary **masks,*omask;
    unsigned char *iflag,*rmask,*rplus;
    litestruct *fileptrs;

    /* Dereference the information you need */

    fileptrs = uinfo->fileptrs;
    datas = uinfo->datas;
    vars = uinfo->vars;
    masks = uinfo->masks;
    odata = uinfo->odata;
    ovdata = uinfo->ovdata;
    omask = uinfo->omask;
    npts = uinfo->npts;
    nf = uinfo->nf;
    rmask = uinfo->rmask;
    rplus = uinfo->rplus;

    /* Get vectors for workspace */

    work[0] = cpl_malloc(nf*sizeof(float));
    work[1] = cpl_malloc(nf*sizeof(float));
    iflag = cpl_malloc(nf*sizeof(unsigned char));

    /* Ok, loop for each pixel... */

    cliplev = thresh*avskynoise;
    for (i = 0; i < npts; i++) {

        /* Scale or shift data */

        nn = 0;
        switch (scaletype) {
        case 0:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i];
                if (vars != NULL) 
                    work[1][nn] = vars[k][i];
                nn++;
            }
            break;
        case 1:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i] + (fileptrs+k)->skyfudge;
                if (vars != NULL) 
                    work[1][nn] = vars[k][i];
                nn++;
            }
            break;
        case 2:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i]*(fileptrs+k)->skyfudge;
                if (vars != NULL) 
                    work[1][nn] = vars[k][i]*powf((fileptrs+k)->skyfudge,2.0);
                nn++;
            }
            break;
        case 3:
            for (k = 0; k < nf; k++) {
                if (masks[k][i])
                    continue;
                work[0][nn] = datas[k][i] + (fileptrs+k)->skyfudge;
                if (vars != NULL) 
                    work[1][nn] = vars[k][i]*powf((fileptrs+k)->skyfudge,2.0);
                nn++;
            }
            break;
        }
        
        /* If nothing is available, then flag the output pixel */

        if (nn == 0) {
            odata[i] = 0.0;
            if (vars != NULL)
                ovdata[i] = 0.0;
            omask[i] = 1;
            rmask[i] = 0;
            rplus[i] = 0;
            continue;
        }
            
        /* Get the mean */
        
        value = 0.0;
        valv = 0.0;
        for (k = 0; k < nn; k++) {
            value += work[0][k];
            valv += work[1][k];
            iflag[k] = 0;
        }
        value /= (float)nn;
        valv /= powf((float)nn,2.0);

        /* Enter a rejection loop  */
        
        nrejmax = nn - 1;
        nplus = 0;
        nf2 = 0;
        for (kk = 0; kk < nrejmax; kk++) {
            maxresid = -1.0e30;
            krem = -1;
            for (k = 0; k < nn; k++) {
                if (iflag[k] == 1) 
                    continue;
                resid = work[0][k] - value;
                fresid = (float)fabs((double)resid);
                if (fresid > cliplev) {
                    if (nn <= 2)
                        fresid = work[0][k] - value;
                    if (fresid > maxresid) {
                        krem = k;
                        maxresid = fresid;
                    }
                }
            }
            if (krem == -1)
                break;
            if ((work[0][krem] - value) > 0.0)
                nplus++;
            value2 = 0.0;
            valv2 = 0.0;
            iflag[krem] = 1;
            nf2 = 0;
            for (k = 0; k < nn; k++) {
                if (iflag[k] == 0) {
                    value2 += work[0][k];
                    valv2 += work[1][k];
                    nf2 += 1;
                }
            }
            value = value2/(float)nf2;
            valv = valv2/powf((float)nf2,2.0);
        }

        /* If you've clipped any, then recalculate the mean...*/

        nrej = nn - nf2;

        /* Store the result away */

        odata[i] = value;
        if (vars != NULL)
            ovdata[i] = valv;
        omask[i] = 0;
        rmask[i] = min(255,nrej);
        rplus[i] = min(255,nplus);
    }

    /* Get rid of workspace */

    cpl_free(work[0]);
    cpl_free(work[1]);
    cpl_free(iflag);
}
            
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        skyest
    \par Purpose:
        Calculate a background level and noise estimate
    \par Description:
        A background median and noise level is calculated from an input 
        data array. A bad pixel map can be included.
    \par Language:
        C
    \param npts
        The number of pixels in the data array
    \param data
        The data array
    \param mask
        A pixel mask
    \param thresh
        The threshold for clipping in units of background noise
    \param skymed
        The returned sky level
    \param skynoise
        The returned sky noise    
    \return Nothing
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void skyest(long npts, float *data, cpl_binary *mask, float thresh, 
                   float *skymed, float *skynoise) {
    unsigned char *bpm;

    /* Set up the bad pixel mask */

    bpm = (unsigned char *)mask;

    /* Get the stats */

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

}

/*---------------------------------------------------------------------------*/
/**
    \brief  Tidy up allocated space
 */
/*---------------------------------------------------------------------------*/

static void tidy(usefulinfo *uinfo) {
    int i;

    /* Free up work space associated with file structures */

    freespace(uinfo->fileptrs);
    for (i = 0; i < uinfo->nf; i++)
        freespace(uinfo->datas[i]);
    if (uinfo->vars != NULL) {
        for (i = 0; i < uinfo->nf; i++)
            freespace(uinfo->vars[i]);
    }
    freespace(uinfo->datas);
    freespace(uinfo->vars);
    freespace(uinfo->masks);
}

/**@}*/


/*

$Log: casu_imcombine.c,v $
Revision 1.3  2015/11/25 10:26:31  jim
replaced some hardcoded numbers with defines

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

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

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

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

Revision 1.6  2015/01/09 13:12:44  jim
Fixed little bug in output to outvimage

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

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

Revision 1.3  2014/03/26 15:43:49  jim
Modified to remove globals

Revision 1.2  2013/11/21 09:38:13  jim
detabbed

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


*/

