/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/fits.cpp
 * Purpose:  Basic Fits class methods
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 13.08.1999: file created
 * 05.01.2000: subarray methods moved to fits_range.cpp
 ******************************************************************/
 
#include <algorithm>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "fits.h"
#include "math_utils.h"
#include "platform.h"
#include "fitting.h"

#include <gsl/gsl_math.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>

#ifdef WIN
#pragma warning (disable: 4018) // disable warning for comparing signed and unsigned
#endif /* WIN */

#ifndef DPQT
void dpProgress(const int progress, const char *message) {
}
#endif /* !DPQT */

//char *dpFitsExceptionReasons[] = {
//	"Unknown",
//	"Could not allocate memory",
//	"Arrays do not match",
//	"Could not open file for reading",
//	"Could not open file for writing"
//};

/*
 * for debugging purposes: a counter of how many Fits are allocated
 */

long numberOfFits = 0;
int FitsInterrupt = 0;
bool FitsInfo = 0;

void getFitsFile(char *msg, Fits & result) {
	bool read = FALSE;
	char fname[256];
	
	while (!read) {
		dp_output("%s", msg);
		scanf("%s", fname);
		if ((strcmp(fname, "none") == 0) || (strcmp(fname, "NONE") == 0)) {
			result.create(0, 0);
			return;
		}
		read = result.ReadFITS(fname);
	}
}

float *floatdata(const Fits & a) {
	float *rv = NULL;
    dpint64 n;

	if (a.membits == R4) return a.r4data;
	if ((rv = (float *)malloc(a.Nelements() * sizeof(float))) == NULL) return NULL;
	for (n = 0; n < a.Nelements(); n++) rv[n] = (float)a.ValueAt(n);
	return rv;
}

double *doubledata(const Fits & a) {
    double *rv = NULL;
    dpint64 n;

    if (a.membits == R8) return a.r8data;
    if ((rv = (double *)malloc(a.Nelements() * sizeof(double))) == NULL) return NULL;
    for (n = 0; n < a.Nelements(); n++) rv[n] = (float)a.ValueAt(n);
    return rv;
}

/*! /var
 * Verbosity; default to shut up
 */

int FitsVerbose = 0;

/*
 * Replacement value for division by zero
 */
 
float FitsInfinity = 0.0;

void FitsIndexRef::operator=(const double &d) {
	parent->setValue(d, index.x()+1, index.y()+1, index.z()+1);
}

FitsIndexRef::operator double() {
	return parent->ValueAt(parent->C_I(index.x(), index.y(), index.z()));
}

FitsRangeRef::operator Fits() {
	Fits rv;

	parent->extractRange(rv, range.x1()+1, range.x2()+1, range.y1()+1, range.y2()+1, range.z1()+1, range.z2()+1);

	return rv;
}

void FitsRangeRef::operator=(Fits &f) {
	parent->setRange(f, range.x1()+1, range.x2()+1, range.y1()+1, range.y2()+1, range.z1()+1, range.z2()+1);
}

void FitsRangeRef::operator=(const double &d) {
	parent->setRange(d, range.x1()+1, range.x2()+1, range.y1()+1, range.y2()+1, range.z1()+1, range.z2()+1);
}

/*!
Constructor; we only supply one since constructors don't allow for
             reporting errors
*/

Fits::Fits() {
	initializeMemory();
	InitHeader();
	strcpy(FileName, "");
    extensionNumber = -1;
    columnNumber = -1;
	numberOfFits++;
//    printf("FITS constructor: Number of FITS: %i\n", numberOfFits);
}

/*!
Constructor; We need to supply this one in order to be able to use
             temporary classes
*/

Fits::Fits(const Fits & a) {
    dp_output("WARNING --- Dangerous constructor for class Fits\n");
	initializeMemory();
	strcpy(FileName, "");
    extensionNumber = -1;
    columnNumber = -1;
	InitHeader();
	copy(a);
	numberOfFits++;
//    printf("FITS constructor: Number of FITS: %i\n", numberOfFits);
}

/*!
Destructor
*/

Fits::~Fits()
{
    free(header); header = NULL;
    free(dataptr); dataptr = NULL;
    free(FileName); FileName = NULL;
    free(columnName); columnName = NULL;

	numberOfFits--;

//    printf("FITS destructor: Number of FITS: %i\n", numberOfFits);
}

/*!
Create a Fits image (2-dimensional).
*/
 
bool Fits::create(dpint64 x, dpint64 y, FitsBitpix t)
{
    dpint64 n;

    naxis[0] = (dpint64)(y > 1 ? 2 : 1);
    naxis[1] = (dpint64)x;
    naxis[2] = (dpint64)(y > 1 ? y : 1);
    naxis[3] = (dpint64)1;
    membits = t;
    extensionType = IMAGE;
	n_elements = naxis[1] * naxis[2] * naxis[3];
	bytesPerPixel = abs(membits) / 8;

//	crot = 0.0;
	strcpy(crtype, "");
	
	bscale = 1.0;
	bzero = 0.0;
	if (!allocateMemory()) return FALSE;
	
	switch (t) {
		case I1:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) i1data[n] = 0;
			break;
		case I2:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) i2data[n] = 0;
			break;
		case I4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) i4data[n] = 0;
			break;
        case I8:
#pragma omp parallel for
            for (n = 0; n < n_elements; n++) i8data[n] = 0;
            break;
        case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = 0.0;
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = 0.0;
			break;
		case C16:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) cdata[n].r = cdata[n].i = 0.0;
			break;
		default: break;
	}
	free(header);
	InitHeader();
	updateHeader();

	return TRUE;
}

/*!
Create a Fits cube (3-dimensional).
*/
 
bool Fits::create(dpint64 x, dpint64 y, dpint64 z, FitsBitpix t)
{
    dpint64 n;

	naxis[0] = (z > 1 ? 3 : y > 1 ? 2 : 1);
	naxis[1] = x;
	naxis[2] = (y > 1 ? y : 1);
	naxis[3] = (z > 1 ? z : 1);
	n_elements = naxis[1] * naxis[2] * naxis[3];
	membits = t;
    extensionType = IMAGE;
	bytesPerPixel = abs(membits) / 8;
//	crot = 0.0;
	strcpy(crtype, "");
	
	if (!allocateMemory()) return FALSE;

	switch (t) {
		case I1:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) i1data[n] = 0;
			break;
		case I2:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) i2data[n] = 0;
			break;
		case I4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) i4data[n] = 0;
			break;
        case I8:
#pragma omp parallel for
            for (n = 0; n < n_elements; n++) i8data[n] = 0;
            break;
        case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = 0.0;
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = 0.0;
			break;
		case C16:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) cdata[n].r = cdata[n].i = 0.0;
			break;
		default: break;
	}
	free(header);
	InitHeader();
	updateHeader();

	return TRUE;
}

/*!
Create a Fits image (2-dimensional). Values are NOT initialized.
*/
 
bool Fits::recreate(dpint64 x, dpint64 y)
{
    strcpy(FileName, "");
    extensionNumber = -1;
    columnNumber = -1;
    naxis[0] = (y > 1 ? 2 : 1);
    naxis[1] = x;
    naxis[2] = (y > 1 ? y : 1);
    naxis[3] = 1;
    n_elements = naxis[1] * naxis[2] * naxis[3];
    if (!allocateMemory()) return FALSE;
	
    return TRUE;
}

/*!
Create a Fits cube (3-dimensional). Values are NOT initialized.
*/
 
bool Fits::recreate(dpint64 x, dpint64 y, dpint64 z)
{
    strcpy(FileName, "");
    extensionNumber = -1;
    columnNumber = -1;
	naxis[0] = (z > 1 ? 3 : y > 1 ? 2 : 1);
	naxis[1] = x;
	naxis[2] = (y > 1 ? y : 1);
	naxis[3] = (z > 1 ? z : 1);
	n_elements = naxis[1] * naxis[2] * naxis[3];
	if (!allocateMemory()) return FALSE;

	return TRUE;
}

