/**************************************************************

flat.c (C-Munipack project)
Flat correction
Copyright (C) 2003 David Motl, dmotl@volny.cz

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.

**************************************************************/

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

#include "comfun.h"
#include "console.h"
#include "robmean.h"
#include "ccdfile.h"
#include "image.h"
#include "cmpack_common.h"
#include "cmpack_flat.h"

/*******************   DATA TYPES   ***************************/

/* Flat correction context */
struct _CmpackFlatCorr
{
	int refcnt;						/**< Reference counter */
	CmpackConsole *con;				/**< Console */
	CmpackBorder border;			/**< Border size */
	CmpackImage *flat;				/**< Flat frame image data */
	double minvalue, maxvalue;		/**< Bad pixel value, overexposed value */
};

/*******************   LOCAL FUNCTIONS   *******************************/

/* Computes the output image from source image and flat image (out=mean*sci/flat) */
static void flat_flat(CmpackFlatCorr *lc, CmpackImage *image)
{
	int i, width, height, x, y, left, right, top, bottom;
	int underflow, overflow, divzero;
	double *sdata, *fdata, minvalue, maxvalue, value, flat;

	if (is_debug(lc->con)) {
		printpars(lc->con, "Image data format", 1, pixformat(cmpack_image_bitpix(image)));
		printpard(lc->con, "Bad pixel threshold", 1, lc->minvalue, 2); 
		printpard(lc->con, "Overexp. pixel threshold", 1, lc->maxvalue, 2); 
		printparvi(lc->con, "Border", 1, 4, (int*)(&lc->border));
	}

	/* flat subtraction and range checking */
	underflow = overflow = divzero = 0;
	width = cmpack_image_width(image);
	height = cmpack_image_height(image);
	left = lc->border.left;
	top = lc->border.top;
	right = width - lc->border.right;
	bottom = height - lc->border.bottom;
	minvalue = lc->minvalue;
	maxvalue = lc->maxvalue;
	sdata = (double*)cmpack_image_data(image);
	fdata = (double*)cmpack_image_data(lc->flat);
	for (y=0;y<height;y++) {
		for (x=0;x<width;x++) {
			i = x + y*width;
			if (x>=left && x<right && y>=top && y<bottom) {
				value = sdata[i];
				if (value>minvalue && value<maxvalue) {
					flat = fdata[i];
					if (flat<=0) {
						value = minvalue;
						divzero = 1;
					} else {
						value /= flat;
						if (value<minvalue) {
							value = minvalue;
							underflow = 1;
						}
						if (value>maxvalue) {
							value = maxvalue;
							overflow = 1;
						}
					}
				}
			} else {
				value = minvalue;
			}
			sdata[i] = value;
		}
	}

	/* Normal return */
	if (divzero) 
		printout(lc->con, 1, "Warning: Division by zero occurred during computation");
	if (overflow)
		printout(lc->con, 1, "Warning: An overflow has been occurred during computation");
	if (underflow)
		printout(lc->con, 1, "Warning: An underflow has been occurred during computation");
}

static void flat_clear(CmpackFlatCorr *ctx)
{
	if (ctx->flat) {
		cmpack_image_destroy(ctx->flat);
		ctx->flat = NULL;
	}
	if (ctx->con) {
		cmpack_con_destroy(ctx->con);
		ctx->con = NULL;
	}
}

/***********************   PUBLIC FUNCTIONS   **************************/

/* Initializes the context */
CmpackFlatCorr *cmpack_flat_init(void)
{
	CmpackFlatCorr *f = (CmpackFlatCorr*)cmpack_calloc(1, sizeof(CmpackFlatCorr));
	f->refcnt = 1;
	f->minvalue = 0;
	f->maxvalue = 65535.0;
	return f;
}

/* Increment the reference counter */
CmpackFlatCorr *cmpack_flat_reference(CmpackFlatCorr *ctx)
{
	ctx->refcnt++;
	return ctx;
}

/* Decrement reference counter / detroy the instance */
void cmpack_flat_destroy(CmpackFlatCorr *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			flat_clear(ctx);
			cmpack_free(ctx);
		}
	}
}

/* Attach console */
void cmpack_flat_set_console(CmpackFlatCorr *ctx, CmpackConsole *con)
{
	if (con!=ctx->con) {
		if (ctx->con) 
			cmpack_con_destroy(ctx->con);
		ctx->con = con;
		if (ctx->con) 
			cmpack_con_reference(ctx->con);
	}
}

/* Set image border */
void cmpack_flat_set_border(CmpackFlatCorr *lc, const CmpackBorder *border)
{
	if (border)
		lc->border = *border;
	else
		memset(&lc->border, 0, sizeof(CmpackBorder));
}

