/* $Id: casu_backmap.c,v 1.4 2015/09/15 14:13:26 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/09/15 14:13:26 $
 * $Revision: 1.4 $
 * $Name:  $
 */

/* Includes */

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

#include <string.h>

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

/* Set up some global histogramming parameters */

#define MAXHIST     66536
#define MINHISTVAL  -1000
#define MAXHISTVAL  65535

static void medsig(int *hist, int ist, int itarg, float *med, float *sig);

/**
    \defgroup casu_modules casu_modules

    \brief
    These are the main CASU processing modules

    \author
    Jim Lewis, CASU
 
*/

/**@{*/

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Model background of an image

    \par Name:
        casu_backmap
    \par Purpose:
        Model the background of an input map
    \par Description:
        The large scale variation of a input map is modelled by robust
        median estimates in cells of a given size. This background map
        itself is then smoothed to ensure there are no sharp edges. Bad pixels
        are taken into account.
    \par Language:
        C
    \param map
        The input data image 
    \param bpm 
        The input bad pixel mask
    \param nx
        The X dimension of the input maps
    \param ny
        The Y dimension of the input maps
    \param nbsize
        The size of the cells for the smoothing boxes
    \param avback
        The average of the sky in the cells
    \param skymap
        An output sky map.
    \param status
        An input/output status that is the same as the returned values
    \retval CASU_OK
        if everything is ok
    \retval CASU_FATAL
        if input parameters are faulty or all pixels are bad in bpm
    \par QC headers:
        None
    \par DRS headers:
        None
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_backmap(float *map, cpl_binary *bpm, int nx, int ny, 
                        int nbsize, float *avback, float **skymap, 
                        int *status) {
    float fracx,fracy,skymed,sigma,skymedc,sigmac,fnbsize,dely,delx;
    float t1,t2,*bvals;
    int ifracx,ifracy,nbsizx,nbsizy,nbx,nby,npixstripe,l,i,ll;
    int isquare,ilev,j,iclip,mcpix,iloop,irej,nbsizo2,kk,k,iby,ibyp1,ibx,ibxp1;
    int *shist,**hist,*nnp,jj1,jj2,jj3,jj4,npts,ind;
    unsigned char *badmask;

    /* Do some preliminary checking...*/

    *skymap = NULL;
    *avback = 0.0;
    if (*status != CASU_OK)
        return(*status);
    if (map == NULL || bpm == NULL) 
        FATAL_ERROR
    npts = nx*ny;
    if (npts <= 0)
        FATAL_ERROR
    for (j = 0; j < npts; j++) 
        if (bpm[j] == 0)
            break;
    if (j == npts) 
        FATAL_ERROR
    if (nbsize <= 0)
        FATAL_ERROR

    /* Check to see if nbsize is close to exact divisor */

    fracx = ((float)nx)/((float)nbsize);
    fracy = ((float)ny)/((float)nbsize);
    ifracx = (int)(fracx + 0.1);
    ifracy = (int)(fracy + 0.1);
    nbsizx = nx/ifracx;
    nbsizy = ny/ifracy;
    nbsize = max(casu_nint(0.9*nbsize),min(nbsize,min(nbsizx,nbsizy)));
    nbsize = min(nx,min(ny,nbsize)); /* trap for small maps */

    /* Divide the map into partitions */

    nbx = nx/nbsize;
    nby = ny/nbsize;
    npixstripe = nbsize*nx;

    /* Get histogram workspace if you can */

    hist = cpl_malloc(nbx*sizeof(int *));
    for (l = 0; l < nbx; l++)
        hist[l] = cpl_malloc(MAXHIST*sizeof(int));

    /* Same for background values array and for a bad mask */

    bvals = cpl_malloc(nbx*nby*sizeof(float *));
    badmask = cpl_calloc(nbx*nby,sizeof(unsigned char *));

    /* Finally a counter array */

    nnp = cpl_malloc(nbx*sizeof(int));

    /* Loop for each row of background squares. Start by initialising
       the accumulators and histograms */

    for (l = 0; l < nby; l++) {
        memset((char *)nnp,0,nbx*sizeof(*nnp));
        for (i = 0; i < nbx; i++)
            memset((char *)hist[i],0,MAXHIST*sizeof(int));

        /* Skim through the data in this stripe. Find out which square each
           belongs to and add it it to the relevant histogram */

        ll = l*npixstripe;
        for (i = 0; i < npixstripe; i++) {
            ind = ll + i;
            if (ind >= npts)
                break;
            if (bpm[ind] == 0)   {
                isquare = (int)((float)(i % nx)/(float)nbsize);
                isquare = min(nbx-1,max(0,isquare));
                ilev = min(MAXHISTVAL,max(MINHISTVAL,casu_nint(map[ind])));
                hist[isquare][ilev-MINHISTVAL] += 1;
                nnp[isquare] += 1;
            }
        }

        /* Only do background estimation if enough pixels */

        for (j = 0; j < nbx; j++) {
            jj1 = l*nbx + j;
            if (nnp[j] > 0.25*nbsize*nbsize) {
                shist = hist[j];
                medsig(shist,MINHISTVAL-1,nnp[j],&skymed,&sigma);

                /* Do an iterative 3-sigma upper clip to give a more robust
                   estimator */

                iclip = MAXHISTVAL;
                mcpix = nnp[j];
                skymedc = skymed;
                sigmac = sigma;
                for (iloop = 0; iloop < 3; iloop++) {
                    irej = 0;
                    for (i = casu_nint(skymedc+3.0*sigmac); i <= iclip; i++)
                        irej += shist[i-MINHISTVAL];
                    if (irej == 0)
                        break;
                    iclip = casu_nint(skymedc+3.0*sigmac) - 1;
                    mcpix = mcpix - irej;
                    medsig(shist,MINHISTVAL-1,mcpix,&skymedc,
                           &sigmac);
                }
                bvals[jj1] = skymedc;
                badmask[jj1] = 0;
            } else {
                bvals[jj1] = -1000.0;
                badmask[jj1] = 1;
            }
        }

    }

    /* Filter raw background values */

    casu_bfilt(bvals,badmask,nbx,nby,3,MEDIANCALC,1);

    /* Compute average background level */

    *avback = casu_med(bvals,badmask,(long)(nbx*nby));
    freespace(badmask);

    /* OK now create a sky map */
    
    *skymap = cpl_malloc(nx*ny*sizeof(float));
    nbsizo2 = nbsize/2;
    fnbsize = 1.0/((float)nbsize);
    for (k = 0; k < ny; k++) {
        kk = k*nx;

        /* Nearest background pixel vertically */

        iby = (k + 1 + nbsizo2)/nbsize;
        ibyp1 = iby + 1;
        iby = min(nby,max(1,iby));
        ibyp1 = min(nby,ibyp1);
        dely = (k + 1 - nbsize*iby + nbsizo2)*fnbsize;

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

            /* nearest background pixel across */

            ibx = (j + 1 + nbsizo2)/nbsize;
            ibxp1 = ibx + 1;
            ibx = min(nbx,max(1,ibx));
            ibxp1 = min(nbx,ibxp1);
            delx = (j + 1 - nbsize*ibx + nbsizo2)*fnbsize;
            jj1 = (iby-1)*nbx + ibx - 1;
            jj2 = (ibyp1-1)*nbx + ibx - 1;
            jj3 = (iby-1)*nbx + ibxp1 - 1;
            jj4 = (ibyp1-1)*nbx + ibxp1 - 1;

            /* bilinear interpolation to find background */

            t1 = (1.0 - dely)*bvals[jj1] + dely*bvals[jj2];
            t2 = (1.0 - dely)*bvals[jj3] + dely*bvals[jj4];
            (*skymap)[kk+j] = (1.0 - delx)*t1 + delx*t2;
        }
    }

    /* Free some workspace */

    for (l = 0; l < nbx; l++)
        freespace(hist[l]);
    freespace(hist);
    freespace(bvals);
    freespace(nnp);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        medsig
    \par Purpose:
        Work out an estimate of the median and sigma of a distribution
    \par Description:
        Work out an estimate of the median and sigma of a distribution. The
        median is found by linearly interpolating the distribution bins 
        on either side of the true median. The sigma is defined as 1.48 times
        the distance between the median and the third quartile.
    \par Language:
        C
    \param hist
        The input data histogramme
    \param ist
        The location in the histogramme where we are allowed to start
        looking. This allows for clipping at the low end.
    \param itarg
        The number of pixels in the histogramme that we are allowed to
        take into account. Since we start at a bin defined by 'ist', then
        lowering 'itarg' is in effect an upper clip.
    \param med
        The output median
    \param sig
        The output sigma
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void medsig(int *hist, int ist, int itarg, float *med, float *sig) {
    int isum, medata,indx;
    float ffrac,sigmed;

    /* The median. We start in the lowest allowable bin defined by 'ist'. 
       We then continue summing up until we have reached just over 
       half the allowed contributions defined by 'itarg'. */

    isum = 0;
    medata = ist;
    indx = medata - MINHISTVAL;
    while (isum <= (itarg+1)/2 && indx < MAXHIST - 1) {
        medata++;
        indx = medata - MINHISTVAL;
        isum += hist[indx];
    }
    ffrac = (float)(isum - (itarg+1)/2)/(float)hist[medata-MINHISTVAL];
    *med = (float)medata - ffrac + 0.5;

    /* The sigma. We start in the lowest allowable bin defined by 'ist'. 
       We then continue summing up until we have reached just over 
       3/4 of the allowed contributions defined by 'itarg'.  The sigma
       is defined as 1.48* the distance from this third quartile to the
       median point. A minimal value is also included. */

    isum = 0;
    medata = ist;
    indx = medata - MINHISTVAL;
    while (isum <= (itarg+3)/4 && indx < MAXHIST - 1) {
        medata++;
        indx = medata - MINHISTVAL;
        isum += hist[indx];
    }
    ffrac = (float)(isum - (itarg+3)/4)/(float)hist[medata-MINHISTVAL];
    sigmed = (float)medata - ffrac + 0.5;
    *sig = 1.48*(*med - sigmed);
    *sig = max(2.5,*sig);
}


/**@}*/

/*

$Log: casu_backmap.c,v $
Revision 1.4  2015/09/15 14:13:26  jim
Fixed problem with illegal memory read

Revision 1.3  2015/09/11 09:27:53  jim
Fixed some problems with the docs

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.6  2015/03/12 09:16:51  jim
Modified to remove some compiler moans

Revision 1.5  2015/01/29 11:45:43  jim
modified comments

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

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

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

Revision 1.1  2014/03/26 15:26:28  jim
New entry


*/