/*!
Copies a Fits.
*/

bool Fits::copy(const Fits & a)
{
    strncpy(FileName, a.FileName, 255);
    extensionNumber = a.extensionNumber;
    columnNumber = a.columnNumber;
    naxis[0] = a.naxis[0];
    naxis[1] = a.naxis[1];
    naxis[2] = a.naxis[2];
    naxis[3] = a.naxis[3];
    n_elements = a.n_elements;
    membits = a.membits;
    extensionType = a.extensionType;
    bytesPerPixel = a.bytesPerPixel;
    if (!allocateMemory()) return FALSE;

//    crot = a.crot;
    strncpy(crtype, a.crtype, 9);
    strncpy(columnName, a.columnName, 72);

    bscale = a.bscale;
    bzero = a.bzero;
    memcpy(dataptr, a.dataptr, n_elements * bytesPerPixel);
    setCasts();
    CopyHeader(a);
    return TRUE;
}

/*!
Copy the value at array index source to array index dest.
*/
void Fits::copyIndex(dpint64 dest, dpint64 source) {
	switch (membits) {
		case I1: i1data[dest] = i1data[source]; break;
		case I2: i2data[dest] = i2data[source]; break;
		case I4: i4data[dest] = i4data[source]; break;
        case I8: i8data[dest] = i8data[source]; break;
        case R4: r4data[dest] = r4data[source]; break;
		case R8: r8data[dest] = r8data[source]; break;
		case C16: cdata[dest].r = cdata[source].r; cdata[dest].i = cdata[source].i; break;
		default: break;
	}
}

/*!
Swap the values at array index source and array index dest.
*/
void Fits::swapIndex(dpint64 dest, dpint64 source) {
	switch (membits) {
		case I1: {
			unsigned char tmp;
			tmp = i1data[dest];
			i1data[dest] = i1data[source];
			i1data[source] = tmp;
		}
			break;
		case I2: {
			short tmp;
			tmp = i2data[dest];
			i2data[dest] = i2data[source];
			i2data[source] = tmp;
		}
			break;
		case I4: {
			long tmp;
			tmp = i4data[dest];
			i4data[dest] = i4data[source];
			i4data[source] = tmp;
		}
			break;
        case I8: {
            long long tmp;
            tmp = i8data[dest];
            i8data[dest] = i8data[source];
            i8data[source] = tmp;
        }
            break;
        case R4: {
			float tmp;
			tmp = r4data[dest];
			r4data[dest] = r4data[source];
			r4data[source] = tmp;
		}
			break;
		case R8: {
			double tmp;
			tmp = r8data[dest];
			r8data[dest] = r8data[source];
			r8data[source] = tmp;
		}
			break;
		case C16: {
			double tmpr, tmpi;
			tmpr = cdata[dest].r;
			tmpi = cdata[dest].i;
			cdata[dest].r = cdata[source].r;
			cdata[dest].i = cdata[source].i;
			cdata[source].r = tmpr;
			cdata[source].i = tmpi;
		}
			break;
		default: break;
	}
}

/*!
Set the value of the element at location x, y, z (fortran-notation)
*/

bool Fits::setValue(double v, int x, int y, int z) {
    dpint64 n = F_I(x, y, z);

	if (n > n_elements) return FALSE;
	switch (membits) {
		case I1: i1data[n] = (unsigned char)((v - bzero) / bscale); break;
		case I2: i2data[n] = (short)((v - bzero) / bscale); break;
		case I4: i4data[n] = (int)((v - bzero) / bscale); break;
        case I8: i8data[n] = (long long)((v - bzero) / bscale); break;
        case R4: r4data[n] = (float)v; break;
		case R8: r8data[n] = v; break;
		case C16: cdata[n].r = v; cdata[n].i = 0.0; break;
		default: break;
	}

	return TRUE;
}

/*!
Normalize array, i.e. all values will be scaled to lie between 0 and 1.
*/

bool Fits::norm( void )
{
    dpint64 i;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4: {
			double xmax, xmin, xrange;

			get_minmax(&xmin, &xmax);
			xrange = xmax - xmin;
			if (xrange <= EPS) {
				for (i = 0; i < n_elements; i++) r4data[i] = 1.0;
			} else {
				for (i = 0; i < n_elements; i++) r4data[i] = (r4data[i] - xmin) / xrange;
			}
		}
			break;
		case R8: {
			double xmax, xmin, xrange;

			get_minmax(&xmin, &xmax);
			xrange = xmax - xmin;
			if (xrange <= EPS) {
				for (i = 0; i < n_elements; i++) r8data[i] = 1.0;
			} else {
				for (i = 0; i < n_elements; i++) r8data[i] = (r8data[i] - xmin) / xrange;
			}
		}
			break;
		case C16: {
			double r;

			for (i = 0; i < n_elements; i++) {
				r = Cabs(cdata[i]);
				if (r <= EPS) {
					cdata[i] = Complex(0.0, 0.0);
				} else {
					cdata[i] = RCmul(1.0 / r, cdata[i]);
				}
			}
		}
			break;
		default: break;
	}

	return TRUE;
}

/*!
Normalize array to unsigned bytes, i.e. all values will be scaled to lie between 0 and 255.
The result will be stored in the array result. If min == max, minmax will be calculated.
method can have 3 values:

method == 0 (default): linear scaling
method == 1          : logarithmic scaling
method == 2          : square root scaling
*/

bool Fits::normub(Fits &result, double min, double max, int method) {
    dpint64 i;
	double range;
	double value;
	
	if (min >= max) get_minmax(&min, &max);
	range = max - min;
	result.create(Naxis(1), Naxis(2), Naxis(3), I1);
	range = 255. / range;

	switch (method) {
		case 0: // linear scaling
			for (i = 0; i < Nelements(); i++) {
				value = ValueAt(i);
				if (value > max) result.i1data[i] = 255;
				else if (value < min) result.i1data[i] = 0;
				else result.i1data[i] = (unsigned char)((value - min) * range);
			}
			break;
		case 1: { // log scaling
			double lmin, lmax;
			double fact = 1.0;
			int i = 0;
			while (((max - min) * fact < 1000.) && (i++ < 100)) fact *= 10.;
			lmin = 0.0 * fact; //log10(1.0);
			lmax = log10((max - min) * fact + 1.0);
			range = 255. / (lmax - lmin);
			for (i = 0; i < Nelements(); i++) {
				value = ValueAt(i);
				if (value > max) value = max;
				else if (value < min) value = min;
				value = (value - min) * fact + 1.0;
				result.i1data[i] = (unsigned char)(log10(value) * range);
			}
				break;
			}
		case 2: { // sqrt scaling
			double lmin, lmax;
			lmin = 0.0; //sqrt(0.0);
			lmax = sqrt(max - min);
			range = 255. / (lmax - lmin);
			for (i = 0; i < Nelements(); i++) {
				value = ValueAt(i);
				if (value > max) value = max;
				else if (value < min) value = min;
				value = value - min;
				result.i1data[i] = (unsigned char)(sqrt(value) * range);
			}
				break;
			}
		default:
			break;
	}
	return TRUE;
}

/*!
Normalize: if nfact is 1, the average value will be 1, else the total value will be 1.
*/

bool Fits::norf(int nfact)
{
	double flux = get_flux();
	
	if (nfact == 1) flux /= (double)(n_elements);
	
	if (fabs(flux) > EPS) div(flux);

	return TRUE;
}

/*!
Clip: All values below lo will be set to lo, all values above hi will be set to hi.

If hi < lo, only values lower than lo will be clipped.
*/