/* Set image border */
void cmpack_flat_get_border(CmpackFlatCorr *lc, CmpackBorder *border)
{
	*border = lc->border;
}

/* Set image border */
void cmpack_flat_set_thresholds(CmpackFlatCorr *lc, double minvalue, double maxvalue)
{
	lc->minvalue = minvalue;
	lc->maxvalue = maxvalue;
}

/* Set minimum pixel value */
void cmpack_flat_set_minvalue(CmpackFlatCorr *lc, double minvalue)
{
	lc->minvalue = minvalue;
}

/* Set minimum pixel value */
double cmpack_flat_get_minvalue(CmpackFlatCorr *lc)
{
	return lc->minvalue;
}

/* Set maximum pixel value */
void cmpack_flat_set_maxvalue(CmpackFlatCorr *lc, double maxvalue)
{
	lc->maxvalue = maxvalue;
}

/* Set minimum pixel value */
double cmpack_flat_get_maxvalue(CmpackFlatCorr *lc)
{
	return lc->maxvalue;
}

/* Loads the flat-frame */
int cmpack_flat_rflat(CmpackFlatCorr *lc, CmpackCcdFile *flat)
{
	int res, x, y, nx, ny, pixels;
	double *temp, *fdata, fmed, fsig, value, flat_maxvalue;
	CmpackBitpix bitpix;

	/* Clear previous flat frame */
	if (lc->flat) 
		cmpack_image_destroy(lc->flat);
	lc->flat = NULL;

    /* Check file name */
    if (!flat) {
        printout(lc->con, 0, "Invalid flat frame context");
 	    return CMPACK_ERR_INVALID_PAR;
    }

    /* Read and check dimensions */
	nx = cmpack_ccd_width(flat);
	ny = cmpack_ccd_height(flat);
	if (nx<=0 || nx>=65536 || ny<=0 || ny>=65536) {
		printout(lc->con, 1, "Invalid dimensions of the flat frame");
		return CMPACK_ERR_INVALID_SIZE;
	}

	/* Read and check image format */
	bitpix = cmpack_ccd_bitpix(flat);
	switch (bitpix) 
	{
	case CMPACK_BITPIX_SSHORT:
		flat_maxvalue = 0x7FFF;			/**< Signed short int (2 bytes) */
		break;
	case CMPACK_BITPIX_USHORT:				/**< Unsigned short int (2 bytes) */
		flat_maxvalue = 0xFFFF;
		break;
	case CMPACK_BITPIX_SLONG:				/**< Signed long int (4 bytes) */
		flat_maxvalue = 0x7FFFFFFFL;
		break;
	case CMPACK_BITPIX_ULONG:				/**< Unsigned long int (4 bytes) */
		flat_maxvalue = 0xFFFFFFFFUL;
		break;
	case CMPACK_BITPIX_FLOAT:				/**< Single precision FP (4 bytes) */
		flat_maxvalue = FLT_MAX;
		break;
	case CMPACK_BITPIX_DOUBLE:				/**< Double precision FP (8 bytes) */
		flat_maxvalue = DBL_MAX;
		break;
	default:
		printout(lc->con, 1, "Invalid image format of the flat frame");
		return CMPACK_ERR_INVALID_BITPIX;
	}

	/* Read flat frame data */
	res = cmpack_ccd_to_image(flat, CMPACK_BITPIX_DOUBLE, &lc->flat);
	if (res!=0) 
		return res;

    /* Median computation */
	temp = (double*)cmpack_malloc(nx*ny*sizeof(double));
	fdata = (double*)cmpack_image_data(lc->flat);
	pixels = 0;
	for (y=lc->border.top; y<ny-lc->border.bottom; y++) {
		for (x=lc->border.left; x<nx-lc->border.right; x++) {
			value = fdata[x+y*nx];
			if (value > 0 && value < flat_maxvalue)
				temp[pixels++] = value;
		}
	}
	if (pixels==0) {
		printout(lc->con, 0, "The flat frame has got too many bad pixels.");
		cmpack_image_destroy(lc->flat);
		lc->flat = NULL;
		cmpack_free(temp);
		return CMPACK_ERR_MANY_BAD_PXLS;
	}

	cmpack_robustmean(pixels, temp, &fmed, &fsig);
	cmpack_free(temp);
	if (fmed==0) {
		printout(lc->con, 0, "Mean value of the flat frame is zero (invalid flat frame)");
		cmpack_image_destroy(lc->flat);
		lc->flat = NULL;
    	return CMPACK_ERR_MEAN_ZERO;
	}

    /* Divide all flat values by median of the frame */
	for (y=lc->border.top; y<ny-lc->border.bottom; y++) {
		for (x=lc->border.left; x<nx-lc->border.right; x++) {
			value = fdata[x+y*nx];
			if (value > 0 && value < flat_maxvalue)
				fdata[x+y*nx] = value / fmed;
		}
	}

    /* Normal return */
	if (is_debug(lc->con)) {
		printout(lc->con, 1, "Flat correction frame:");
		printpari(lc->con, "Width", 1, cmpack_image_width(lc->flat));
		printpari(lc->con, "Height", 1, cmpack_image_height(lc->flat));
		printpard(lc->con, "Median", 1, fmed, 2);
		printpard(lc->con, "Std. dev.", 1, fsig, 2);
	}
    return 0;
}

