/* $Id: casu_nebuliser.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 <cpl.h>
#include "casu_mods.h"
#include "casu_utils.h"
#include "casu_fits.h"
#include "casu_stats.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MEDIANCALC 1
#define MEANCALC 2
#define DATAMIN -1000.0
#define DATAMAX 65535.0

typedef struct {
    float sum;
    float sumw;
    int   naver;
    float nextw;
    float lastw;
    float nextval;
    float lastval;
    short int nextc;
    short int lastc;
} nextlast;

static void twodfilt(float *data, unsigned char *bpm, int nx, int ny, 
                     int medfilt, int linfilt, int niter, int axis, 
                     int twod, int takeout_sky, int inorm, int wantback, 
                     float signeg, float sigpos, float **backmap);
static void bfilt_1d(float *data, unsigned char *bpm, int nx, int ny, 
                     int filt, int stat, int axis);
static void bfilt_2d(float *data, unsigned char *bpmcopy, int nx, int ny,
                     int filt, int stat);
static void docols_2(float *data, unsigned char *bpm, float *dbuf, 
                     unsigned char *bbuf, int nx, int ny, int filter, 
                     int stat);
static void dorows_2(float *data, unsigned char *bpm, float *dbuf, 
                     unsigned char *bbuf, int nx, int ny, int filter, 
                     int stat);
static void dostat(float *data, unsigned char *bpm, unsigned char *goodval,
                   int npts, int nfilt, int whichstat);
static void wraparound(float *data, unsigned char *bpm, int npts, int nfilt, 
                       int whichstat, float **ybuf, unsigned char **ybbuf, 
                       int *nbuf);
static void medavg(float *array, unsigned char *bpm, int *ipoint, int npix, 
                   int whichstat, int newl, nextlast *nl, float *outval, 
                   unsigned char *outbp);
static void quickie(float *array, unsigned char *iarray, int *iarray2, 
                    int lll, int narray);
static void sortm(float *a1, unsigned char *a2, int *a3, int n);
static void plugholes(float *data, unsigned char *bpm, int nx);


/**@{*/

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Remove small scale background variations

    \par Name:
        casu_nebuliser
    \par Purpose:
        Remove small scale background variations
    \par Description:
        An input image is smoothed using an unsharp masking algorithm. The
        smoothed image is subtracted which should leave an image with minimal
        background variation.
    \par Language:
        C
    \param infile
        The input image to be smoothed
    \param inconf
        The relevant confidence map. If this is NULL, then all pixels will
        be considered good.
    \param medfilt
        The size in pixels of the median filter box
    \param linfilt
        The size in pixels of the linear filter box
    \param niter
        The number of rejection iterations to use
    \param axis
        In the case of a 1d smooth, which axis to do first (1 => X, 2 => Y)
    \param twod
        If set, then a full 2d smooth is done. Otherwise it is a combination
        of 2 1d smoothing operations
    \param takeout_sky
        If set then the output median background will be zero
    \param norm
        If set, then the output is the input divided by the smoothed image
    \param wantback
        If set, then we want to have an output image of the background map
    \param signeg
        The low clipping threshold
    \param sigpos
        The high clipping threshold
    \param backmap
        The output background map, if requested. Otherwise set to NULL
    \param status 
        An input/output status that is the same as the returned values below.
    \retval CASU_OK 
        if everything is ok
    \retval CASU_FATAL 
        if image and confidence maps don't have the same dimensionality
    \par QC headers:
        None
    \par DRS headers:
        The following DRS keywords are written to the input file's extension
        header:
        - \b NEBULISED
            A boolean flag to say that the image has been nebulised
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_nebuliser(casu_fits *infile, casu_fits *inconf, int medfilt,
                          int linfilt, int niter, int axis, int twod,
                          int takeout_sky, int norm, int wantback, 
                          float signeg, float sigpos, casu_fits **backmap,
                          int *status) {
    cpl_image *im;
    int nx,ny,i,*cdata;
    float *data,*backdata,*bdata;
    int npts;
    unsigned char *bpm;
    const char *fctid = "casu_nebuliser";

    /* Inherited status */

    *backmap = NULL;
    if (*status != CASU_OK)
        return(*status);

    /* Get the data array of the input file */

    im = casu_fits_get_image(infile);
    nx = cpl_image_get_size_x(im);
    ny = cpl_image_get_size_y(im);
    data = cpl_image_get_data_float(im);

    /* Do we have a confidence map? If so then use it to create a bpm. 
       If not, then create a bpm with all zeros */

    npts = nx*ny;
    bpm = cpl_calloc(npts,sizeof(unsigned char));
    if (inconf != NULL) {
        im = casu_fits_get_image(inconf);
        if (cpl_image_get_size_x(im) != nx || cpl_image_get_size_y(im) != ny) {
            cpl_msg_error(fctid,"Image and conf map dimensions don't match");
            freespace(bpm);
            FATAL_ERROR
        }
        cdata = cpl_image_get_data(im);
        for (i = 0; i < npts; i++)
            bpm[i] = (cdata[i] == 0);
    }

    /* Ok do the filtering now */

    twodfilt(data,bpm,nx,ny,medfilt,linfilt,niter,axis,twod,takeout_sky,norm,
             wantback,signeg,sigpos,&backdata);

    /* Write a header item to the data header to show that the nebulisation
       has been successful */

    cpl_propertylist_append_bool(casu_fits_get_ehu(infile),
                                 "ESO DRS NEBULISED",1);
    cpl_propertylist_set_comment(casu_fits_get_ehu(infile),
                                 "ESO DRS NEBULISED",
                                 "Nebuliser has been used on this image");

    /* Now if we have a background image, then create the fits structure
       for it as a copy of the input image */

    if (wantback) {
        *backmap = casu_fits_duplicate(infile);
        im = casu_fits_get_image(*backmap);
        bdata = cpl_image_get_data_float(im);
        memmove(bdata,backdata,npts*sizeof(float));
        freespace(backdata);
    }

    /* Tidy and exit */

    freespace(bpm);
    GOOD_STATUS
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        twodfilt
    \par Purpose:
        Do a running one- or two-dimensional filter
    \par Description:
        The input image is smoothed using a running two-dimensional filter
        or a pair of one dimensional filters. The former can be quite time 
        consuming.
    \par Language:
        C
    \param data
        The input image data to smoothed
    \param bpm
        The bad pixel mask for the image
    \param nx
        The X dimension of the data
    \param ny
        The Y dimension of the data
    \param medfilt
        The size in pixels of the median filter box
    \param linfilt
        The size in pixels of the linear filter box
    \param niter
        The number of rejection iterations to use
    \param axis
        In the case of a 1d smooth, which axis to do first (1 => X, 2 => Y)
    \param twod
        If set, then a full 2d smooth is done. Otherwise it is a combination
        of 2 1d smoothing operations
    \param takeout_sky
        If set then the output median background will be zero
    \param inorm
        If set, then the output is the input divided by the smoothed image
    \param wantback
        If set, then we want to have an output image of the background map
    \param signeg
        The low clipping threshold
    \param sigpos
        The high clipping threshold
    \param backmap
        The output background map, if requested. Otherwise set to NULL
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void twodfilt(float *data, unsigned char *bpm, int nx, int ny, 
                     int medfilt, int linfilt, int niter, int axis, 
                     int twod, int takeout_sky, int inorm, int wantback, 
                     float signeg, float sigpos, float **backmap) {
    int i,iter,nter,nmask;
    long nn;
    float *buffer,*orig,*orig_sm,*work,medsky,sigsky,rescale,lthr,hthr;
    float diff;
    unsigned char *bpmcopy;
    
    /* Get some workspace. One holds a copy of the original data. The
       others are for work */

    nn = nx*ny;
    buffer = cpl_malloc(3*nn*sizeof(*buffer));
    orig = buffer;
    orig_sm = orig + nn;
    work = orig_sm + nn;
    memmove((char *)orig,(char *)data,nn*sizeof(*data));
    memmove((char *)orig_sm,(char *)data,nn*sizeof(*data));
    memmove((char *)work,(char *)data,nn*sizeof(*data));

    /* Copy the bad pixel mask, so that the pre-existing bad pixels are 
       now flagged with 1. */

    bpmcopy = cpl_calloc(nn,sizeof(*bpmcopy));
    for (i = 0; i < nn; i++) 
        bpmcopy[i] = (bpm[i] ? 1 : 0);

    /* Do gentle smooth on the original data */

    if (niter > 1 && medfilt > 10) {
        bfilt_1d(orig_sm,bpmcopy,nx,ny,5,MEDIANCALC,axis);
        bfilt_1d(orig_sm,bpmcopy,nx,ny,3,MEANCALC,axis);
    }

    /* Now do an iteration loop */

    for (iter = 1; iter <= niter; iter++) {
        if (iter > 1)
            memmove((char *)data,(char *)orig,nn*sizeof(*data));

        /* Filter the original input data, using the latest interation
           on the pixel mask */

        if (! twod) 
            bfilt_1d(data,bpmcopy,nx,ny,medfilt,MEDIANCALC,axis);
        else
            bfilt_2d(data,bpmcopy,nx,ny,medfilt,MEDIANCALC);
        if (iter == niter) 
            break;

        /* Look at the difference between the smoothed map and the (possibly
           gently smoothed) original data */

        for (i = 0; i < nn; i++) 
            work[i] = orig_sm[i] - data[i];

        /* What is the median level and RMS of the residual map? We may need
           to iterate on this */

        casu_qmedsig(work,bpmcopy,nn,3.0,3,DATAMIN,DATAMAX,&medsky,&sigsky);
        rescale = 2.0;
        nter = 0;
        while (sigsky < 2.5 && nter < 16) {
            nter++;
            for (i = 0; i < nn; i++)
                work[i] *= rescale;
            casu_qmedsig(work,bpmcopy,nn,3.0,3,DATAMIN,DATAMAX,&medsky,
                         &sigsky);
        }
        if (nter > 0) {
            rescale = (float)pow(2.0,(double)nter);
            for (i = 0; i < nn; i++)
                work[i] /= rescale;
            medsky /= rescale;
            sigsky /= rescale;
        }
        lthr = -signeg*sigsky;
        hthr = sigpos*sigsky;

        /* Clip out discordant points */

        nmask = 0;
        for (i = 0; i < nn; i++) {
            if (bpmcopy[i] == 1)
                continue;
            diff = work[i] - medsky;
            if (diff > hthr || diff < lthr) {
                bpmcopy[i] = 2;
                nmask++;
            } else {
                bpmcopy[i] = 0;
            }
        }
    }
      
    /* Now do the linear filter */

    if (! twod) 
        bfilt_1d(data,bpm,nx,ny,linfilt,MEANCALC,axis);
    else
        bfilt_2d(data,bpm,nx,ny,linfilt,MEANCALC);
    
    /* Get the sky level if you want to keep it */

    if (! takeout_sky)
        casu_qmedsig(orig,bpmcopy,nn,3.0,3,DATAMIN,DATAMAX,&medsky,&sigsky);
    else 
        medsky = 0.0;

    /* Do we want a background map */

    if (wantback) {
        *backmap = cpl_malloc(nn*sizeof(**backmap));
        for (i = 0; i < nn; i++) 
            (*backmap)[i] = data[i];
    } else {
        *backmap = NULL;
    }

    /* How do we want to normalise? */

    if (inorm == 0) {
        for (i = 0; i < nn; i++) 
            data[i] = orig[i] - data[i] + medsky;
    } else {
        for (i = 0; i < nn; i++) 
            data[i] = orig[i]/max(1.0,data[i]);
    }

    /* Tidy and exit */

    freespace(buffer);
    freespace(bpmcopy);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        bfilt1d
    \par Purpose:
        Filter by doing a pair of 1d filters
    \par Description:
        The input image is smoothed using a pair of running one-dimensional
        filters
    \par Language:
        C
    \param data
        The input image data to smoothed
    \param bpm
        The bad pixel mask for the image
    \param nx
        The X dimension of the data
    \param ny
        The Y dimension of the data
    \param filt
        The filter box size in pixels
    \param stat
        The statistic to use. 1 == median, 2 = mean
    \param axis
        Which axis to do first? (1 => X, 2 => Y)
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void bfilt_1d(float *data, unsigned char *bpm, int nx, int ny, 
                     int filt, int stat, int axis) {

    float *dbuf;
    unsigned char *bbuf;
    int nbuf;

    /* Get some workspace */

    nbuf = max(nx,ny);
    dbuf = cpl_malloc(nbuf*sizeof(*dbuf));
    bbuf = cpl_malloc(nbuf*sizeof(*bbuf));

    /* Order the reset correction so that the first smoothing is done
       across the axis of the anomaly */

    if (axis == 1) {
        dorows_2(data,bpm,dbuf,bbuf,nx,ny,filt,stat);
        docols_2(data,bpm,dbuf,bbuf,nx,ny,filt,stat);
    } else {
        docols_2(data,bpm,dbuf,bbuf,nx,ny,filt,stat);
        dorows_2(data,bpm,dbuf,bbuf,nx,ny,filt,stat);
    }

    /* Ditch workspace */

    freespace(dbuf);
    freespace(bbuf);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        bfilt2d
    \par Purpose:
        Filter by doing a full 2d smoothing operation
    \par Description:
        The input image is smoothed using a full 2d smoothing algorithm
    \par Language:
        C
    \param data
        The input image data to smoothed
    \param bpmcopy
        The bad pixel mask for the image
    \param nx
        The X dimension of the data
    \param ny
        The Y dimension of the data
    \param filt
        The filter box size in pixels
    \param stat
        The statistic to use. 1 == median, 2 = mean
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void bfilt_2d(float *data, unsigned char *bpmcopy, int nx, int ny,
                     int filt, int stat) {
    float *dbuf,*outmap,value,*om;
    unsigned char *outbpm,*ob;
    int nbuf,j,i,nf2,nalloc,jj,ii,ind,ind1,j1old,j2old,i1old,i2old;

    /* Filter halfwidth */

    nf2 = filt/2;

    /* Get some workspace */

    nalloc = (2*filt+1)*(2*filt+1);
    dbuf = cpl_malloc(nalloc*sizeof(*dbuf));
    outmap = cpl_malloc(nx*ny*sizeof(*outmap));
    outbpm = cpl_malloc(nx*ny*sizeof(*outbpm));

    /* Loop for each input pixel */

    for (j = 0; j < ny; j++) {
        for (i = 0; i < nx; i++) {
            nbuf = 0;
            for (jj = j - nf2; jj <= j + nf2; jj++) {
                if (jj < 0 || jj >= ny)
                    continue;
                ind1 = jj*nx;
                for (ii = i - nf2; ii <= i + nf2; ii++) {
                    if (ii < 0 || ii >= nx) 
                        continue;
                    ind = ind1 + ii;
                    if (bpmcopy[ind])
                        continue;
                    dbuf[nbuf++] = data[ind];
                }
            }
            
            /* If we don't have enough, try and increase the window size.
               This will only affect the edges */

            if (nbuf < filt/4) {
                j1old = j - nf2;
                j2old = j + nf2;
                i1old = i - nf2;
                i2old = i + nf2;
                for (jj = j - filt; jj <= j + filt; jj++) {
                    if (jj < 0 || jj >= ny || (jj >= j1old && jj <= j2old))
                        continue;
                    ind1 = jj*nx;
                    for (ii = i - filt; ii <= i + filt; ii++) {
                        if (ii < 0 || ii >= nx || (ii >= i1old && ii <= i2old)) 
                            continue;
                        ind = ind1 + ii;
                        if (bpmcopy[ind])
                            continue;
                        dbuf[nbuf++] = data[ind];
                    }

                }
            }

            /* Right, assuming we have enough entries, then get a median */

            ind = j*nx + i;
            if (nbuf > filt/4) {
                if (stat == MEDIANCALC) 
                    value = casu_med(dbuf,NULL,(long)nbuf);
                else
                    value = casu_mean(dbuf,NULL,(long)nbuf);
                outmap[ind] = value;
                outbpm[ind] = 0;
            } else {
                outmap[ind] = -1000.0;
                outbpm[ind] = 1;
            }
        }
    }

    /* Right, fill in the holes and then transfer the filtered data to
       the input array */

    for (j = 0; j < ny; j++) {
        om = outmap + j*nx;
        ob = outbpm + j*nx;
        plugholes(om,ob,nx);
        for (i = 0; i < nx; i++) 
            data[j*nx+i] = om[i];
    }

    /* Tidy up and get out of here */

    freespace(outmap);
    freespace(outbpm);
    freespace(dbuf);
    return;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        docols_2
    \par Purpose:
        Do a 1d filter along the columns of an image
    \par Description:
        The input image is smoothed along its columns
    \par Language:
        C
    \param data
        The input image data to smoothed
    \param bpm
        The bad pixel mask for the image
    \param dbuf
        A workspace for the data that is a least as long as
        a column
    \param bbuf
        A workspace for the bpm that is as least as long as
        a column
    \param nx
        The X dimension of the data
    \param ny
        The Y dimension of the data
    \param filter
        The filter box size in pixels
    \param stat
        The statistic to use. 1 == median, 2 = mean
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void docols_2(float *data, unsigned char *bpm, float *dbuf, 
                     unsigned char *bbuf, int nx, int ny, int filter, 
                     int stat) {

    int j,k,indx,nn;
    unsigned char *goodval,*b;
    float *t;

    if (filter <= 0)
        return;

    goodval = cpl_malloc(ny*sizeof(*goodval));
    t = cpl_malloc(ny*sizeof(*t));
    b = cpl_malloc(ny*sizeof(*b));
    for (k = 0; k < nx; k++) {
        memset((char *)goodval,0,ny);
        nn = 0;
        for (j = 0; j < ny; j++) {
            indx = j*nx + k;
            if (bpm[indx] == 0) {
                dbuf[nn] = data[indx];
                bbuf[nn++] = 0;
            }
        } 
        dostat(dbuf,bbuf,goodval,nn,filter,stat);
        nn = 0;
        for (j = 0; j < ny; j++) {
            indx = j*nx + k;
            if (bpm[indx] == 0) {
                t[j] = dbuf[nn++];
                b[j] = 0;
            } else {
                t[j] = -999.0;
                b[j] = 1;
            }
        }
        plugholes(t,b,ny);
        nn = 0;
        for (j = 0; j < ny; j++) {
            indx = j*nx + k;
            data[indx] = t[j];
        }
    } 
    freespace(goodval);
    freespace(t);
    freespace(b);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        dorows_2
    \par Purpose:
        Do a 1d filter along the rows of an image
    \par Description:
        The input image is smoothed along its rows
    \par Language:
        C
    \param data
        The input image data to smoothed
    \param bpm
        The bad pixel mask for the image
    \param dbuf
        A workspace for the data that is a least as long as
        a row
    \param bbuf
        A workspace for the bpm that is as least as long as
        a row
    \param nx
        The X dimension of the data
    \param ny
        The Y dimension of the data
    \param filter
        The filter box size in pixels
    \param stat
        The statistic to use. 1 == median, 2 = mean
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void dorows_2(float *data, unsigned char *bpm, float *dbuf, 
                     unsigned char *bbuf, int nx, int ny, int filter, 
                     int stat) {

    int j,k,indx,nn;
    unsigned char *goodval,*b;
    float *t;

    if (filter <= 0)
        return;

    goodval = cpl_malloc(nx*sizeof(*goodval));
    t = cpl_malloc(nx*sizeof(*t));
    b = cpl_malloc(nx*sizeof(*b));
    for (k = 0; k < ny; k++) {
        memset((char *)goodval,0,nx);
        nn = 0;
        for (j = 0; j < nx; j++) {
            indx = k*nx + j;
            if (bpm[indx])
                continue;
            dbuf[nn] = data[indx];
            bbuf[nn++] = 0;
        }
        dostat(dbuf,bbuf,goodval,nn,filter,stat);
        nn = 0;
        for (j = 0; j < nx; j++) {
            indx = k*nx + j;
            if (bpm[indx] == 0) {
                t[j] = dbuf[nn++];
                b[j] = 0;
            } else {
                t[j] = -999.0;
                b[j] = 1;
            }
        }
        plugholes(t,b,nx);
        for (j = 0; j < nx; j++) {
            indx = k*nx + j;
            data[indx] = t[j];
        }
    }
    freespace(goodval);
    freespace(t);
    freespace(b);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        dostat
    \par Purpose:
        Do the sliding median or mean of a buffer
    \par Description:
        The input buffer is smoothed using a sliding mean or median. To try
        and speed things up the algorithm does not reload all of the
        values for each filter window position as that would involve
        recalculating and resorting values that have already been done.
    \par Language:
        C
    \param data
        The input image data to smoothed
    \param bpm
        The bad pixel mask for the image
    \param goodval
        An array marking where the good pixels are in the output array
    \param npts
        The number of pixels in the buffer
    \param nfilt
        The filter box size in pixels
    \param whichstat
        The statistic to use. 1 == median, 2 = mean
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void dostat(float *data, unsigned char *bpm, unsigned char *goodval,
                   int npts, int nfilt, int whichstat) {
    int nbuf,jl,jh,j,*ipoint,ifree,i;
    unsigned char *ybbuf,*barray,bval;
    nextlast nl;
    float *ybuf,*darray,val;
    
    /* Check to make sure there are some points in the array... */

    if (npts < nfilt || npts < 10)
        return;

    /* Check to make sure the filter size is odd */

    if ((nfilt/2)*2 == nfilt)
        nfilt++;

    /* Do the wrap around and load the data into an oversized array*/

    wraparound(data,bpm,npts,nfilt,whichstat,&ybuf,&ybbuf,&nbuf);

    /* Start doing the filtering...*/

    darray = cpl_malloc(nfilt*sizeof(*darray));
    barray = cpl_malloc(nfilt*sizeof(*barray));
    ipoint = cpl_malloc(nfilt*sizeof(*ipoint));
    memmove((char *)darray,(char *)ybuf,nfilt*sizeof(*ybuf));
    memmove((char *)barray,(char *)ybbuf,nfilt*sizeof(*ybbuf));
    for (j = 0; j < nfilt; j++)
        ipoint[j] = j;
    ifree = 0;
    medavg(darray,barray,ipoint,nfilt,whichstat,-1,&nl,&val,&bval);
    if (! bval) 
        data[0] = val;
    goodval[0] = bval;
    jl = nfilt;
    jh = nfilt + npts - 2;
    for (j = jl; j <= jh; j++) {
        for (i = 0; i < nfilt; i++) {
            if (ipoint[i] == 0) {
                ifree = i;
                ipoint[i] = nfilt - 1;
                nl.lastval = darray[ifree];
                nl.lastw = 0.0;
                nl.lastc = 0;
                if (barray[ifree] == 0) {
                    nl.lastw = 1.0;
                    nl.lastc = 1;
                }                
                darray[ifree] = ybuf[j];
                barray[ifree] = ybbuf[j];
                nl.nextval = darray[ifree];
                nl.nextw = 0.0;
                nl.nextc = 0;
                if (barray[ifree] == 0) {
                    nl.nextw = 1.0;
                    nl.nextc = 1;
                }                
            } else
                ipoint[i]--;
        }
        medavg(darray,barray,ipoint,nfilt,whichstat,ifree,&nl,&val,&bval);
        if (! bval) 
            data[j-jl+1] = val;
        goodval[j-jl+1] = bval;
    }

    /* Ditch workspace */

    freespace(darray);
    freespace(barray);
    freespace(ipoint);
    freespace(ybuf);
    freespace(ybbuf);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        wraparound
    \par Purpose:
        Extend the ends of a data buffer 
    \par Description:
        Extend the ends of a data buffer by doing a wrap around
        interpolation
    \par Language:
        C
    \param data
        The input data buffer
    \param bpm
        The input bad pixel mask buffer
    \param npts
        The number of pixels in the buffer
    \param nfilt
        The filter box size in pixels
    \param whichstat
        The statistic to use. 1 == median, 2 = mean
    \param ybuf
        The output extended data buffer
    \param ybbuf
        The output extended bad pixel mask buffer
    \param nbuf
        The size of the output buffers
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void wraparound(float *data, unsigned char *bpm, int npts, int nfilt, 
                       int whichstat, float **ybuf, unsigned char **ybbuf, 
                       int *nbuf) {

    float *darray,xmns,xmnf;
    int i1,ilow,i,*ipoint;
    unsigned char *barray,bxmns,bxmnf;
    nextlast nl;

    /* Do some padding at the edges */

    i1 = nfilt/2;
    ilow = max(3,nfilt/4);
    ilow = (ilow/2)*2 + 1;

    /* Get some workspace */

    darray = cpl_malloc(nfilt*sizeof(*darray));
    barray = cpl_malloc(nfilt*sizeof(*barray));
    ipoint = cpl_calloc(nfilt,sizeof(*ipoint));
    *nbuf = npts + 2*i1;
    *ybuf = cpl_malloc(*nbuf*sizeof(float));
    *ybbuf = cpl_malloc(*nbuf*sizeof(unsigned char));

    /* Do the wrap around.*/

    memmove((char *)darray,(char *)data,ilow*sizeof(*data));
    memmove((char *)barray,(char *)bpm,ilow*sizeof(*bpm));
    medavg(darray,barray,ipoint,ilow,whichstat,-1,&nl,&xmns,&bxmns);
    memmove((char *)darray,(char *)(data+npts-ilow),ilow*sizeof(*data));
    memmove((char *)barray,(char *)(bpm+npts-ilow),ilow*sizeof(*bpm));
    medavg(darray,barray,ipoint,ilow,whichstat,-1,&nl,&xmnf,&bxmnf);
    for (i = 0; i < i1; i++) {
        if (! bxmns) {
            (*ybuf)[i] = 2.0*xmns - data[i1+ilow-i-1];
            (*ybbuf)[i] = bpm[i1+ilow-i-1];
        } else {
            (*ybuf)[i] = data[i1+ilow-i-1];
            (*ybbuf)[i] = 1;
        }
        if (! bxmnf) {
            (*ybuf)[npts+i1+i] = 2.0*xmnf - data[npts-i-ilow-1];
            (*ybbuf)[npts+i1+i] = bpm[npts-i-ilow-1];
        } else {
            (*ybuf)[npts+i1+i] = data[npts-i-ilow-1];
            (*ybbuf)[npts+i1+i] = 1;
        }
    }

    /* Now place the full line into the buffer */

    memmove((char *)(*ybuf+i1),data,npts*sizeof(*data));
    memmove((char *)(*ybbuf+i1),bpm,npts*sizeof(*bpm));

    /* Free workspace */

    freespace(darray);
    freespace(barray);
    freespace(ipoint);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        medavg
    \par Purpose:
        Work out the median or average in a filter window
    \par Description:
        Work out the median or average in a filter window
    \par Language:
        C
    \param array
        The input data buffer
    \param bpm
        The input bad pixel mask buffer
    \param ipoint
        An integer array to point to the location of ordered
        values in the data array
    \param npix
        The number of pixels in the buffer
    \param whichstat
        The statistic to use. 1 == median, 2 = mean
    \param newl
        The position in the arrays of the newest pixel in the window.
    \param nl
        A structure with the running totals for the window
    \param outval
        The output data value
    \param outbp
        The output bad pixel value
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void medavg(float *array, unsigned char *bpm, int *ipoint, int npix, 
                   int whichstat, int newl, nextlast *nl, float *outval, 
                   unsigned char *outbp) { 
    float *buf = NULL;
    int m,i;

    /* If there is a new element and that new element is bad, then
       the buffer is already sorted from last time (just one element
       sorter */
    
    m = 0;
    if (whichstat == MEDIANCALC) {
        if (newl == -1) 
            sortm(array,bpm,ipoint,npix);
        else  
            quickie(array,bpm,ipoint,newl,npix);

        /* Get some workspace */

        buf = cpl_malloc(npix*sizeof(*buf));

        /* Now put everything that's good in the buffer */

        m = 0;
        for (i = 0; i < npix; i++) {
            if (bpm[i] == 0) {
                buf[m] = array[i];
                m++;
            }
        }
    } else if (whichstat == MEANCALC) {
        if (newl == -1) {
            nl->sum = 0.0;
            nl->sumw = 0.0;
            nl->naver = 0;
            for (i = 0; i < npix; i++) {
                if (bpm[i] == 0) {
                    nl->sum += array[i];
                    nl->sumw += 1.0;
                    nl->naver += 1;
                }
            }
            m = nl->naver;
        } else {
            nl->sum += (nl->nextw*nl->nextval - nl->lastw*nl->lastval);
            nl->sumw += (nl->nextw - nl->lastw);
            nl->naver += (nl->nextc - nl->lastc);
            m = nl->naver;
        }
    }
        
    /* If they were all bad, then send a null result back */

    if (m == 0) {
        *outval = 0.0;
        *outbp = 1;
        if (whichstat == MEDIANCALC)
            freespace(buf);

    /* Otherwise calculate the relevant stat */

    } else {
        if (whichstat == MEDIANCALC) {
            *outval = buf[m/2];
            freespace(buf);
        } else if (whichstat == MEANCALC)
            *outval = (nl->sum)/(nl->sumw);
        *outbp = 0;
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        quickie
    \par Purpose:
        Do a quick sort of a data buffer 
    \par Description:
        Does a quick sort of an already partially sorted data buffer. The
        newest pixel in the buffer is located and relocated to
        where it belongs in a fully sorted array
    \par Language:
        C
    \param array
        The input data buffer
    \param iarray
        The input bad pixel mask buffer
    \param iarray2
        An integer array to point to the location of ordered
        values in the data array
    \param lll
        The array location of the newest pixel
    \param narray
        The number of pixels in the array
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void quickie(float *array, unsigned char *iarray, int *iarray2, 
                    int lll, int narray) {

    float test;
    int i,j,npt,it2;
    unsigned char it;

    test = array[lll];
    it = iarray[lll];
    it2 = iarray2[lll];
    j = -1;
    for (i = 0; i < narray; i++) {
        if (i != lll && test <= array[i]) {
            j = i;
            break;
        }
    }
    if (j == -1) 
        j = narray;
    if (j - 1 == lll)
        return;

    if (j - lll < 0) {
        npt = lll - j;
        for (i = 0; i < npt; i++) {
            array[lll-i] = array[lll-i-1];
            iarray[lll-i] = iarray[lll-i-1];
            iarray2[lll-i] = iarray2[lll-i-1];
        }
        array[j] = test;
        iarray[j] = it;
        iarray2[j] = it2;
    } else {
        j--;
        npt = j - lll;
        if (npt != 0) {
            for (i = 0; i < npt; i++) {
                array[lll+i] = array[lll+i+1];
                iarray[lll+i] = iarray[lll+i+1];
                iarray2[lll+i] = iarray2[lll+i+1];
            }
        }
        array[j] = test;
        iarray[j] = it;
        iarray2[j] = it2;
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        sortm
    \par Purpose:
        Standard quicksort algorithm
    \par Description:
        Does a quicksort a data buffer and cosorts accompanying
        integer arrays.
    \par Language:
        C
    \param a1
        The input data buffer
    \param a2
        The accompanying unsigned char array
    \param a3
        The accompanying integer array
    \param n
        The number of pixels in the arrays
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
            
static void sortm(float *a1, unsigned char *a2, int *a3, int n) {
    int iii,ii,i,ifin,j,b3;
    unsigned char b2;
    float b1;

    iii = 4;
    while (iii < n)
        iii *= 2;
    iii = min(n,(3*iii)/4 - 1);

    while (iii > 1) {
        iii /= 2;
        ifin = n - iii;
        for (ii = 0; ii < ifin; ii++) {
            i = ii;
            j = i + iii;
            if (a1[i] > a1[j]) {
                b1 = a1[j];
                b2 = a2[j];
                b3 = a3[j];
                while (1) {
                    a1[j] = a1[i];
                    a2[j] = a2[i];
                    a3[j] = a3[i];
                    j = i;
                    i = i - iii;
                    if (i < 0 || a1[i] <= b1) 
                        break;
                }
                a1[j] = b1;
                a2[j] = b2;
                a3[j] = b3;
            }
        }
    }
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        plugholes
    \par Purpose:
        Plug holes in a data array that are flagged by a bpm
    \par Description:
        Plug holes in a data array that are flagged by a bpm. The bad pixels
        are replaced by a linear interpolation of good pixels in the vicinity
    \par Language:
        C
    \param data
        The input data buffer
    \param bpm
        The input bad pixel mask
    \param nx
        The number of pixels in the arrays
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void plugholes(float *data, unsigned char *bpm, int nx) {
    int i,ifirst,ilast,i1,i2,j;
    float nc,d1,d2,t1,t2,slope;

    /* First of all, find the first good value in the array */

    i = 0;
    while (i < nx && bpm[i] != 0)
        i++;
    ifirst = i;

    /* If all the values in the array are bad, then do nothing */

    if (ifirst == nx)
        return;

    /* Find the last good value in the array */

    i = nx - 1;
    while (i >= 0 && bpm[i] != 0) 
        i--;
    ilast = i;

    /* Right, now start from the first good value and fill in any holes in the
       middle part of the array */

    i = ifirst;
    while (i <= ilast) {
        if (bpm[i] == 0) {
            i++;
            continue;
        }
        i1 = i - 1;
        while (bpm[i] != 0) 
            i++;
        i2 = i;
        nc = (float)(i2 - i1 + 1);
        d1 = data[i1];
        d2 = data[i2];
        for (j = i1+1; j <= i2-1; j++) {
            t1 = 1.0 - (float)(j - i1)/nc;
            t2 = 1.0 - t1;
            data[j] = t1*d1 + t2*d2;
        }
    }

    /* Now the left bit... */

    if (ifirst > 0) {
        slope = data[ifirst+1] - data[ifirst];
        for (j = 0; j < ifirst; j++)
            data[j] = slope*(float)(j - ifirst) + data[ifirst];
    }

    /* Now the right bit... */

    if (ilast < nx - 1) {
        slope = data[ilast] - data[ilast-1];
        for (j = ilast; j < nx; j++) 
            data[j] = slope*(float)(j - ilast) + data[ilast];
    }
}

/**@}*/

/*

$Log: casu_nebuliser.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  2014/12/11 12:23:33  jim
new version

Revision 1.5  2014/05/13 10:26:52  jim
Fixed bug in call to medavg

Revision 1.4  2014/03/26 15:54:19  jim
Removed globals. Confidence is now floating point

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

Revision 1.2  2013-10-24 09:24:33  jim
fixed some docs

Revision 1.1  2013-09-30 18:09:43  jim
Initial entry


*/