bool Fits::clip(double lo, double hi)
{
    dpint64 i;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (lo < hi) switch (membits) {
		case R4:
			for (i = 0; i < n_elements; i++) {
				if (r4data[i] > hi) r4data[i] = hi;
				else if (r4data[i] < lo) r4data[i] = lo;
			}
			break;
		case R8:
			for (i = 0; i < n_elements; i++) {
				if (r8data[i] > hi) r8data[i] = hi;
				else if (r8data[i] < lo) r8data[i] = lo;
			}
			break;
		default: break;
	} else if (hi < lo) switch (membits) {
		case R4:
			for (i = 0; i < n_elements; i++) {
				if (r4data[i] < lo) r4data[i] = lo;
			}
			break;
		case R8:
			for (i = 0; i < n_elements; i++) {
                if (r8data[i] < lo) r8data[i] = lo;
			}
			break;
		default: break;
	}

	return TRUE;
}

/*!
Inclusive clipping: All values between lo and hi will be set to value.
*/

bool Fits::inclip(double lo, double hi, double value)
{
    dpint64 i;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (lo < hi) switch(membits) {
		case R4:
			for (i = 0; i < n_elements; i++) {
				if ((r4data[i] <= hi) && (r4data[i] >= lo)) {
					r4data[i] = value;
				}
			}
			break;
		case R8:
			for (i = 0; i < n_elements; i++) {
				if ((r8data[i] <= hi) && (r8data[i] >= lo)) {
					r8data[i] = value;
				}
			}
			break;
		default: break;
	} else if (hi < lo) switch(membits) {
		case R4:
			for (i = 0; i < n_elements; i++) {
				if ((r4data[i] >= hi) && (r4data[i] <= lo)) {
					r4data[i] = value;
				}
			}
			break;
		case R8:
			for (i = 0; i < n_elements; i++) {
				if ((r8data[i] >= hi) && (r8data[i] <= lo)) {
					r8data[i] = value;
				}
			}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Return limits for which the array is non-zero.
*/

void Fits::limits(long *xlow, long *xhigh, long *ylow, long *yhigh, long *zlow, long *zhigh) {
	int x, y, z;
    dpint64 n;
	long x1, x2, y1, y2, z1, z2;
	double value;

	x1 = naxis[1];
	y1 = naxis[2];
	z1 = naxis[3];
	x2 = 1;
	y2 = 1;
	z2 = 1;

	n = 0;
	for (z = 1; z <= naxis[3]; z++) {
		for (y = 1; y <= naxis[2]; y++) {
			for (x = 1; x <= naxis[1]; x++) {
				value = ValueAt(n);
				if (value != 0.0) {
					if (x < x1) x1 = x;
					if (x > x2) x2 = x;
					if (y < y1) y1 = y;
					if (y > y2) y2 = y;
					if (z < z1) z1 = z;
					if (z > z2) z2 = z;
				}
				n++;
			}
		}
	}
	if (x1 > x2) {
		x1 = 1;
		x2 = naxis[1];
	}
	if (y1 > y2) {
		y1 = 1;
		y2 = naxis[2];
	}
	if (z1 > z2) {
		z1 = 1;
		z2 = naxis[3];
	}
	if (xlow != NULL) *xlow = x1;
	if (xhigh != NULL) *xhigh = x2;
	if (ylow != NULL) *ylow = y1;
	if (yhigh != NULL) *yhigh = y2;
	if (zlow != NULL) *zlow = z1;
	if (zhigh != NULL) *zhigh = z2;
}

/*!
Return pixel coordinates of brightest pixel around (x, y) within a search box of radius r.
*/
void Fits::max_pixel(int *x, int *y, int r) {
	int x1, x2, y1, y2, i, j;
	double max;

	x1 = *x - r;
	x2 = *x + r;
	y1 = *y - r;
	y2 = *y + r;
	if (x1 < 1) x1 = 1;
	if (x2 > naxis[1]) x2 = naxis[1];
	if (y1 < 1) y1 = 1;
	if (y2 > naxis[2]) y2 = naxis[2];

	max = ValueAt(F_I(*x, *y));
	for (i = x1; i <= x2; i++) {
		for (j = y1; j <= y2; j++) {
			if (ValueAt(F_I(i, j)) > max) {
				*x = i;
				*y = j;
				max = ValueAt(F_I(i, j));
			}
		}
	}
}

/*!
Return the index of the pixel with the highest value (fortran-notation).
*/

double Fits::max_pos(int *x, int *y, int *z)
{
	long i, j, k, l, zz;
	double max = ValueAt(0);
	double value;
	
	*x = *y = zz = 1;
	l = naxis[3];
	if ((l < 1) || (z == NULL)) l = 1;
	if (!gsl_finite(max)) max = 0.0;

	for (i = 1; i <= naxis[1]; i++) {
		for (j = 1; j <= naxis[2]; j++) {
			for (k = 1; k <= l; k++) {
				value = ValueAt(F_I(i, j, k));
				if (gsl_finite(value)) {
					if (value > max) {
						max = value;
						*x = i;
						*y = j;
						zz = k;
					}
				}
			}
		}
	}
	if (z != NULL) *z = zz;
	return max;
}

/*!
Return the index of the pixel with the highest value within a mask (fortran-notation).
*/

void Fits::max_pos_in_mask(int *x, int *y, const Fits &mask)
{
	long i, j;
	bool first = TRUE;
	double mvalue, dvalue, max = ValueAt(0);
	
	*x = *y = 1;
	for (i = 1; i <= naxis[1]; i++) {
		for (j = 1; j <= naxis[2]; j++) {
			mvalue = mask.ValueAt(mask.F_I(i, j));
			if (mvalue != 0.0) {
				dvalue = ValueAt(F_I(i, j)) * mvalue;
				if (first) {
					max = dvalue;
					first = FALSE;
					*x = i;
					*y = j;
				} else if (dvalue > max) {
					max = dvalue;
					*x = i;
					*y = j;
				}
			}
		}
	}
}

/*!
Return the centroid of the array (fortran-notation).
*/

void Fits::centroid(double *x, double *y, double *z)
{
	long i, j, k, l;
	double zz, v, sum = 0.0;
	
	*x = *y = zz = 0.0;
	l = naxis[3];
	if ((l < 1) || (z == NULL)) l = 1;
	for (i = 1; i <= naxis[1]; i++) {
		for (j = 1; j <= naxis[2]; j++) {
			for (k = 1; k <= l; k++) {
				v = ValueAt(F_I(i, j, k));
				if (v > 0.0) {
					*x += v * i;
					*y += v * j;
					zz += v * k;
					sum += v;
				}
			}
		}
	}
	if (sum == 0.0) sum = 1.0;
	*x /= sum;
	*y /= sum;
	if (z != NULL) *z = zz / sum;
}

/*!
Return an array of centroids around given pixel coordinates.
*/
bool Fits::centroids(Fits &result, Fits &positions, int radius) {
    Fits c1, c2;
    int n, x, y, x1, y1, d;
    double xcen, ycen;

    if (positions.Naxis(1) != 2) return FALSE;
    if (positions.Naxis(2) < 1) return FALSE;
    if (Naxis(0) != 2) return FALSE;
    if (!result.copy(positions)) return FALSE;
    if (!result.setType(R4)) return FALSE;
    d = radius * 2 + 1;
    if (!c1.create(d, d)) return FALSE;
    if (!c2.circle(d, d, (float)(radius+1), (float)(radius+1), (float)radius)) return FALSE;

    for (n = 0; n < result.Naxis(2); n++) {
        for (x = nint(positions(1, n+1)) - radius, x1 = 1; x <= nint(positions(1, n+1)) + radius; x++, x1++) {
            for (y = nint(positions(2, n+1)) - radius, y1 = 1; y <= nint(positions(2, n+1)) + radius; y++, y1++) {
                if ((x > 0) && (y > 0) && (x <= Naxis(1)) && (y <= Naxis(2))) {
                    c1.r4data[c1.F_I(x1, y1)] = ValueAt(F_I(x, y));
                } else {
                    c1.r4data[c1.F_I(x1, y1)] = 0.0;
                }
            }
        }
        c1.mul(c2);
        c1.centroid(&xcen, &ycen);
        result.r4data[result.C_I(0, n)] = (float)(positions(1, n+1) + xcen) - radius - 1;
        result.r4data[result.C_I(1, n)] = (float)(positions(2, n+1) + ycen) - radius - 1;
    }
    return TRUE;
}

/*!
Return an array of gaussfits around given pixel coordinates.
*/
bool Fits::gausspos(Fits &result, Fits &positions, int radius) {
    Fits g;
    int n, x, y;

    if (positions.Naxis(1) != 2) return FALSE;
    if (positions.Naxis(2) < 1) return FALSE;
    if (Naxis(0) != 2) return FALSE;
    if (!result.copy(positions)) return FALSE;
    if (!result.setType(R4)) return FALSE;

    for (n = 0; n < result.Naxis(2); n++) {
        x = positions.C_I(0, n);
        y = positions.C_I(1, n);
        if (gauss2dsimplefit(g, *this, (int)positions.ValueAt(x), (int)positions.ValueAt(y), radius) > 0.0) {
            result.r4data[x] = g.ValueAt(2);
            result.r4data[y] = g.ValueAt(3);
        }
    }
    return TRUE;
}

/*!
Find local maxima in the Fits around a given radius.
Flag can have 3 values:
Flag = 0 means search in a circle
Flag = 1 means search in a box
Flag = 2 means search for maxima without a given set of positions
*/

bool Fits::maxima(Fits &positions, int radius, int flag, float fraction) {
	switch (flag) {
		case 0: {
			int i, j, n, true_x, true_y;
			float rr = (float)(radius * radius);
			double max;

			for (n = 1; n <= positions.Naxis(2); n++) {
				true_x = (int)(positions(1, n) + 0.5);
				true_y = (int)(positions(2, n) + 0.5);
				positions.setValue((double)true_x, 1, n);
				positions.setValue((double)true_y, 2, n);
				if ((true_x > 0) && (true_y > 0) && (true_x <= naxis[1]) && (true_y <= naxis[2])) {
					max = ValueAt(F_I(true_x, true_y));
					for (i = true_x - radius; i <= true_x + radius; i++) {
						for (j = true_y - radius; j <= true_y + radius; j++) {
							if ((i > 0) && (i <= (int)naxis[1]) && (j > 0) && (j <= (int)naxis[2])) {
								if ((float)((i-true_x)*(i-true_x)) + (float)((j-true_y)*(j-true_y)) <= rr) {
									if (ValueAt(F_I(i, j)) > max) {
										max = ValueAt(F_I(i, j));
										positions.setValue((double)i, 1, n);
										positions.setValue((double)j, 2, n);
									}
								}
							}
						}
					}
				}
			}
		}
			break;
		case 1: {
			int i, j;
			int true_x, true_y, n;
			double max;

			for (n = 1; n <= positions.Naxis(2); n++) {
				true_x = (int)(positions(1, n) + 0.5);
				true_y = (int)(positions(2, n) + 0.5);
				positions.setValue((double)true_x, 1, n);
				positions.setValue((double)true_y, 2, n);
				if ((true_x > 0) && (true_y > 0) && (true_x <= naxis[1]) && (true_y <= naxis[2])) {
					max = ValueAt(F_I(true_x, true_y));
					for (i = true_x - radius; i <= true_x + radius; i++) {
						for (j = true_y - radius; j <= true_y + radius; j++) {
							if ((i > 0) && (i <= (int)naxis[1]) && (j > 0) && (j <= (int)naxis[2])) {
								if (ValueAt(F_I(i, j)) > max) {
									positions.setValue((double)i, 1, n);
									positions.setValue((double)j, 2, n);
									max = ValueAt(F_I(i, j));
								}
							}
						}
					}
				}
			}
		}
			break;
		case 2: {
			int n, c, x, y, i, j;
			double max, max1;

			n = c = 0;
			if (!positions.create(2, 100)) return FALSE;
			for (x = 1; x <= naxis[1]; x++) {
				for (y = 1; y <= naxis[2]; y++) {
					max = ValueAt(F_I(x, y));
					max1 = 0.0;
					for (i = x - radius; i <= x + radius; i++) {
						for (j = y - radius; j <= y + radius; j++) {
							if ((i > 0) && (i <= (int)naxis[1]) && (j > 0) && (j <= (int)naxis[2]) && (i != x) && (j != y)) {
								if (ValueAt(F_I(i, j)) > max1) {
									max1 = ValueAt(F_I(i, j));
								}
							}
						}
					}
					if (max1 * fraction < max) {
						n++;
						c++;
						if (c > 99) {
							if (!positions.recreate(2, positions.naxis[2] + 100)) return FALSE;
							c = 0;
						}
						positions.setValue((double)x, 1, n);
						positions.setValue((double)y, 2, n);
					}
				}
			}
			if (!positions.resize(2, n)) return FALSE;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Return the index of the pixel with the lowest value (fortran-notation).
*/

void Fits::min_pos(int *x, int *y, int *z)
{
	long i, j, k, l, zz;
	double min = ValueAt(0);
	
	*x = *y = zz = 1;
	l = naxis[3];
	if ((l < 1) || (z == NULL)) l = 1;
	for (i = 1; i <= naxis[1]; i++) {
		for (j = 1; j <= naxis[2]; j++) {
			for (k = 1; k <= l; k++) {
				if (ValueAt(F_I(i, j, k)) < min) {
					min = ValueAt(F_I(i, j, k));
					*x = i;
					*y = j;
					zz = k;
				}
			}
		}
	}
	if (z != NULL) *z = zz;
}

/*!
Return minimum, maximum, total value and average value.
*/

void Fits::makeStat(double *min, double *max, double *flux, double *avg)
{
    dpint64 i;
	double v, f = 0.0;

	*min = *max = ValueAt(0);
	for (i = 0; i < n_elements; i++) {
		v = ValueAt(i);
		if (v > *max) *max = v;
		if (v < *min) *min = v;
		f += v;
	}
	if (n_elements == 0) *avg = 0.0;
	else *avg = f / (double)n_elements;
	*flux = f;
}

/*!
Return the maximum value.
*/

double Fits::get_max( void )
{
    dpint64 i = 0;
	double max = 0.0, value;

	while (!gsl_finite(ValueAt(i)) && (i < n_elements-1)) i++;
	max = ValueAt(i);
	
	for (i = 0; i < n_elements; i++) {
		value = ValueAt(i);
		if (gsl_finite(value)) {
			if (value > max) max = value;
		}
	}
	return max;
}

/*!
Return the minimum value.
*/
		
double Fits::get_min( void )
{
    dpint64 i = 0;
	double min = 0.0, value;

	while (!gsl_finite(ValueAt(i)) && (i < n_elements-1)) i++;
	min = ValueAt(i);
	
	for (i = 0; i < n_elements; i++) {
		value = ValueAt(i);
		if (gsl_finite(value)) {
			if (value < min) min = value;
		}
	}
	return min;
}
		
/*!
Compute minimum and maximum values.
*/

void Fits::get_minmax(double *min, double *max) {
    dpint64 i = 0;

    *min = *max = 0;
    if (n_elements < 1) return;

    switch (membits) {
        case I1: {
                unsigned char _min, _max, _value;

                _min = _max = i1data[0];
                for (i = 0; i < n_elements; i++) {
                    _value = i1data[i];
                    if (_value < _min) _min = _value;
                    else if (_value > _max) _max = _value;
                }
                *min = (double)_min * bscale + bzero;
                *max = (double)_max * bscale + bzero;
            }
            break;
        case I2: {
                short _min, _max, _value;

                _min = _max = i2data[0];
                for (i = 0; i < n_elements; i++) {
                    _value = i2data[i];
                    if (_value < _min) _min = _value;
                    else if (_value > _max) _max = _value;
                }
                *min = (double)_min * bscale + bzero;
                *max = (double)_max * bscale + bzero;
            }
            break;
        case I4: {
                int _min, _max, _value;

                _min = _max = i4data[0];
                for (i = 0; i < n_elements; i++) {
                    _value = i4data[i];
                    if (_value < _min) _min = _value;
                    else if (_value > _max) _max = _value;
                }
                *min = (double)_min * bscale + bzero;
                *max = (double)_max * bscale + bzero;
            }
            break;
        case I8: {
                long long _min, _max, _value;

                _min = _max = i8data[0];
                for (i = 0; i < n_elements; i++) {
                    _value = i8data[i];
                    if (_value < _min) _min = _value;
                    else if (_value > _max) _max = _value;
                }
                *min = (double)_min * bscale + bzero;
                *max = (double)_max * bscale + bzero;
            }
            break;
        case R4: {
                dpint64 start = 0;
                float _min, _max, _value;

                while (!gsl_finite(r4data[start]) && (start < n_elements-1)) start++;
                _min = _max = r4data[start];
                for (i = start; i < n_elements; i++) {
                    _value = r4data[i];
                    if (gsl_finite(_value)) {
                        if (_value < _min) _min = _value;
                        else if (_value > _max) _max = _value;
                    }
                }
                *min = (double)_min;
                *max = (double)_max;
            }
            break;
        case R8: {
                dpint64 start = 0;
                double _min, _max, _value;

                while (!gsl_finite(r8data[start]) && (start < n_elements-1)) start++;
                _min = _max = r8data[start];
                for (i = start; i < n_elements; i++) {
                    _value = r8data[i];
                    if (gsl_finite(_value)) {
                        if (_value < _min) _min = _value;
                        else if (_value > _max) _max = _value;
                    }
                }
                *min = _min;
                *max = _max;
            }
            break;
        case C16: {
                dpint64 start = 0;
                double _min, _max, _value;

                while (!gsl_finite(ValueAt(start)) && (start < n_elements-1)) start++;
                _min = _max = ValueAt(start);
                for (i = start; i < n_elements; i++) {
                    _value = ValueAt(i);
                    if (gsl_finite(_value)) {
                        if (_value < _min) _min = _value;
                        else if (_value > _max) _max = _value;
                    }
                }
                *min = _min;
                *max = _max;
            }
            break;
        default:
            break;
    }

    if (!gsl_finite(*min))
        *min = 0.0;

    if (!gsl_finite(*max))
        *max = 0.0;
}

/*!
Compute minimum and maximum values, ignoring one value.
*/

void Fits::get_minmax(double *min, double *max, double ignore) {
    if (!gsl_finite(ignore)) {
        get_minmax(min, max);
        return;
    } else {
        dpint64 i;
	double value;

	*min = *max = ignore;
	for (i = 0; i < n_elements; i++) {
            value = ValueAt(i);
            if ((value != ignore) && gsl_finite(value)) {
                if (*min == ignore) *min = value;
                if (*max == ignore) *max = value;
                if (value < *min) *min = value;
                else if (value > *max) *max = value;
            }
	}
	if (*min == ignore) {
            *min = 0.0;
            *max = 0.0;
	}
    }
}

/*!
Return the total value.
*/

double Fits::get_flux(bool check, dpint64 *nv)
{
    dpint64 i, n = 0;
	double flux = 0.0;

	switch (membits) {
		case I1: 
			for (i = 0; i < n_elements; i++) flux += (double)i1data[i];
			flux *= bscale;
			flux += (double)n_elements * bzero;
			if (nv) *nv = n_elements;
			break;
		case I2: 
			for (i = 0; i < n_elements; i++) flux += (double)i2data[i];
			flux *= bscale;
			flux += (double)n_elements * bzero;
			if (nv) *nv = n_elements;
			break;
		case I4: 
			for (i = 0; i < n_elements; i++) flux += (double)i4data[i];
			flux *= bscale;
			flux += (double)n_elements * bzero;
			if (nv) *nv = n_elements;
			break;
        case I8:
            for (i = 0; i < n_elements; i++) flux += (double)i8data[i];
            flux *= bscale;
            flux += (double)n_elements * bzero;
            if (nv) *nv = n_elements;
            break;
        case R4:
			if (check) {
				for (i = 0; i < n_elements; i++) {
					if (gsl_finite(r4data[i])) {
						flux += (double)r4data[i];
						n++;
					}
				}
			} else {
				for (i = 0; i < n_elements; i++) flux += (double)r4data[i];
				n = n_elements;
			}
			if (nv) *nv = n;
			break;
		case R8:
			if (check) {
				for (i = 0; i < n_elements; i++) {
					if (gsl_finite(r8data[i])) {
						flux += r8data[i];
						n++;
					}
				}
			} else {
				for (i = 0; i < n_elements; i++) flux += r8data[i];
				n = n_elements;
			}
			if (nv) *nv = n;
			break;
		default: 
			break;
	}
	if (!check) if (!gsl_finite(flux)) return get_flux(TRUE, nv);
	return flux;
}

/*!
Return the total integer value.
*/

dpint64 Fits::get_iflux()
{
    dpint64 i, iflux = 0;

    switch (membits) {
    case I1:
            for (i = 0; i < n_elements; i++) iflux += i1data[i];
            break;
    case I2:
            for (i = 0; i < n_elements; i++) iflux += i2data[i];
            break;
    case I4:
            for (i = 0; i < n_elements; i++) iflux += i4data[i];
            break;
    case I8:
            for (i = 0; i < n_elements; i++) iflux += i8data[i];
            break;
    default:
            break;
    }
    return iflux;
}

double Fits::get_avg(void) {
    dpint64 nv;
	double rv;

	rv = get_flux(FALSE, &nv);
	if (nv > 1) rv /= (double)nv;
	return rv;
}

double Fits::get_avg(double ignore)
{
    dpint64 i, n = 0;
	double flux = 0.0, value;

	for (i = 0; i < n_elements; i++) {
		value = ValueAt(i);
		if ((value != ignore) && (gsl_finite(value))) {
			flux += value;
			n++;
		}
	}
	if (n > 0) flux /= n;
	return flux;
}

float Fits::get_median( void )
{
	Fits temp;
    dpint64 i, n = 0;
	
	if (!temp.copy(*this)) return 0.0f;
	if (!temp.setType(R4)) return 0.0f;

	for (i = 0; i < n_elements; i++) {
        if (gsl_finite(temp.r4data[i])) {
			temp.r4data[n] = temp.r4data[i];
			n++;
        }
	}
	if (n == 0) return 0.0f;
	else if (n == 1) return temp.r4data[0];

    if (n % 2) {
        std::nth_element(temp.r4data, temp.r4data + n/2, temp.r4data + n);
        return temp.r4data[n/2];
    } else {
        std::nth_element(temp.r4data, temp.r4data + n/2, temp.r4data + n);
        float low = temp.r4data[n/2];
        std::nth_element(temp.r4data, temp.r4data + n/2 - 1, temp.r4data + n);
        return (low + temp.r4data[n/2 - 1]) / 2.0;
    }
//    gsl_sort_float(temp.r4data, 1, n);
//    return gsl_stats_float_median_from_sorted_data(temp.r4data, 1, n);
	
//	if (n % 2)
//		return nr_select(n / 2 + 1, n, temp.r4data - 1);
//
//	return (nr_select(n / 2, n, temp.r4data - 1) + 
//					nr_select(n / 2 + 1, n, temp.r4data - 1)) / 2.0f;
}

float Fits::get_median(float ignore)
{
	Fits temp;
    dpint64 i, n = 0;
	
	if (!temp.copy(*this)) return 0.0f;
	if (!temp.setType(R4)) return 0.0f;

	for (i = 0; i < n_elements; i++) {
		if ((temp.r4data[i] != ignore) && (gsl_finite(temp.r4data[i]))) {
			temp.r4data[n] = temp.r4data[i];
			n++;
		}
	}
	if (n == 0) return 0.0f;
	
    if (n % 2) {
        std::nth_element(temp.r4data, temp.r4data + n/2, temp.r4data + n);
        return temp.r4data[n/2];
    } else {
        std::nth_element(temp.r4data, temp.r4data + n/2, temp.r4data + n);
        float low = temp.r4data[n/2];
        std::nth_element(temp.r4data, temp.r4data + n/2 - 1, temp.r4data + n);
        return (low + temp.r4data[n/2 - 1]) / 2.0;
    }

//    gsl_sort_float(temp.r4data, 1, n);
//	return gsl_stats_float_median_from_sorted_data(temp.r4data, 1, n);

//	else if (n == 1) return temp.r4data[0];
//	if (n % 2)
//		return nr_select(n / 2 + 1, n, temp.r4data - 1);

//	return (nr_select(n / 2, n, temp.r4data - 1) + 
//					nr_select(n / 2 + 1, n, temp.r4data - 1)) / 2.0f;
}

float Fits::get_nth(double fraction)
{
	Fits temp;
    dpint64 i, n = 0;
//	long nth;
	
	if (!temp.copy(*this)) return 0.0f;
    if (temp.membits == C16) temp.Abs();
	if (!temp.setType(R4)) return 0.0f;

	for (i = 0; i < n_elements; i++) {
		if (gsl_finite(temp.r4data[i])) {
			temp.r4data[n] = temp.r4data[i];
			n++;
		}
	}
	if (n == 0) return 0.0f;
	else if (n == 1) return temp.r4data[0];
	

// sanity check
        if (fraction < 0.0) fraction = 0.0;
        if (fraction > 1.0) fraction = 1.0;

        std::nth_element(temp.r4data, temp.r4data + (int)(n*fraction), temp.r4data + n);
        return temp.r4data[(int)(n*fraction)];

//        gsl_sort_float(temp.r4data, 1, n);
//	return gsl_stats_float_quantile_from_sorted_data(temp.r4data, 1, n, fraction);

//	nth = (long)(fraction * n);
//	return nr_select(nth, n, temp.r4data - 1);
}

double Fits::get_variance(void) {
	double a, rv, v;
    dpint64 n, ne;
	
	ne = 0;
	a = get_avg();
	rv = 0.0;
	for (n = 0; n < n_elements; n++) {
		v = ValueAt(n);
		if (gsl_finite(v)) {
			rv += (v - a) * (v - a);
			ne++;
		}
	}
	if (ne > 2) rv /= (double)(ne - 1);
	
	return rv;
}

double Fits::get_variance(double ignore) {
	double a, rv, v;
    dpint64 n, ne;
	
	a = get_avg(ignore);
	rv = 0.0;
	ne = 0;
	for (n = 0; n < n_elements; n++) {
		v = ValueAt(n);
		if ((v != ignore) && (gsl_finite(v))) {
			rv += (v - a) * (v - a);
			ne++;
		}
	}
	if (ne > 2) {
		rv /= (double)(ne - 1);
	}
	
	return rv;
}

double Fits::get_stddev(void) {
	return sqrt(get_variance());
}

double Fits::get_stddev(double ignore) {
	return sqrt(get_variance(ignore));
}

double Fits::get_meandev(void) {
	double a, rv, v;
    dpint64 n;
	
	a = get_avg();
	rv = 0.0;
	for (n = 0; n < n_elements; n++) {
		v = ValueAt(n);
		rv += (v - a) * (v - a);
	}
	rv = sqrt(rv);
	rv /= (double)(n_elements - 1);
	
	return rv;
}

double Fits::get_meandev(double ignore) {
	double a, rv, v;
    dpint64 n, ne;
	
	a = get_avg(ignore);
	rv = 0.0;
	ne = 0;
	for (n = 0; n < n_elements; n++) {
		v = ValueAt(n);
		if (v != ignore) {
			rv += (v - a) * (v - a);
			ne++;
		}
	}
	rv = sqrt(rv);
	if (ne > 1) {
		rv /= (double)(ne - 1);
	}
	
	return rv;
}

float Fits::get_meddev( void )
{
	Fits temp;
	float med;
        dpint64 i;
        dpint64 n = 0;
	
	if (!temp.copy(*this)) return 0.0;
	if (!temp.setType(R4)) return 0.0;

        for (i = 0; i < n_elements; i++) {
            if (gsl_finite(temp.r4data[i])) {
                temp.r4data[n] = temp.r4data[i];
                n++;
            }
        }
        if (n == 0) return 0.0f;
        else if (n == 1) return temp.r4data[0];

        gsl_sort_float(temp.r4data, 1, n);
        med = gsl_stats_float_median_from_sorted_data(temp.r4data, 1, n);

//	if (n_elements % 2)
//		med = nr_select(n_elements / 2 + 1, n_elements, temp.r4data - 1);
//	else
//		med = (nr_select(n_elements / 2, n_elements, temp.r4data - 1) + 
//					nr_select(n_elements / 2 + 1, n_elements, temp.r4data - 1)) / 2.0;

        for (i = 0; i < n; i++) temp.r4data[i] = fabs(temp.r4data[i] - med);

        gsl_sort_float(temp.r4data, 1, n);
        return gsl_stats_float_median_from_sorted_data(temp.r4data, 1, n);

//	if (n_elements % 2)
//		return nr_select(n_elements / 2 + 1, n_elements, temp.r4data - 1);
//		return (nr_select(n_elements / 2, n_elements, temp.r4data - 1) + 
//					nr_select(n_elements / 2 + 1, n_elements, temp.r4data - 1)) / 2.0f;
}

double Fits::get_fwhm(int xcenter, int ycenter, int radius) {
	double sum21, sum22, FFLUX, f;
	int j, k, ii, kk;

	sum21 = sum22 = FFLUX = 0.0;
	for (j = xcenter - radius; j <= xcenter + radius; j++) {
		if (j >= 1 && j <= naxis[1]) {
			ii = j - xcenter;
			for (k = ycenter - radius; k <= ycenter + radius; k++) {
				if (k >= 1 && k <= naxis[2]) {
					f  = ValueAt(F_I(j, k));
					FFLUX += f;
					kk = k - ycenter;
					sum21 += f * ii * ii;
					sum22 += f * kk * kk;
				}
			}
		}
	}
	return 1.6651092 * sqrt(fabs(sum21/FFLUX + sum22/FFLUX));
}

bool Fits::radial_avg(Fits &result, int x, int y, int radius, int center) {
	int i, j, k = 1, tx, ty;
	double max;
	
	if (!result.create(2, (2 * radius + 1) * (2 * radius + 1))) return FALSE;
	if (center > 0) {
		tx = x;
		ty = y;
		max = ValueAt(F_I(x, y));
		for (i = x - center; i <= x + center; i++) {
			for (j = y - center; j <= y + center; j++) {
				if ((i > 0) && (i <= naxis[1]) && (j > 0) && (j <= naxis[2])) {
					if (ValueAt(F_I(i, j)) > max) {
						tx = i;
						ty = j;
						max = ValueAt(F_I(i, j));
					}
				}
			}
		}
		x = tx;
		y = ty;
	}
	for (i = x - radius; i <= x + radius; i++) {
		for (j = y - radius; j <= y + radius; j++) {
			result.setValue(sqrt((double)((x - i) * (x - i) + (y - j) * (y - j))), 1, k);
			if ((i > 0) && (i <= naxis[1]) && (j > 0) && (j <= naxis[2])) {
				result.setValue(ValueAt(F_I(i, j)), 2, k);
			} else {
				result.setValue(0.0, 2, k);
			}
			k++;
		}
	}
	return TRUE;
}

bool Fits::radial_profile(Fits &result, int xcen, int ycen) {
    int i, j, r;
    float d, dy2, dx2;
    Fits mask;
    int nmax = Naxis(1);
    if (Naxis(2) > nmax) nmax = Naxis(2);

    if (!result.create(nmax + 2, 1, R8)) return FALSE;
    if (!mask.create(nmax + 2, 1, I4)) return FALSE;

    result.r8data[0] = ValueAt(F_I(xcen, ycen));
    mask.i4data[0] = 1;
    for (r = 1; r <= nmax; r++) {
        for (i = 1; i <= Naxis(1); i++) {
            dx2 = sqr((float)(i - xcen));
            for (j = 1; j <= Naxis(2); j++) {
                dy2 = sqr((float)(j - ycen));
                d = sqrt(dx2 + dy2);
                if ((d <= r) && (d > r-1)) {
                    result.r8data[r] += ValueAt(F_I(i, j));
                    mask.i4data[r]++;
                }
            }
        }
    }
    result /= mask;
    result.SetFloatKey("CRPIX1", 0.0);
    result.SetFloatKey("CRVAL1", 0.0);
    result.SetFloatKey("CDELT1", (fabs(getCDELT(1)) + fabs(getCDELT(2))) / 2.0);

    return TRUE;
}

bool Fits::elliptical_profile(Fits &result, int xcen, int ycen, double angle, double ratio, int width) {
    Fits ani, ano;
    long n, v;
    double sum;
    int r, i;
    int nmax = Naxis(1);
    if (Naxis(2) > nmax) nmax = Naxis(2);

    if (!result.create(nmax + 2, 1, R8)) return FALSE;

/* The very first bin */
    dpProgress(-nmax, "elliptical profile...");
    ani.ellipse(Naxis(1), Naxis(2), xcen, ycen, width, width/ratio, angle);
    v = 0;
    sum = 0.0;
    for (n = 0; n < Nelements(); n++) {
        if (ani.i1data[n] == 1) {
            v++;
            sum += ValueAt(n);
        }
    }
    if (v > 0) result.r8data[0] = sum / (double)v;
    i = 1;
    for (r = width; r < nmax; r += width) {
        dpProgress(r, "elliptical profile...");
        ano.ellipse(Naxis(1), Naxis(2), xcen, ycen, (r+width), (r+width)/ratio, angle);
        ani.ellipse(Naxis(1), Naxis(2), xcen, ycen, r, r/ratio, angle);
        v = 0;
        sum = 0.0;
        for (n = 0; n < Nelements(); n++) {
            if (ano.i1data[n] == 1 && ani.i1data[n] == 0) {
                v++;
                sum += ValueAt(n);
            }
        }
        if (v > 0) result.r8data[i] = sum / (double)v;
        i++;
        if (FitsInterrupt) break;
    }
    result.recreate(i, 1);
    dpProgress(0, "");
    return TRUE;
}
	
bool Fits::rr_ri(const Fits & a, const Fits & b)
{
    dpint64 n;

	if (a.Nelements() != b.Nelements()) return FALSE;
	if (!copy(a)) return FALSE;
	if (!setType(C16)) return FALSE;

	for (n = 0; n < n_elements; n++) {
		cdata[n].i = b.ValueAt(n);
	}

	return TRUE;
}

bool Fits::rr_aa(const Fits & a, const Fits & b)
{
    dpint64 n;
	double xr, xa;
	
	if (a.Nelements() != b.Nelements()) return FALSE;
	if (!copy(a)) return FALSE;
	if (!setType(C16)) return FALSE;

	for (n = 0; n < n_elements; n++) {
		xr = a.ValueAt(n);
		xa = b.ValueAt(n);
		cdata[n].r = xr * cos(xa);
		cdata[n].i = xr * sin(xa);
	}

	return TRUE;
}

void Fits::ri_r( void )
{
	if (membits == C16) setType(R8);
}

void Fits::ri_i( void )
{
    dpint64 i;
	
	if (membits == C16) {
 		for (i = 0; i < n_elements; i++) {
			r8data[i] = cdata[i].i;
		}
		membits = R8;
		allocateMemory();
	}
}

void Fits::ri_amp( void )
{
    dpint64 i;
	
	if (membits == C16) {
 		for (i = 0; i < n_elements; i++) {
			r8data[i] = sqrt(sqr(cdata[i].r) + sqr(cdata[i].i));
		}
		membits = R8;
		allocateMemory();
	}
}

void Fits::ri_pow( void )
{
    dpint64 i;
	
	if (membits == C16) {
 		for (i = 0; i < n_elements; i++) {
			r8data[i] = Cabs(cdata[i]);
		}
		membits = R8;
		allocateMemory();
	}
}

void Fits::ri_arg( void )
{
    dpint64 i;
	
/*
 * ATTENTION : here the DC of the phase plane is shifted to 
 *             the next DC
 */
						
	if (membits == C16) {
		for (i = 0; i < n_elements; i++) {
			if (fabs(cdata[i].r) < EPS) {
				if (cdata[i].i >= 0.0) r8data[i] = M_PI / 2.0;
				else r8data[i] = -M_PI / 2.0;
			} else {
				if (cdata[i].r > 0.0) r8data[i] = atan(cdata[i].i / cdata[i].r);
				else if (cdata[i].i >= 0.0) r8data[i] = atan(cdata[i].i / cdata[i].r) + M_PI;
				else r8data[i] = atan(cdata[i].i / cdata[i].r) - M_PI;
			}
		}
		membits = R8;
		allocateMemory();
	}
}

/*!
If a complex array, replace all values by its complex conjugate. Does nothing for any other array type.
*/
void Fits::conj( void )
{
    dpint64 i;
	
	if (membits == C16) {
		for (i = 0; i < n_elements; i++) {
			cdata[i].i = -cdata[i].i;
		}
	}
}

/*
Comparison function for createFitsIndex;
This function uses the global variable float *_compareFloat
*/
float *_compareFloat;

int floatCompare(const void *a, const void *b)
{
    if (_compareFloat[*(int *)a] == _compareFloat[*(int *)b]) return 0;
    else if (_compareFloat[*(int *)a] < _compareFloat[*(int *)b]) return -1;
	else return 1;
}

/*!
Create an index table of the array in ascending or descending order.
*/
bool Fits::createFitsIndex(const Fits &array, bool reverse) {
    dpint64 i;
	Fits _tmp;

	if (!create(array.Nelements(), 1, I4)) return FALSE;
	for (i = 0; i < array.Nelements(); i++) i4data[i] = i;
	if (array.membits == R4) _compareFloat = array.r4data;
	else {
		if (!_tmp.copy(array)) return FALSE;
		if (!_tmp.setType(R4)) return FALSE;
		_compareFloat = _tmp.r4data;
	}
	qsort(i4data, array.Nelements(), sizeof(int), floatCompare);
	for (i = 0; i < array.Nelements(); i++) i4data[i]++;
	if (reverse) flip(1);

	return TRUE;
}

/*!
Invert a 2D-matrix
*/
bool Fits::ginvert() {
//	float **mat, **s;
	int n, i, j, signum;
	
	if (naxis[1] != naxis[2]) {
		dp_output("Fits::invert: Not a square matrix\n");
		return FALSE;
	}
	
	gsl_permutation *p = gsl_permutation_alloc(Naxis(1));
	gsl_matrix* g = gsl_matrix_alloc(Naxis(1), Naxis(2));
	gsl_matrix* gi = gsl_matrix_alloc(Naxis(1), Naxis(2));

	n = naxis[1];
	if (!setType(R8)) return FALSE;
	for (i = 0; i < n; i++) {
		for (j = 0; j < n; j++) {
			gsl_matrix_set(g, i, j, r8data[C_I(i,j)]);
		}
	}
	
	gsl_linalg_LU_decomp(g, p, &signum);
	gsl_linalg_LU_invert(g, p, gi);

	for (i = 0; i < n; i++) {
		for (j = 0; j < n; j++) {
			r8data[C_I(i,j)] = gsl_matrix_get(gi, i, j);
		}
	}
	
	gsl_matrix_free(g);
	gsl_matrix_free(gi);
	gsl_permutation_free(p);
	
	return TRUE;
}

/*!
Create a histogram of the data.
*/
bool Fits::histogram(Fits &data, double min, double max, double binsize) {
	if (binsize == 0.0) binsize = 1.0;
	int nbins = (int)((max - min) / binsize) + 1;
    dpint64 n;
	double value;
//	int binnum;
	int v;

	if (!create(nbins, 1, I4)) return FALSE;

	for (n = 0; n < data.Nelements(); n++) {
		value = (data[n] - min) / binsize;
		for (v = 0; v < nbins-1; v++) {
			if (v <= value && value < v + 1) i4data[v]++;
		}
		v = nbins - 1;
		if (v <= value && value <= v + 1) i4data[v]++;
	}

	return TRUE;
}

/*!
Create the reverse indices of a histogram
*/
bool Fits::histogram_indices(Fits &data, double min, double max, double binsize) {
	Fits hist;
	
	if (hist.histogram(data, min, max, binsize)) {
		create(hist.Nelements() + data.Nelements() + 1, 1, I4);  
		for (int i = 0; i <= hist.Nelements(); i++) {
			if (i == 0) {
				i4data[i] = hist.Nelements() + 2;
			} else {
				i4data[i] = i4data[i-1] + hist.i4data[i-1];
			}
		}
  
		int pos = hist.Nelements() + 1;

		if (binsize == 0.0) binsize = 1.0;
		for (int histpos = 0; histpos < hist.Nelements(); histpos++) {
			for (int i = 0; i < data.Nelements(); i++) {
				if (data.ValueAt(i) >= min && data.ValueAt(i) < min + binsize) {
					i4data[pos] = i + 1;
					pos++;
				}
			}
			min += binsize;
		}
//		resize(pos);
	} else return FALSE;
	return TRUE;
}

/*!
Create a histogram equalization of an image with 256 entries.
*/
bool Fits::histeq(Fits &data, double min, double max) {
	Fits hist, cuml;
    dpint64 n;
	long index;

	if (!create(data.Naxis(1), data.Naxis(2))) return FALSE;

	if (!hist.histogram(data, min, max, (max - min) / 5000.0)) return FALSE;
	if (!cuml.create(hist.Naxis(1), 1)) return FALSE;
	cuml.r4data[0] = hist[0];
	for (n = 1; n < hist.Nelements(); n++) {
		cuml.r4data[n] = cuml.r4data[n-1] + hist[n];
	}
	for (n = 0; n < Nelements(); n++) {
		index = (long)data[n];
		if (index < 0) index = 0;
		else if (index > 4999) index = 4999;
		r4data[n] = cuml[index];
	}
	norm();
	mul(255.0);
	setType(I1);

	return TRUE;
}

/*!
Return the maximum value, optionally ignoring non-finite values.
*/

double dp_max(Fits &v, bool check)
{
    dpint64 i;
	double max = 0.0;

	max = v[0];
	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if (gsl_finite(v[i])) {
				if (!gsl_finite(max)) max = v[i];
				else if (v[i] > max) max = v[i];
			}
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			if (v[i] > max) max = v[i];
		}
	}

	if (!gsl_finite(max)) if (!check) max = dp_max(v, TRUE);
	if (!gsl_finite(max)) max = 0.0;
	return max;
}

/*!
Return the maximum value ignoring one value, optionally ignoring non-finite values.
*/

double dp_max_i(Fits &v, double ignore, bool check)
{
    dpint64 i;
	double max = 0.0;

	max = v[0];
	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if ((gsl_finite(v[i])) && (v[i] != ignore)) {
				if (!gsl_finite(max)) max = v[i];
				else if (v[i] > max) max = v[i];
			}
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			if (v[i] != ignore) {
				if (v[i] > max) max = v[i];
			}
		}
	}

	if (!gsl_finite(max)) if (!check) max = dp_max_i(v, ignore, TRUE);
	if (!gsl_finite(max)) max = 0.0;
	return max;
}

/*!
Return the minimum value, optionally ignoring non-finite values.
*/

double dp_min(Fits &v, bool check)
{
    dpint64 i;
	double min = 0.0;

	min = v[0];
	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if (gsl_finite(v[i])) {
				if (!gsl_finite(min)) min = v[i];
				else if (v[i] < min) min = v[i];
			}
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			if (v[i] < min) min = v[i];
		}
	}

	if (!gsl_finite(min)) if (!check) min = dp_min(v, TRUE);
	if (!gsl_finite(min)) min = 0.0;
	return min;
}