/* Computes the output image from source image and flat image (out=mean*sci/flat) */
int cmpack_flat(CmpackFlatCorr *lc, CmpackCcdFile *file)
{
	int res, nx, ny;
	CmpackBitpix bitpix;
	CmpackImage *image;

	/* Check flat file */
	if (!lc->flat) {
		printout(lc->con, 0, "Missing flat frame");
		return CMPACK_ERR_NO_FLAT_FRAME;
	}

  	/* Check parameters */
	if (!file) {
		printout(lc->con, 0, "Invalid frame context");
		return CMPACK_ERR_INVALID_PAR;
	}

	/* Check image size */
	nx = cmpack_ccd_width(file);
	ny = cmpack_ccd_height(file);
	if (nx<=0 || ny<=0 || nx>=65536 || ny>=65536) {
		printout(lc->con, 0, "Invalid size of the source image");
		return CMPACK_ERR_INVALID_SIZE;
	}
	if (cmpack_image_width(lc->flat)!=nx || cmpack_image_height(lc->flat)!=ny) {
		printout(lc->con, 0, "The size of the flat frame is different from the source image");
		return CMPACK_ERR_DIFF_SIZE_FLAT;
	}
	bitpix = cmpack_ccd_bitpix(file);
	if (bitpix==CMPACK_BITPIX_UNKNOWN) {
		printout(lc->con, 0, "Unsupported data format of the source frame");
		return CMPACK_ERR_READ_ERROR;
	}

	/* Read exposure data */
	res = cmpack_ccd_to_image(file, CMPACK_BITPIX_DOUBLE, &image);
	if (res!=0) 
		return res;
	
	/* Flat correction */
	flat_flat(lc, image);

	/* Store image data */
	res = ccd_write_image(file, image);
	if (res==0)
		ccd_update_history(file, "Flat frame correction.");
	cmpack_image_destroy(image);

	return res;
}

/* Computes the output image from source image and flat image (out=mean*sci/flat) */
int cmpack_flat_ex(CmpackFlatCorr *lc, CmpackCcdFile *infile, CmpackCcdFile *outfile)
{
	int res, nx, ny;
	CmpackBitpix bitpix;
	CmpackImage *image;

	/* Check flat file */
	if (!lc->flat) {
		printout(lc->con, 0, "Missing flat frame");
		return CMPACK_ERR_NO_FLAT_FRAME;
	}

  	/* Check parameters */
	if (!infile) {
		printout(lc->con, 0, "Invalid input frame context");
		return CMPACK_ERR_INVALID_PAR;
	}
	if (!outfile) {
		printout(lc->con, 0, "Invalid output frame context");
		return CMPACK_ERR_INVALID_PAR;
	}

	/* Check image size */
	nx = cmpack_ccd_width(infile);
	ny = cmpack_ccd_height(infile);
	if (nx<=0 || nx>=65536 || ny<=0 || ny>=65536) {
		printout(lc->con, 0, "Invalid dimensions in the source frame");
		return CMPACK_ERR_INVALID_SIZE;
	}
	bitpix = cmpack_ccd_bitpix(infile);
	if (bitpix==CMPACK_BITPIX_UNKNOWN) {
		printout(lc->con, 0, "Invalid data format in the source frame");
		return CMPACK_ERR_READ_ERROR;
	}

	/* Read exposure data */
	res = cmpack_ccd_to_image(infile, CMPACK_BITPIX_DOUBLE, &image);
	if (res!=0) 
		return res;

	/* Flat correction */
	flat_flat(lc, image);

	/* Update output frame */
	res = ccd_prepare(outfile, nx, ny, bitpix);
	if (res==0) 
		res = ccd_copy_header(outfile, infile, lc->con, 0);
	if (res==0) 
		res = ccd_write_image(outfile, image);
	if (res==0)
		ccd_update_history(outfile, "Flat frame correction.");

	cmpack_image_destroy(image);
	return res;
}