/*!
Return the minimum value ignoring one value, optionally ignoring non-finite values.
*/

double dp_min_i(Fits &v, double ignore, bool check)
{
    dpint64 i;
	double min = 0.0;

	min = v[0];
	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if ((gsl_finite(v[i])) && (v[i] != ignore)) {
				if (!gsl_finite(min)) min = v[i];
				else if (v[i] < min) min = v[i];
			}
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			if (v[i] != ignore) {
				if (v[i] < min) min = v[i];
			}
		}
	}

	if (!gsl_finite(min)) if (!check) min = dp_min_i(v, ignore, TRUE);
	if (!gsl_finite(min)) min = 0.0;
	return min;
}

/*!
Return the total value, optionally ignoring non-finite values.
*/

double dp_total(Fits &v, bool check)
{
    dpint64 i;
	double total = 0.0;

	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if (gsl_finite(v[i])) total += v[i];
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			total += v[i];
		}
	}

	if (!gsl_finite(total)) if (!check) total = dp_total(v, TRUE);
	if (!gsl_finite(total)) total = 0.0;
	return total;
}

/*!
Return the total value ignoring one value, optionally ignoring non-finite values.
*/

double dp_total_i(Fits &v, double ignore, bool check)
{
    dpint64 i;
	double total = 0.0;

	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if ((gsl_finite(v[i])) && (v[i] != ignore)) total += v[i];
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			if (v[i] != ignore) total += v[i];
		}
	}

	if (!gsl_finite(total)) if (!check) total = dp_total_i(v, ignore, TRUE);
	if (!gsl_finite(total)) total = 0.0;
	return total;
}

/*!
Return the average value, optionally ignoring non-finite values.
*/

double dp_avg(Fits &v, bool check)
{
    dpint64 i, n = 0;
	double total = 0.0;

	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if (gsl_finite(v[i])) {
				total += v[i];
				n++;
			}
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			total += v[i];
			n++;
		}
	}

	total /= (double)n;
	if (!gsl_finite(total)) if ((!check) && (n > 0)) total = dp_avg(v, TRUE);
	if (!gsl_finite(total)) total = 0.0;
	return total;
}

/*!
Return the average value ignoring one value, optionally ignoring non-finite values.
*/

double dp_avg_i(Fits &v, double ignore, bool check)
{
    dpint64 i, n = 0;
	double total = 0.0;

	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			if ((gsl_finite(v[i])) && (v[i] != ignore)) {
				total += v[i];
				n++;
			}
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			if (v[i] != ignore) {
				total += v[i];
				n++;
			}
		}
	}

	total /= (double)n;
	if (!gsl_finite(total)) if ((!check) && (n > 0)) total = dp_avg_i(v, ignore, TRUE);
	if (!gsl_finite(total)) total = 0.0;
	return total;
}

/*!
Return minimum, maximum, total value and average value, optionally ignoring non-finite values.
*/

void dp_stat(Fits &v, double *min, double *max, double *flux, double *avg, bool check)
{
    dpint64 i, n = 0;
	double va, f = 0.0;

	*min = *max = v[0];
	if (check) {
		for (i = 0; i < v.Nelements(); i++) {
			va = v[i];
			if (gsl_finite(va)) {
				if (!gsl_finite(*min)) *min = va;
				if (!gsl_finite(*max)) *max = va;
				if (va > *max) *max = va;
				if (va < *min) *min = va;
				f += va;
				n++;
			}
			*avg = f / (double)n;
		}
	} else {
		for (i = 0; i < v.Nelements(); i++) {
			va = v[i];
			if (va > *max) *max = va;
			if (va < *min) *min = va;
			f += va;
		}
		*avg = f / (double)v.Nelements();
	}
	
	*flux = f;
	if (!gsl_finite(*flux)) if (!check) dp_stat(v, min, max, flux, avg, TRUE);

	if (!gsl_finite(*flux)) {
		*flux = *avg = *min = *max = 0.0;
	}
}

