/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/fits_cube.cpp
 * Purpose:  Fits class methods to handle fits datacubes
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 13.08.1999: file created
 ******************************************************************/

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

#include <algorithm>

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

#ifdef IBM
#include <strings.h>
#endif

#include "platform.h"

#include "fits.h"
/* #include "../external/fitsio.h" */
#include "math_utils.h"

/*!
Utility function to create standard (CAHA) filenames
*/

void makefname(char *result, char *prefix, int num, int length, char *suffix)
{
	char ns[11];
	int n;

	switch (length) {
		case 1: sprintf(ns, "%1i", num); break;
		case 2: sprintf(ns, "%2i", num); break;
		case 3: sprintf(ns, "%3i", num); break;
		case 4: sprintf(ns, "%4i", num); break;
		case 5: sprintf(ns, "%5i", num); break;
		case 6: sprintf(ns, "%6i", num); break;
		case 7: sprintf(ns, "%7i", num); break;
		case 8: sprintf(ns, "%8i", num); break;
		case 9: sprintf(ns, "%9i", num); break;
		default: break;
	}
	for (n = 0; n < length; n++) {
		if (ns[n] == ' ') ns[n] = '0';
	}
	strcpy(result, prefix);
	strcat(result, ns);
	strcat(result, suffix);
}

void disentangleTemplate(char *templ, char *prefix, int *length, char *suffix)
{
	int i, j;

	i = 0;
	while (templ[i] != '#') {
		prefix[i] = templ[i];
		i++;
	}
	prefix[i] = (char)0;
	j = 0;
	while (templ[i] == '#') {
		i++;
		j++;
	}
	*length = j;
	j = 0;
	while (templ[i] != (char)0) {
		suffix[j] = templ[i];
		i++;
		j++;
	}
	suffix[j] = (char)0;
}

/*!
Read consecutive files and make a long exposure
*/

bool Fits::ravg(char *templ, int first, int last)
{
	Fits current;
	char prefix[255], suffix[255], fname[255];
	int i, length;

	disentangleTemplate(templ, prefix, &length, suffix);

	for (i = first; i <= last; i++) {
		makefname(fname, prefix, i, length, suffix);
		
		dp_output("Reading file %s...\n", fname);
		fflush(stdout);
		if (i == first) {
			if (!ReadFITS(fname)) return FALSE;
		} else {
			if (!current.ReadFITS(fname)) return FALSE;
			add(current);
		}
	}
	div((float)(last - first + 1));

	return TRUE;
}

/*
 * Utility function to close a FITS file and return FALSE
 */

bool closeAndReturnFALSE(Fits &f) {
    f.CloseFITS();
    return false;
}

/*!
Take a fits cube and do an average of the images therein.
This version reads the single images consecutively from
disk in order to save memory.
*/

bool cube_avg(const char *fname, Fits & result, int first, int last)
{
	int m, nfiles, numfiles, status;
	long n[4], inc[4];
	Fits current;

	status = 0;
	inc[0] = inc[1] = inc[2] = inc[3] = 1;

// find out how big the array is
	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
    if (!current.getHeaderInformation()) return closeAndReturnFALSE(current);
	n[0] = current.Naxis(0);
	n[1] = current.Naxis(1);
	n[2] = current.Naxis(2);
	numfiles = current.Naxis(3);
    if (!result.create(n[1], n[2])) return closeAndReturnFALSE(current);
	result.CopyHeader(current);
	result = 0.0;
//	if (!current.create(n[1], n[2], current.membits)) return FALSE;
	
	nfiles = last - first + 1;
	if (last < first) nfiles = numfiles - first + 1;

// now go through array and read data
	numfiles = 0;
	dp_output("Doing average of %i images\nReading image no.         ", nfiles);
	fflush(stdout);
	for (m = first; m < first + nfiles; m++) {
        if (!current.ReadFITSCubeImage(m)) return closeAndReturnFALSE(current);
		dp_output("\033[8D\033[8P%5i...", m);
//		printf("...%i", m);
		fflush(stdout);
		result += current;
		numfiles++;
		if (FitsInterrupt) {
			dp_output("CUBEAVG interrupted after %i images\n", m - first + 1);
			fflush(stdout);
			break;
		}
	}
	current.CloseFITS();
	if (numfiles > 1) result /= (float)(numfiles);
	dp_output("\n");

	return TRUE;
}

/*!
Take a fits cube and do an average of the images therein.
This version reads the single images consecutively from
disk in order to save memory.
*/

bool cube_avg(const char *fname, Fits & result, double reject)
{
	int m, status;
	long n[4], inc[4];
    dpint64 i;
	Fits current, mask;
	double value;

	status = 0;
	inc[0] = inc[1] = inc[2] = inc[3] = 1;

// find out how big the array is
	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
    if (!current.getHeaderInformation()) return closeAndReturnFALSE(current);
	n[0] = current.Naxis(0);
	n[1] = current.Naxis(1);
	n[2] = current.Naxis(2);
	n[3] = current.Naxis(3);
    if (!result.create(n[1], n[2])) return closeAndReturnFALSE(current);
	result.CopyHeader(current);
    if (!mask.create(n[1], n[2], I4)) return closeAndReturnFALSE(current);
	result = 0.0;
	
// now go through array and read data
	dp_output("Doing average of %i images\nReading image no.         ", n[3]);
	fflush(stdout);
	for (m = 1; m <= n[3]; m++) {
        if (!current.ReadFITSCubeImage(m)) return closeAndReturnFALSE(current);
		dp_output("\033[8D\033[8P%5i...", m);
		fflush(stdout);
		for (i = 0; i < result.Nelements(); i++) {
			value = current.ValueAt(i);
			if (value != reject) {
				result.r4data[i] += value;
				mask.i4data[i]++;
			}
		}
		if (FitsInterrupt) {
			dp_output("CUBEAVG interrupted after %i images\n", m);
			fflush(stdout);
			break;
		}
	}
	current.CloseFITS();
	for (i = 0; i < result.Nelements(); i++) {
		if (mask.i4data[i] > 1) result.r4data[i] /= (double)mask.i4data[i];
	}
	dp_output("\n");

	return TRUE;
}

// bool cube_avg(char *fname, Fits & result, int first, int last)
// {
// 	int i, n=0, numfiles;
// 	Fits current;
// 
// 	numfiles = current.FitsKey(fname, "NAXIS3");
// 	current.create(current.Naxis(1), current.Naxis(2));
// 	result.create(current.Naxis(1), current.Naxis(2));
// 
// 	i = first;
// 	if (last < first) last = numfiles;
// 	if (last > numfiles) last = numfiles;
// 	while (i <= last) {
// 		if (current.ReadFits(fname, i)) {
// 			if (FitsVerbose) printf("processing frame %i...\n", i);
// 			if (i == first) result = current;
// 			else result += current;
// 			n++;
// 			i++;
// 		} else break;
// 	}
// 
// 	result /= n;
//  	return TRUE;
// }

/*!
Take a fits cube and do an average of the images therein.
*/

bool cube_avg(Fits & cube, Fits & result, int first, int last)
{
	long x, y;
	int z;
    dpint64 i;
    Fits mask;
    double value;
	
	if (cube.Naxis(0) != 3) {
        result.copy(cube);
        return TRUE;
//		dp_output("cube_avg: input image is no cube.\n");
//		return FALSE;
	}

    if (!result.create(cube.Naxis(1), cube.Naxis(2))) return FALSE;
	result.CopyHeader(cube);
    if (!mask.create(cube.Naxis(1), cube.Naxis(2), I4)) return FALSE;

	if (last < first) last = cube.Naxis(3);
	if (first < 1) first = 1;
	if (first > cube.Naxis(3)) first = cube.Naxis(3);
	if (last < 1) last = 1;
	if (last > cube.Naxis(3)) last = cube.Naxis(3);

	result = 0.0;
	dpProgress(-cube.Naxis(1), "");
	for (x = 1; x <= cube.Naxis(1); x++) {
		for (y = 1; y <= cube.Naxis(2); y++) {
			for (z = first; z <= last; z++) {
				value = cube.ValueAt(cube.at(x, y, z));
                if (gsl_finite(value)) {
                    result.r4data[result.at(x, y)] += value;
                    mask.i4data[mask.at(x, y)]++;
                }
			}
		}
		if (FitsInterrupt) {
			dpProgress(0, "");
			return FALSE;
		}
		dpProgress(x, "");
	}
    for (i = 0; i < result.Nelements(); i++) {
        if (mask.i4data[i] > 1) result.r4data[i] /= (double)mask.i4data[i];
    }
    return TRUE;
}

/*!
Take a fits cube and do an average of the images therein.
*/

bool cube_avg(Fits & cube, Fits & result, double reject)
{
	long x, y;
    dpint64 i;
	int z;
	Fits mask;
	double value;
	
	if (cube.Naxis(0) != 3) {
        result.copy(cube);
        return TRUE;
//		dp_output("cube_avg: input image is no cube.\n");
//		return FALSE;
	}
	if (!result.create(cube.Naxis(1), cube.Naxis(2))) return FALSE;
	result.CopyHeader(cube);
	if (!mask.create(cube.Naxis(1), cube.Naxis(2), I4)) return FALSE;
	
	result = 0.0;
	dpProgress(-cube.Naxis(1), "");
	for (x = 1; x <= cube.Naxis(1); x++) {
		for (y = 1; y <= cube.Naxis(2); y++) {
			for (z = 1; z <= cube.Naxis(3); z++) {
				value = cube.ValueAt(cube.at(x, y, z));
				if ((value != reject) && gsl_finite(value)) {
					result.r4data[result.at(x, y)] += value;
					mask.i4data[mask.at(x, y)]++;
				}
			}
		}
		if (FitsInterrupt) {
			dpProgress(0, "");
			return FALSE;
		}
		dpProgress(x, "");
	}
	for (i = 0; i < result.Nelements(); i++) {
		if (mask.i4data[i] > 1) result.r4data[i] /= (double)mask.i4data[i];
	}
	return TRUE;
}

/*!
Take a fits cube and do an average of the images therein.
*/

bool cube_avg(Fits & cube, Fits & result, int first, int last, double reject)
{
	long x, y;
    dpint64 i;
	int z;
	Fits mask;
	double value;
	
	if (cube.Naxis(0) != 3) {
        result.copy(cube);
        return TRUE;
//		dp_output("cube_avg: input image is no cube.\n");
//		return FALSE;
	}
	if (!result.create(cube.Naxis(1), cube.Naxis(2))) return FALSE;
	result.CopyHeader(cube);
	if (!mask.create(cube.Naxis(1), cube.Naxis(2), I4)) return FALSE;
	
	result = 0.0;
	dpProgress(-cube.Naxis(1), "");
	for (x = 1; x <= cube.Naxis(1); x++) {
		for (y = 1; y <= cube.Naxis(2); y++) {
			for (z = first; z <= last; z++) {
				value = cube.ValueAt(cube.at(x, y, z));
				if ((value != reject) && gsl_finite(value)) {
					result.r4data[result.at(x, y)] += cube.ValueAt(cube.at(x, y, z));
					mask.i4data[mask.at(x, y)]++;
				}
			}
		}
		if (FitsInterrupt) {
			dpProgress(0, "");
			return FALSE;
		}
		dpProgress(x, "");
	}
	for (i = 0; i < result.Nelements(); i++) {
		if (mask.i4data[i] > 1) result.r4data[i] /= (double)mask.i4data[i];
	}
	return TRUE;
}

bool Fits::ssaaddframe(Fits &frame, int sx, int sy, double fact) {
	long xstart, xend, ystart, yend, fx, fy;
	long x, y;

	if (!setType(R4)) return FALSE;
	xstart = sx >= 0 ? sx : 0;
	ystart = sy >= 0 ? sy : 0;
	xend = sx + frame.Naxis(1);
	yend = sy + frame.Naxis(2);
	if (xend > Naxis(1)) xend = Naxis(1);
	if (yend > Naxis(2)) yend = Naxis(1);
	fx = sx < 0 ? -sx : sx;

	for (x = xstart; x <= xend; x++, fx++) {
		fy = sy < 0 ? -sy : sy;
		for (y = ystart; y <= yend; y++, fy++) {
			r4data[C_I(x, y)] += (float)(frame.ValueAt(frame.C_I(fx, fy)) * fact);
		}
	}
	return TRUE;
}

void Fits::ssaaddmask(int xf, int yf, int sx, int sy) {
	long xstart, xend, ystart, yend;
	long x, y;

	xstart = sx >= 0 ? sx : 0;
	ystart = sy >= 0 ? sy : 0;
	xend = sx + xf;
	yend = sy + yf;
	if (xend > Naxis(1)) xend = Naxis(1);
	if (yend > Naxis(2)) yend = Naxis(1);

	switch (membits) {
		case I1:
			for (x = xstart; x <= xend; x++) {
				for (y = ystart; y <= yend; y++) {
					i1data[C_I(x, y)]++;
				}
			}
			break;
		case I2:
			for (x = xstart; x <= xend; x++) {
				for (y = ystart; y <= yend; y++) {
					i2data[C_I(x, y)]++;
				}
			}
			break;
		case I4:
			for (x = xstart; x <= xend; x++) {
				for (y = ystart; y <= yend; y++) {
					i4data[C_I(x, y)]++;
				}
			}
			break;
        case I8:
            for (x = xstart; x <= xend; x++) {
                for (y = ystart; y <= yend; y++) {
                    i8data[C_I(x, y)]++;
                }
            }
            break;
        case R4:
			for (x = xstart; x <= xend; x++) {
				for (y = ystart; y <= yend; y++) {
					r4data[C_I(x, y)]++;
				}
			}
			break;
		case R8:
			for (x = xstart; x <= xend; x++) {
				for (y = ystart; y <= yend; y++) {
					r8data[C_I(x, y)]++;
				}
			}
			break;
		case C16:
			for (x = xstart; x <= xend; x++) {
				for (y = ystart; y <= yend; y++) {
					cdata[C_I(x, y)].r++;
				}
			}
			break;
		default: break;
	}
}

bool Fits::ssa(Fits & cube, int x, int y, int method, Fits *sky, Fits *flat, Fits *dpl, Fits *mask)
{
	Fits current, sum, findmax, factor;
	long i;
	int xmax, ymax, c;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
	double fxmax, fymax;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;

	c = cube.naxis[1] * cube.naxis[2];
	if (!create(cube.Naxis(1), cube.Naxis(2))) return FALSE;
	if (!factor.create(cube.Naxis(1), cube.Naxis(2), cube.Naxis(3) > 255 ? I2 : I1)) return FALSE;

	for (i = 1; i <= cube.Naxis(3); i++) {
		cube.extractRange(current, -1, -1, -1, -1, i, i);
		if (has_sky == 1) current.sub(*sky);
		if (has_flat == 1) current.mul(*flat);
		if (has_dpl == 1) DeadpixApply(current, *dpl);
		findmax.copy(current);
		if (has_mask == 1) findmax *= *mask;
		if (method == 1) findmax.centroid(&fxmax, &fymax);
		else findmax.max_pos(&xmax, &ymax);
		if (method > 1) {
			Fits circle;
			circle.circle(findmax.Naxis(1), findmax.Naxis(2), (float)xmax, (float)ymax, (float)method);
			findmax *= circle;
			findmax.centroid(&fxmax, &fymax);
		}
		if (method > 0) {
			xmax = (int)(fxmax + 0.5);
			ymax = (int)(fymax + 0.5);
		}
		dp_output("SSA image no. %4i...    dx = %4i, dy = %4i\n", i, x-xmax, y-ymax);
		ssaaddframe(current, x - xmax, y - ymax);
		factor.ssaaddmask(current.Naxis(1), current.Naxis(2), x - xmax, y - ymax);
		if (FitsInterrupt) {
			dp_output("SSA interrupted after %i images\n", i);
			break;
		}
	}
	div(factor);
	
	return TRUE;
}

void Fits::rssa(char *prefix, int first, int last, int length, char *suffix, int x, int y, char *maskname, char *skyname, char *flatname, char *dplname)
{
	Fits current, flat, sky, sum, mask, findmax, factor;
	char fname[255];
	int i, xmax, ymax;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;

	if (strncasecmp(skyname, "none", 4) != 0) {
		has_sky = 1;
		sky.ReadFITS(skyname);
	}
	if (strncasecmp(flatname, "none", 4) != 0) {
		has_flat = 1;
		flat.ReadFITS(flatname);
	}
	if (strncasecmp(dplname, "none", 4) != 0) {
		has_dpl = 1;
	}
	if (strncasecmp(maskname, "none", 4) != 0) {
		has_mask = 1;
		mask.ReadFITS(maskname);
	}
	for (i = first; i <= last; i++) {
		makefname(fname, prefix, i, length, suffix);
		
		dp_output("Reading file %s...\n", fname);
		current.ReadFITS(fname);
		if (has_sky == 1) current.sub(sky);
		if (has_flat == 1) current.mul(flat);
		if (has_dpl == 1) current.dpl_apply(dplname);
		if (i == first) {
// 			findmax.naxis[1] = current.naxis1;
// 			findmax.naxis2 = current.naxis2;
// 			findmax.bitpix = current.bitpix;
// 			findmax.get_mem();
			findmax.create(current.Naxis(1), current.Naxis(2));
// 			factor.naxis1 = current.naxis1;
// 			factor.naxis2 = current.naxis2;
// 			factor.bitpix = current.bitpix;
// 			factor.get_mem();
			factor.create(current.Naxis(1), current.Naxis(2));
			factor = 0.0;
		}
		findmax.copy(current);
		if (has_mask == 1) findmax *= mask;
		findmax.max_pos(&xmax, &ymax);
		dp_output("    dx = %i, dy = %i\n", x-xmax, y-ymax);
		current.shift(x-xmax, y-ymax, 2);
		findmax = 1.0;
		findmax.shift(x-xmax, y-ymax, 2);
		factor.add(findmax);
		if (i == first) {
			naxis[1] = current.naxis[1];
			naxis[2] = current.naxis[2];
			membits = current.membits;
			allocateMemory();
			copy(current);
		} else {
			add(current);
		}
	}
	div(factor);
}

bool Fits::qssa(const char *fname, int x, int y, int method, Fits *sky, Fits *flat, Fits *dpl, Fits *mask)
{
	Fits current, sum, findmax, factor;
	int i, xmax, ymax, c;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
	double fxmax, fymax;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;

	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
	current.getHeaderInformation();
	c = current.Naxis(3);

	for (i = 1; i <= c; i++) {
        if (!current.ReadFITSCubeImage(i)) return closeAndReturnFALSE(current);
		if (has_sky == 1) current.sub(*sky);
		if (has_flat == 1) current.mul(*flat);
		if (has_dpl == 1) DeadpixApply(current, *dpl);
		if (i == 1) {
			factor.create(current.Naxis(1), current.Naxis(2));
			factor = 0.0;
			findmax.create(current.Naxis(1), current.Naxis(2));
		}
		findmax.copy(current);
		if (method < 0) {
			findmax.smooth(-(float)method);
		}
		if (method <= 0) {
			if (has_mask == 1) findmax.max_pos_in_mask(&xmax, &ymax, *mask);
			else findmax.max_pos(&xmax, &ymax);
			dp_output("Reading image no. %5i...    dx = %4i, dy = %4i\n", i, x-xmax, y-ymax);
			current.shift(x - xmax, y - ymax, 2);
			findmax = 1.0;
			findmax.shift(x - xmax, y - ymax, 2);
		} else {
			if (has_mask == 1) findmax.mul(*mask);
			findmax.centroid(&fxmax, &fymax);
			findmax = 1.0;
			if (method == 1) {
				xmax = (int)(fxmax + 0.5);
				ymax = (int)(fymax + 0.5);
				dp_output("Reading image no. %5i...    dx = %4i, dy = %4i\n", i, x-xmax, y-ymax);
				current.shift(x - xmax, y - ymax, 2);
				findmax.shift(x - xmax, y - ymax, 2);
			} else {
				dp_output("Reading image no. %5i...    dx = %6.2f, dy = %6.2f\n", i, x-fxmax, y-fymax);
				current.fshift(x - fxmax, y - fymax, 2);
				findmax.fshift(x - fxmax, y - fymax, 2);
			}
		}
		factor.add(findmax);
		if (i == 1) {
			copy(current);
		} else {
			add(current);
		}
		if (FitsInterrupt) {
			dp_output("SSA interrupted after %i images\n", i);
			break;
		}
	}

    current.CloseFITS();
	factor.czero(1.0);
	div(factor);
	
	return TRUE;
}

void Fits::qssa(Fits & cube, int x, int y, int method, Fits *sky, Fits *flat, Fits *dpl, Fits *mask)
{
	Fits current, sum, findmax, factor;
	long i, j, k;
	int xmax, ymax, c;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
	double fxmax, fymax;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;

// 	current.naxis1 = cube.naxis1;
// 	current.naxis2 = cube.naxis2;
// 	current.naxis = 2;
	c = cube.naxis[1] * cube.naxis[2];
// 	current.bitpix = cube.bitpix;
// 	current.get_mem();
	current.create(cube.Naxis(1), cube.Naxis(2));
	current.CopyHeader(cube);
	current.updateHeader();
	for (i = 1; i <= cube.naxis[3]; i++) {
		for (j = 1; j <= current.naxis[1]; j++) {
			for (k = 1; k <= current.naxis[2]; k++) {
				current.r4data[current.F_I(j, k)] = (float)(cube.ValueAt(current.F_I(j, k) + (i - 1) * c));
			}
		}
		if (has_sky == 1) current.sub(*sky);
		if (has_flat == 1) current.mul(*flat);
		if (has_dpl == 1) DeadpixApply(current, *dpl);
		if (i == 1) {
			findmax.create(current.Naxis(1), current.Naxis(2));
			factor.create(current.Naxis(1), current.Naxis(2));
			factor = 0.0;
		}
		findmax.copy(current);
		if (method < 0) {
			findmax.smooth(-(float)method);
		}
		if (method <= 0) {
			if (has_mask == 1) findmax.max_pos_in_mask(&xmax, &ymax, *mask);
			else findmax.max_pos(&xmax, &ymax);
			dp_output("Reading image no. %5i...    dx = %4i, dy = %4i\n", i, x-xmax, y-ymax);
			current.shift(x - xmax, y - ymax, 2);
			findmax = 1.0;
			findmax.shift(x - xmax, y - ymax, 2);
		} else {
			if (has_mask == 1) findmax.mul(*mask);
			findmax.centroid(&fxmax, &fymax);
			findmax = 1.0;
			if (method == 1) {
				xmax = (int)(fxmax + 0.5);
				ymax = (int)(fymax + 0.5);
				dp_output("Reading image no. %5i...    dx = %4i, dy = %4i\n", i, x-xmax, y-ymax);
				current.shift(x - xmax, y - ymax, 2);
				findmax.shift(x - xmax, y - ymax, 2);
			} else {
				dp_output("Reading image no. %5i...    dx = %6.2f, dy = %6.2f\n", i, x-fxmax, y-fymax);
				current.fshift(x - fxmax, y - fymax, 2);
				findmax.fshift(x - fxmax, y - fymax, 2);
			}
		}
		factor.add(findmax);
		if (i == 1) {
			copy(current);
		} else {
			add(current);
		}
		if (FitsInterrupt) {
			dp_output("SSA interrupted after %li images\n", i);
			break;
		}
	}
	factor.czero(1.0);
	div(factor);
}

bool Fits::ShiftAddIntoBigger(Fits &what, int xshift, int yshift) {
	int x0, x1, y0, y1, x, y, xs, ys;

	if ((membits != R4) || (what.membits != R4)) return FALSE;
	x0 = xshift;
	y0 = yshift;
	x1 = xshift + what.Naxis(1);
	y1 = yshift + what.Naxis(2);

	for (x = x0, xs = 0; x < x1; x++, xs++) {
		if ((x >= 0) && (x < Naxis(1))) {
			for (y = y0, ys = 0; y < y1; y++, ys++) {
				if ((y >= 0) && (y < Naxis(2))) {
					r4data[C_I(x, y)] += what.r4data[what.C_I(xs, ys)];
				}
			}
		}
	}
	return TRUE;
}

void Fits::rssa(char *fname, int x, int y, int method, Fits *sky, Fits *flat, Fits *dpl, Fits *mask)
{
	Fits current, sum, findmax, factor;
	int i, xmax, ymax, c;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
	double fxmax, fymax;
	FILE *lf;
	char fff[256];

	lf = fopen(fname, "r+b");
	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;

	i = 0;
	c = 0;
	strcpy(fff, "");
	do {
		c = fscanf(lf, "%s", fff);
		if (c != EOF) {
			i++;
			current.ReadFITS(fff);
			if (has_sky == 1) current.sub(*sky);
			if (has_flat == 1) current.mul(*flat);
			if (has_dpl == 1) DeadpixApply(current, *dpl);
			if (i == 1) {
				findmax.create(current.Naxis(1), current.Naxis(2));
				factor.create(current.Naxis(1), current.Naxis(2));
				factor = 0.0;
			}
			findmax.copy(current);
			if (has_mask == 1) findmax *= *mask;
			if (method > 0) findmax.centroid(&fxmax, &fymax);
			else findmax.max_pos(&xmax, &ymax);
			if (method < 2) {
				xmax = (int)(fxmax + 0.5);
				ymax = (int)(fymax + 0.5);
				dp_output("Reading image %s...    dx = %i, dy = %i\n", fff, x-xmax, y-ymax);
				current.shift((float)(x-xmax), (float)(y-ymax), 2);
				findmax = 1.0;
				findmax.shift((float)(x-xmax), (float)(y-ymax), 2);
			} else {
				dp_output("Reading image %s...    dx = %f, dy = %f\n", fff, x-fxmax, y-fymax);
				current.shift((float)(x-fxmax), (float)(y-fymax), 0);
				findmax = 1.0;
				findmax.shift((float)(x-fxmax), (float)(y-fymax), 0);
			}
			factor.add(findmax);
			if (i == 1) {
                naxis[0] = 2;
                naxis[1] = current.Naxis(1);
                naxis[2] = current.Naxis(2);
				membits = current.membits;
				allocateMemory();
				copy(current);
			} else {
				add(current);
			}
		}
	} while (c != EOF);
	fclose(lf);
	div(factor);
}

bool Fits::ssastat(const char *fname, int x, int y, int method, Fits *sky, Fits *flat, Fits *dpl, Fits *mask)
{
	Fits current, findmax;
	int i, j, k, xmax, ymax, c, width, ii, kk;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
	double fxmax, fymax, min, max, flux, avg, fwhm, f, FFLUX;
	double sum21, sum22, xshift, yshift;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;

	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
	current.getHeaderInformation();
	c = current.Naxis(3);
    if (!create(7, c)) return closeAndReturnFALSE(current);

	for (i = 1; i <= c; i++) {
        if (!current.ReadFITSCubeImage(i)) return closeAndReturnFALSE(current);
		if (has_sky == 1) current.sub(*sky);
		if (has_flat == 1) current.mul(*flat);
		if (has_dpl == 1) DeadpixApply(current, *dpl);
		current.makeStat(&min, &max, &flux, &avg);
		findmax.copy(current);
		if (method < 0) {
			findmax.smooth(-(float)method);
		}
		if (method <= 0) {
			if (has_mask == 1) findmax.max_pos_in_mask(&xmax, &ymax, *mask);
			else findmax.max_pos(&xmax, &ymax);
			fxmax = (float)xmax;
			fymax = (float)ymax;
		} else {
			if (has_mask == 1) findmax.mul(*mask);
			findmax.centroid(&fxmax, &fymax);
			if (method == 1) {
				xmax = (int)(fxmax + 0.5);
				ymax = (int)(fymax + 0.5);
				fxmax = (float)xmax;
				fymax = (float)ymax;
			}
		}
		xshift = x - fxmax;
		yshift = y - fymax;
		
		width = 20;
		if (xmax + width > current.naxis[1]) width = current.naxis[1] - xmax;
		if (ymax + width > current.naxis[2]) width = current.naxis[2] - ymax;
		if (xmax - width <= 0) width = xmax - 1;
		if (ymax - width <= 0) width = ymax - 1;

		sum21 = sum22 = FFLUX = 0.0;
		for (j = xmax - width; j <= xmax + width; j++) {
			ii = j - xmax;
	    for (k = ymax - width; k <= ymax + width; k++) {
				f  = current.ValueAt(current.F_I(j, k));
				FFLUX += f;
				kk = k - ymax;
				sum21 += f * ii * ii;
				sum22 += f * kk * kk;
			}
		}
		fwhm = 1.6651092 * sqrt(fabs(sum21/FFLUX + sum22/FFLUX));
		
		r4data[F_I(1, i)] = xshift;
		r4data[F_I(2, i)] = yshift;
		r4data[F_I(3, i)] = min;
		r4data[F_I(4, i)] = max;
		r4data[F_I(5, i)] = flux;
		r4data[F_I(6, i)] = fwhm;
		r4data[F_I(7, i)] = 1.0;
		dp_output("Frame %4i: sx=%.1f sy=%.1f min=%.1f max=%.1f flux=%.1f fwhm=%.1f\n", i, xshift, yshift, min, max, flux, fwhm);
		if (FitsInterrupt) {
			dp_output("SSASTAT interrupted after %i images\n", i);
			break;
		}
	}
	current.CloseFITS();

	return TRUE;
}

/*!
Perform Simple Shift-and-Add using previously calculated statistics
*/

bool Fits::sssa(const char *fname, Fits stats, int method, Fits *sky, Fits *flat, Fits *dpl, Fits *mask)
{
	Fits current, sum, findmax, factor;
	int i, xmax, ymax, c;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
	float fxmax, fymax;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;

	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
	current.getHeaderInformation();
	c = current.Naxis(3);
	if (c != stats.naxis[2]) {
		dp_output("Statistics don't fit given Fits cube.\n");
        return closeAndReturnFALSE(current);
	}

	factor.create(current.Naxis(1), current.Naxis(2));
	factor = 0.0;
	findmax.create(current.Naxis(1), current.Naxis(2));
	findmax = 1.0;
	create(current.Naxis(1), current.Naxis(2));
	for (i = 1; i <= c; i++) {
		dp_output("Reading image no. %i...", i);
        if (!current.ReadFITSCubeImage(i)) return closeAndReturnFALSE(current);
		if (stats.ValueAt(stats.F_I(7, i)) == 1.0) {
			if (has_sky == 1) current.sub(*sky);
			if (has_flat == 1) current.mul(*flat);
			if (has_dpl == 1) DeadpixApply(current, *dpl);
			findmax = 1.0;
			fxmax = stats.ValueAt(stats.F_I(1, i));
			fymax = stats.ValueAt(stats.F_I(2, i));
			xmax = (int)fxmax;
			ymax = (int)fymax;
			if (method < 2) {
				if (method == 1) {
					xmax = (int)(fxmax + 0.5);
					ymax = (int)(fxmax + 0.5);
				}
				dp_output("    dx = %4i, dy = %4i\n", xmax, ymax);
				current.shift((float)(xmax), (float)(ymax), 2);
				findmax.shift((float)(xmax), (float)(ymax), 2);
			} else {
				dp_output("    dx = %f, dy = %f\n", fxmax, fymax);
				current.shift((float)(fxmax), (float)(fymax), 0);
				findmax.shift((float)(fxmax), (float)(fymax), 0);
			}
			factor.add(findmax);
			add(current);
		} else dp_output("rejected\n");
		if (FitsInterrupt) {
			dp_output("SSA interrupted after %i images\n", i);
			break;
		}
	}

    current.CloseFITS();
	div(factor);
	
	return TRUE;
}

int Fits::wsamaxfind(Fits &result, Fits &mask, int xcen, int ycen, int spnum, float hthresh, int smooth) {
	int nmax = 0;
	long x1, x2, y1, y2, i, j;
	int i1, j1;
	double max, lmax;
	Fits spg;
	bool done;

	if (spnum < 1) return 0;
	mask.limits(&x1, &x2, &y1, &y2);
	if (!result.create(naxis[1], naxis[2])) return 0;

	done = FALSE;

	while (!done) {
		nmax = 0;
		if (!spg.copy(*this)) return 0;
		if (!spg.mul(mask)) return 0;
		if (smooth) if (!spg.smooth((float)smooth)) return 0;
		result = 0.0;
		max = spg.get_max();
//		spg.max_pos(&xmax, &ymax);
//		printf("wsafindmax: %f at [%i, %i]\n", max, xmax, ymax);

		for (i = x1 + 1; i < x2; i++) {
			for (j = y1 + 1; j < y2; j++) {
				lmax = spg.ValueAt(spg.F_I(i, j));
				i1 = i;
				j1 = j;
				if (lmax > hthresh * max) {
					spg.max_pixel(&i1, &j1, 1);
					if ((i == i1) && (j == j1)) {
						result.r4data[result.F_I(i, j)] = lmax / max;
//						printf("local max %f at %i %i\n", lmax, i, j);
						nmax++;
					}
				}
			}
		}
		done = nmax <= spnum;
		if (!done) {
			smooth += 3;
		}
	}
	if (!result.shift(naxis[1] / 2 + 1 - xcen, naxis[2] / 2 + 1 - ycen, 2)) return 0;

//	printf("wsa: Number of speckles = %i\n", nmax);
	return nmax;
}

bool Fits::wsa(const char *fname, int x, int y, float thresh, int spnum, int smooth, Fits *sky, Fits *flat, Fits *dpl, Fits *mask, Fits *bigmask, Fits *flag)
{
	Fits current, sum, findmax, pfindmax, shiftmask;
	int i, c, n, first, doit;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0, has_bigmask = 0, has_flag = 0;
	double fxmax, fymax, maskxcen, maskycen;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;
	if (bigmask != NULL) has_bigmask = 1;
	if (flag != NULL) has_flag = 1;

	if (has_mask == 0) return FALSE;

	mask->centroid(&maskxcen, &maskycen);

	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
	current.getHeaderInformation();
	c = current.Naxis(3);

	if (has_flag) {
		if (c != (long)flag->Nelements()) {
			dp_output("Fits::wsa: flags does not match in size\n");
            return closeAndReturnFALSE(current);
		}
	}

	first = 1;
	for (i = 1; i <= c; i++) {
		if (has_flag) doit = (flag->ValueAt(i - 1) > 0.0);
		else doit = 1;
		if (doit) {
            if (!current.ReadFITSCubeImage(i)) return closeAndReturnFALSE(current);
			if (has_sky == 1) current.sub(*sky);
			if (has_flat == 1) current.mul(*flat);
			if (has_dpl == 1) DeadpixApply(current, *dpl);
			findmax.copy(current);
			if (has_bigmask) {
				shiftmask.copy(*mask);
				findmax.mul(*bigmask);
				findmax.centroid(&fxmax, &fymax);
				shiftmask.shift((int)(fxmax - maskxcen), (int)(fymax - maskycen), 2);
				n = current.wsamaxfind(findmax, shiftmask, x, y, spnum, thresh, smooth);
			} else {
				n = current.wsamaxfind(findmax, *mask, x, y, spnum, thresh, smooth);
			}
			dp_output("wsa: Reading image no. %5i... number of speckles = %i\n", i, n); 
			if (n > 0) {
				findmax.fft();
				pfindmax.copy(findmax);
				pfindmax.conj();
				findmax.ri_pow();
				current.fft();
				current.mul(pfindmax);
			}
			if (first) {
				copy(current);
				sum.copy(findmax);
				first = 0;
			} else {
				add(current);
				sum.add(findmax);
			}
		}
		if (FitsInterrupt) {
			dp_output("WSA interrupted after %i images\n", i);
			break;
		}
	}
	current.CloseFITS();
	div(sum);
	fft();
	div((float)Nelements());
	reass();
	
	return TRUE;
}

bool Fits::wsastat(const char *fname, int x, int y, float thresh, int spnum, int smooth, Fits *sky, Fits *flat, Fits *dpl, Fits *mask, Fits *bigmask, Fits *flag)
{
	Fits current, sum, findmax, pfindmax, shiftmask;
	int i, c, n, first, doit;
	int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0, has_bigmask = 0, has_flag = 0;
	double fxmax, fymax, maskxcen, maskycen;

	if (sky != NULL) has_sky = 1;
	if (flat != NULL) has_flat = 1;
	if (dpl != NULL) has_dpl = 1;
	if (mask != NULL) has_mask = 1;
	if (bigmask != NULL) has_bigmask = 1;
	if (flag != NULL) has_flag = 1;

	if (has_mask == 0) return FALSE;

	mask->centroid(&maskxcen, &maskycen);

	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 0) return closeAndReturnFALSE(current);
	current.getHeaderInformation();
	c = current.Naxis(3);

	if (has_flag) {
		if (c != (long)flag->Nelements()) {
			dp_output("Fits::wsa: flags does not match in size\n");
            return closeAndReturnFALSE(current);
		}
	}

    if (!create(c, 1, I2)) return closeAndReturnFALSE(current);
	first = 1;
	for (i = 1; i <= c; i++) {
		if (has_flag) doit = (flag->ValueAt(i - 1) > 0.0);
		else doit = 1;
		if (doit) {
            if (!current.ReadFITSCubeImage(i)) return closeAndReturnFALSE(current);
			if (has_sky == 1) current.sub(*sky);
			if (has_flat == 1) current.mul(*flat);
			if (has_dpl == 1) DeadpixApply(current, *dpl);
			findmax.copy(current);
			if (has_bigmask) {
				shiftmask.copy(*mask);
				findmax.mul(*bigmask);
				findmax.centroid(&fxmax, &fymax);
				shiftmask.shift((int)(fxmax - maskxcen), (int)(fymax - maskycen), 2);
				n = current.wsamaxfind(findmax, shiftmask, x, y, spnum, thresh, smooth);
			} else {
				n = current.wsamaxfind(findmax, *mask, x, y, spnum, thresh, smooth);
			}
			dp_output("wsastat: Reading image no. %5i... number of speckles = %i\n", i, n);
			i2data[i-1] = n;
		}
		if (FitsInterrupt) {
			dp_output("WSAstat interrupted after %i images\n", i);
			break;
		}
	}
	current.CloseFITS();
	
	return TRUE;
}

void Fits::makeflat(char *skyname, char *darkname)
{
	Fits dark;
    dpint64 i;
	
	if (!dark.ReadFITS(darkname)) return;
	if (!ReadFITS(skyname)) return;
	sub(dark);
//	div(get_flux() / (float)n_elements);
	div(get_median());
	if (!setType(R4)) return;
	for (i = 0; i < n_elements; i++) {
		if (r4data[i] < EPS) r4data[i] = 1.0;
		else r4data[i] = 1.0 / r4data[i];
	}
}

/*!
The number of bytes for cube_median
*/

//#define MEDIAN_MEM 1024*1024*40

/*!
Take a fits cube and do a median of the images therein.
This version reads parts of the single images consecutively
from disk in order to save memory.
(the maximum amount of memory allocated is define below.)
*/

bool cube_median(char *fname, Fits & result, int first, int last)
{
	dp_output("Cubemedian needs to be implemented\n");
	return FALSE;

/* 	float *tmpbuffer, *sortbuffer;
	int i, j, m, nfiles, nrows, numfiles, status, anynull, k;
	long n[4], lowerleft[3], upperright[3], inc[4];
	Fits current;

	status = 0;
	inc[0] = inc[1] = inc[2] = inc[3] = 1;
	
// find out how big the array is
	if (fits_open_file(&fptr, fname, READONLY, &status)) {
		fits_report_error(stderr, status);
		return FALSE;
	}
	for (i = 0; i < 4; i++) n[i] = 0;
	if (fits_read_keys_lng(fptr, "NAXIS", 1, 3, &n[1], &m, &status)) {
		fits_report_error(stderr, status);
		return FALSE;
	}
	n[0] = m;
	numfiles = n[3];
	if (!result.create(n[1], n[2])) return FALSE;
	
	nfiles = last - first + 1;
	if (last < first) nfiles = numfiles - first + 1;
	if (FitsVerbose) printf("Doing median of %i images...\n", nfiles);
	
	nrows = 1;
	
// allocate no more than MEDIAN_MEM
	
	while (nrows*nfiles*n[1]*sizeof(float) < MEDIAN_MEM) nrows *= 2;
	if (nrows > n[2]) nrows = n[2];
	if (FitsVerbose) printf("Doing %i rows at a time\n", nrows);
	tmpbuffer = (float *)malloc(nrows * n[1] * nfiles * sizeof(float));
	sortbuffer = (float *)malloc(nfiles * sizeof(float));
	lowerleft[2] = first;
	upperright[2] = first + nfiles - 1;
	lowerleft[0] = 1;
	upperright[0] = n[1];
// now go through array and read data
	for (m = 0; m < n[2]; m += nrows) {
		if (FitsVerbose) printf("...at row %i\n", m);
		lowerleft[1] = m + 1;
		upperright[1] = lowerleft[1] + nrows - 1;
		if (fits_read_subset_flt(fptr, 1, 3, &n[1], lowerleft, upperright, inc, nullval, tmpbuffer, &anynull, &status)) {
			fits_report_error(stderr, status);
			return FALSE;
		}
		printf(".done reading\n");
 */
/*	Sort the arrays and take the value in the middle --> median
 *  Remember: No use in fiddling around with which value to take
 *  if the sky background for median sky generation varies rapidly,
 *  you will have a too high (or low) value of sky on positions where
 *  a star is in some of the frames. Discussed it with Hacki and found
 *  a solution: You need to know how the sky varies, then you can take
 *  into account that situation. I did not do this here because it is
 *  highly non-trivial.
 *
 *  There is another solution which makes pretty pictures but at the
 *  cost of a higher background: Instead of taking the median, you
 *  can take the minimum of all values (i.e. replace in the 3rd line
 *  tmpbuffer[i*nfiles+nfiles/2-1] with tmpbuffer[i*nfiles]. This is
 *  performed in the following routine.
 */
/* 		for (i = 0; i < n[1]; i++) {
			for (j = m; j < m + nrows; j++) {
				for (k = 0; k < nfiles; k++) {
					sortbuffer[k] = tmpbuffer[i + (j - m) * n[1] + k * n[1] * n[2]];
				}
				if (nfiles % 2)
					result.data[result.C_I(i, j)] = nr_select(nfiles / 2 + 1, nfiles, sortbuffer - 1);
				else
					result.data[result.C_I(i, j)] = (nr_select(nfiles / 2, nfiles, sortbuffer - 1) + 
																					nr_select(nfiles / 2 + 1, nfiles, sortbuffer - 1)) / 2.0;
			}
		}
	}
	free(tmpbuffer);
	free(sortbuffer);
	if (fits_close_file(fptr, &status)) {
		fits_report_error(stderr, status);
		return FALSE;
	}

	return TRUE;
 */}

/*!
Go through the cube and select the minimum of each pixel in the 3rd dimension. Returns an image.
*/

bool Fits::CubeMinimum(const Fits & cube) {
    dpint64 z;
    dpint64 z1, z2;

	if (cube.naxis[3] < 2) {
		if (!copy(cube)) return FALSE;
		return TRUE;
	}
	if (!create(cube.naxis[1], cube.naxis[2], cube.membits)) return FALSE;
	CopyHeader(cube);
	bscale = cube.bscale;
	bzero = cube.bzero;

	memcpy(dataptr, cube.dataptr, n_elements * abs((int)membits / 8));

	for (z = 1; z < cube.naxis[3]; z++) {
		z2 = n_elements * z;
		for (z1 = 0; z1 < n_elements; z1++) {
			switch (membits) {
				case I1: if (cube.i1data[z2] < i1data[z1]) i1data[z1] = cube.i1data[z2]; break;
				case I2: if (cube.i2data[z2] < i2data[z1]) i2data[z1] = cube.i2data[z2]; break;
				case I4: if (cube.i4data[z2] < i4data[z1]) i4data[z1] = cube.i4data[z2]; break;
                case I8: if (cube.i8data[z2] < i8data[z1]) i8data[z1] = cube.i8data[z2]; break;
                case R4: if (gsl_finite(cube.r4data[z2])) if (cube.r4data[z2] < r4data[z1]) r4data[z1] = cube.r4data[z2]; break;
				case R8: if (gsl_finite(cube.r8data[z2])) if (cube.r8data[z2] < r8data[z1]) r8data[z1] = cube.r8data[z2]; break;
				default: return FALSE; break;
			}
			z2++;
		}
	}
	return TRUE;
}

/*!
Go through the cube and select the maximum of each pixel in the 3rd dimension. Returns an image.
*/

bool Fits::CubeMaximum(const Fits & cube) {
    dpint64 z;
    dpint64 z1, z2;

	if (cube.naxis[3] < 2) {
		if (!copy(cube)) return FALSE;
		return TRUE;
	}
	if (!create(cube.naxis[1], cube.naxis[2], cube.membits)) return FALSE;
	CopyHeader(cube);
	bscale = cube.bscale;
	bzero = cube.bzero;

	memcpy(dataptr, cube.dataptr, n_elements * abs((int)membits / 8));

	for (z = 1; z < cube.naxis[3]; z++) {
		z2 = n_elements * z;
		for (z1 = 0; z1 < n_elements; z1++) {
			switch (membits) {
				case I1: if (cube.i1data[z2] > i1data[z1]) i1data[z1] = cube.i1data[z2]; break;
				case I2: if (cube.i2data[z2] > i2data[z1]) i2data[z1] = cube.i2data[z2]; break;
                case I4: if (cube.i4data[z2] > i4data[z1]) i4data[z1] = cube.i4data[z2]; break;
                case I8: if (cube.i8data[z2] > i8data[z1]) i8data[z1] = cube.i8data[z2]; break;
                case R4: if (gsl_finite(cube.r4data[z2])) if (cube.r4data[z2] > r4data[z1]) r4data[z1] = cube.r4data[z2]; break;
				case R8: if (gsl_finite(cube.r8data[z2])) if (cube.r8data[z2] > r8data[z1]) r8data[z1] = cube.r8data[z2]; break;
				default: return FALSE; break;
			}
			z2++;
		}
	}
	return TRUE;
}

/*!
Go through the cube and add all pixels up. Returns an image.
*/

bool Fits::CubeSum(const Fits & cube, int first, int last) {
	long x, y;
	int z;
	
	if (cube.Naxis(0) != 3) {
		return copy(cube);
	}
	if (!create(cube.Naxis(1), cube.Naxis(2))) return FALSE;
	CopyHeader(cube);
	
	if (last < first) last = cube.Naxis(3);
	if (first < 1) first = 1;
	if (first > cube.Naxis(3)) first = cube.Naxis(3);
	if (last < 1) last = 1;
	if (last > cube.Naxis(3)) last = cube.Naxis(3);

	for (x = 1; x <= cube.Naxis(1); x++) {
		for (y = 1; y <= cube.Naxis(2); y++) {
			for (z = first; z <= last; z++) {
				r4data[at(x, y)] += cube.ValueAt(cube.at(x, y, z));
			}
		}
		if (FitsInterrupt) return FALSE;
	}

	return TRUE;
}

/*!
Go through the cube and select the median of each pixel in the 3rd dimension. Returns an image.
*/

bool Fits::CubeMedian(const Fits & cube) {
    long x, y, z, count;
    float *sortbuffer;
    double value;

    if (!create(cube.naxis[1], cube.naxis[2])) return FALSE;
    CopyHeader(cube);
    if ((sortbuffer = (float *)malloc(sizeof(float) * cube.naxis[3])) == NULL)
        return FALSE;

    dpProgress(-cube.Naxis(1), "CubeMedian");
    for (x = 1; x <= naxis[1]; x++) {
        dpProgress(x, "");
        for (y = 1; y <= naxis[2]; y++) {
            count = 0;
            for (z = 1; z <= cube.naxis[3]; z++) {
                value = (cube.ValueAt(cube.F_I(x, y, z)));
                if (gsl_finite(value)) {
                    sortbuffer[count] = value;
                    count++;
                }
            }
            if (count == 0) r4data[F_I(x, y)] = 0.0;
            else if (count == 1) r4data[F_I(x, y)] = sortbuffer[0];
            else if (count == 2) r4data[F_I(x, y)] = (sortbuffer[0] + sortbuffer[1]) / 2.0;
            else {
                gsl_sort_float(sortbuffer, 1, count);
                r4data[F_I(x, y)] = gsl_stats_float_median_from_sorted_data(sortbuffer, 1, count);
            }
//            else if (count % 2)
//                    r4data[F_I(x, y)] = nr_select(count / 2 + 1, count, sortbuffer - 1);
//            else
//                    r4data[F_I(x, y)] = (nr_select(count / 2, count, sortbuffer - 1) +
//                                         nr_select(count / 2 + 1, count, sortbuffer - 1)) / 2.0;
        }
    }
    free(sortbuffer);
    return TRUE;
}

/*!
Go through the cube and select the median of each pixel in the 3rd dimension. Returns an image.
Values which correspond to reject are discarded.
*/

bool Fits::CubeMedian(const Fits & cube, float reject) {
	long x, y, z, count;
	float *sortbuffer;
	double value;

	if (!create(cube.naxis[1], cube.naxis[2])) return FALSE;
	CopyHeader(cube);
	if ((sortbuffer = (float *)malloc(sizeof(float) * cube.naxis[3])) == NULL)
		return FALSE;
	
	dpProgress(-cube.Naxis(1), "CubeMedian");
	for (x = 1; x <= naxis[1]; x++) {
		dpProgress(x, "");
		for (y = 1; y <= naxis[2]; y++) {
			count = 0;
			for (z = 1; z <= cube.naxis[3]; z++) {
				value = (cube.ValueAt(cube.F_I(x, y, z)));
				if ((value != reject) && gsl_finite(value)) {
					sortbuffer[count] = value;
					count++;
				}
			}
			if (count == 0) r4data[F_I(x, y)] = 0.0;
			else if (count == 1) r4data[F_I(x, y)] = sortbuffer[0];
			else if (count == 2) r4data[F_I(x, y)] = (sortbuffer[0] + sortbuffer[1]) / 2.0;
			else {
				gsl_sort_float(sortbuffer, 1, count);
				r4data[F_I(x, y)] = gsl_stats_float_median_from_sorted_data(sortbuffer, 1, count);
			}
//			else if (count % 2)
//				r4data[F_I(x, y)] = nr_select(count / 2 + 1, count, sortbuffer - 1);
//			else
//				r4data[F_I(x, y)] = (nr_select(count / 2, count, sortbuffer - 1) +
//													nr_select(count / 2 + 1, count, sortbuffer - 1)) / 2.0;
		}
	}
	free(sortbuffer);
	return TRUE;
}

/*!
The number of bytes for cube_median
*/

//#define MEDIAN_MEM 1024*1024*40
long MEDIAN_MEM = 1024 * 1024 * 10 * 2;

/*!
Take a fits cube and do a median of the images therein.
This version reads parts of the single images consecutively
from disk in order to save memory.
(the maximum amount of memory allocated is define below.)
*/

bool Fits::CubeMedian(const char *fname, int first, int last) {
 	float *sortbuffer;
	int i, j, m, nfiles, nrows, numfiles, k;
	long n[4], lowerleft[3], upperright[3], inc[4];
	Fits current;

	status = 0;
	inc[0] = inc[1] = inc[2] = inc[3] = 1;
	
// find out how big the array is
	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 1) return closeAndReturnFALSE(current);
    if (!current.getHeaderInformation()) return closeAndReturnFALSE(current);
	for (i = 0; i < 4; i++) n[i] = current.naxis[i];
	numfiles = n[3];
    if (!create(n[1], n[2])) return closeAndReturnFALSE(current);
	CopyHeader(current);
	
	nfiles = last - first + 1;
	if (last < first) nfiles = numfiles - first + 1;
	dp_output("Doing median of %i images ", nfiles);
	fflush(stdout);
	
	nrows = 1;

// allocate no more than MEDIAN_MEM
	
        while (nrows*nfiles*n[1]*(long)sizeof(double) < MEDIAN_MEM) nrows *= 2;
    if (nrows > n[2]) nrows = n[2];
    if (!current.allocateMemory(nrows*nfiles*n[1]*sizeof(double))) return closeAndReturnFALSE(current);
	sortbuffer = (float *)malloc(nfiles * sizeof(float));
	lowerleft[2] = first;
	upperright[2] = first + nfiles - 1;
	lowerleft[0] = 1;
	upperright[0] = n[1];
	dp_output("doing %i rows at a time\n", nrows);
// now go through array and read data
	for (m = 0; m < n[2]; m += nrows) {
//        current.dpseek(current.hdr, SEEK_SET);
		dp_output(".at row %i\n", m);
		fflush(stdout);
		lowerleft[1] = m + 1;
		upperright[1] = lowerleft[1] + nrows - 1;
		if (upperright[1] > n[2]) {
			upperright[1] = n[2];
			nrows = upperright[1] - lowerleft[1] + 1;
		}
        if (!current.ReadFITSData(lowerleft[0], upperright[0], lowerleft[1], upperright[1], lowerleft[2], upperright[2])) return closeAndReturnFALSE(current);

/*	Sort the arrays and take the value in the middle --> median
 *  Remember: No use in fiddling around with which value to take
 *  if the sky background for median sky generation varies rapidly,
 *  you will have a too high (or low) value of sky on positions where
 *  a star is in some of the frames. Discussed it with Hacki and found
 *  a solution: You need to know how the sky varies, then you can take
 *  into account that situation. I did not do this here because it is
 *  highly non-trivial.
 *
 *  There is another solution which makes pretty pictures but at the
 *  cost of a higher background: Instead of taking the median, you
 *  can take the minimum of all values (i.e. replace in the 3rd line
 *  tmpbuffer[i*nfiles+nfiles/2-1] with tmpbuffer[i*nfiles]. This is
 *  performed in the following routine.
 */
 		for (i = 0; i < n[1]; i++) {
			for (j = m; j < m + nrows; j++) {
				if (j < n[2]) {
					for (k = 0; k < nfiles; k++) {
						sortbuffer[k] = (float)(current.ValueAt(i + (j - m) * n[1] + k * n[1] * nrows));
					}
					gsl_sort_float(sortbuffer, 1, nfiles);
					r4data[C_I(i, j)] = gsl_stats_float_median_from_sorted_data(sortbuffer, 1, nfiles);
//					if (nfiles % 2)
//						r4data[C_I(i, j)] = nr_select(nfiles / 2 + 1, nfiles, sortbuffer - 1);
//					else
//						r4data[C_I(i, j)] = (nr_select(nfiles / 2, nfiles, sortbuffer - 1) +
//																					nr_select(nfiles / 2 + 1, nfiles, sortbuffer - 1)) / 2.0;
				}
			}
		}
		if (upperright[1] == n[2]) break;
	}
	free(sortbuffer);
	current.CloseFITS();
	dp_output("\n");
//	dpFitsProgress();

	return TRUE;
}

/*!
Take a fits cube and do a median of the images therein.
This version reads parts of the single images consecutively
from disk in order to save memory.
(the maximum amount of memory allocated is define below.)
*/

bool Fits::CubeMedian(const char *fname, double reject) {
 	float *sortbuffer;
	int i, j, m, nfiles, nrows, numfiles, k;
	long n[4], lowerleft[3], upperright[3], inc[4], nvalues;
	Fits current;
	double value;

	status = 0;
	inc[0] = inc[1] = inc[2] = inc[3] = 1;
	
// find out how big the array is
	if (!current.OpenFITS(fname)) return FALSE;
    if ((current.hdr = current.ReadFitsHeader()) < 1) return closeAndReturnFALSE(current);
    if (!current.getHeaderInformation()) return closeAndReturnFALSE(current);
	for (i = 0; i < 4; i++) n[i] = current.naxis[i];
	numfiles = n[3];
    if (!create(n[1], n[2])) return closeAndReturnFALSE(current);
	CopyHeader(current);
	
	nfiles = n[3];
	dp_output("Doing median of %i images ", nfiles);
	fflush(stdout);
	
	nrows = 1;

// allocate no more than MEDIAN_MEM
	
        while (nrows*nfiles*n[1]*(long)sizeof(double) < MEDIAN_MEM) nrows *= 2;
    if (nrows > n[2]) nrows = n[2];
    if (!current.allocateMemory(nrows*nfiles*n[1]*sizeof(double))) return closeAndReturnFALSE(current);
	sortbuffer = (float *)malloc(nfiles * sizeof(float));
	lowerleft[2] = 1;
	upperright[2] = n[3];
	lowerleft[0] = 1;
	upperright[0] = n[1];
	dp_output("doing %i rows at a time\n", nrows);
// now go through array and read data
	for (m = 0; m < n[2]; m += nrows) {
//        current.dpseek(current.hdr, SEEK_SET);
		dp_output(".at row %i\n", m);
		fflush(stdout);
		lowerleft[1] = m + 1;
		upperright[1] = lowerleft[1] + nrows - 1;
		if (upperright[1] > n[2]) {
			upperright[1] = n[2];
			nrows = upperright[1] - lowerleft[1] + 1;
		}
        if (!current.ReadFITSData(lowerleft[0], upperright[0], lowerleft[1], upperright[1], lowerleft[2], upperright[2])) return closeAndReturnFALSE(current);

/*	Sort the arrays and take the value in the middle --> median
 *  Remember: No use in fiddling around with which value to take
 *  if the sky background for median sky generation varies rapidly,
 *  you will have a too high (or low) value of sky on positions where
 *  a star is in some of the frames. Discussed it with Hacki and found
 *  a solution: You need to know how the sky varies, then you can take
 *  into account that situation. I did not do this here because it is
 *  highly non-trivial.
 *
 *  There is another solution which makes pretty pictures but at the
 *  cost of a higher background: Instead of taking the median, you
 *  can take the minimum of all values (i.e. replace in the 3rd line
 *  tmpbuffer[i*nfiles+nfiles/2-1] with tmpbuffer[i*nfiles]. This is
 *  performed in the following routine.
 */
 		for (i = 0; i < n[1]; i++) {
			for (j = m; j < m + nrows; j++) {
				if (j < n[2]) {
					nvalues = 0;
					for (k = 0; k < nfiles; k++) {
						value = (current.ValueAt(i + (j - m) * n[1] + k * n[1] * nrows));
						if (value != reject) {
							sortbuffer[nvalues] = value;
							nvalues++;
						}
					}
					if (nvalues > 0) {
						if (nvalues == 1) 
							r4data[C_I(i, j)] = sortbuffer[0];
						else {
							gsl_sort_float(sortbuffer, 1, nvalues);
							r4data[C_I(i, j)] = gsl_stats_float_median_from_sorted_data(sortbuffer, 1, nvalues);
						}
//						else if (nvalues % 2)
//							r4data[C_I(i, j)] = nr_select(nvalues / 2 + 1, nvalues, sortbuffer - 1);
//						else
//							r4data[C_I(i, j)] = (nr_select(nvalues / 2, nvalues, sortbuffer - 1) +
//												nr_select(nvalues / 2 + 1, nvalues, sortbuffer - 1)) / 2.0;
					}
				}
			}
		}
		if (upperright[1] == n[2]) break;
	}
	free(sortbuffer);
	current.CloseFITS();
	dp_output("\n");
//	dpFitsProgress();

	return TRUE;
}

/*!
Take a fits cube and do a quantile of the images therein.
This version reads parts of the single images consecutively
from disk in order to save memory.
(the maximum amount of memory allocated is define below.)
*/

bool Fits::CubeQuantile(const char *fname, double quantile) {
        float *sortbuffer;
        int i, j, m, nfiles, nrows, numfiles, k;
        long n[4], lowerleft[3], upperright[3], inc[4], nvalues;
        Fits current;
        double value;

        status = 0;
        inc[0] = inc[1] = inc[2] = inc[3] = 1;

// find out how big the array is
        if (!current.OpenFITS(fname)) return FALSE;
        if ((current.hdr = current.ReadFitsHeader()) < 1) return closeAndReturnFALSE(current);
        if (!current.getHeaderInformation()) return closeAndReturnFALSE(current);
        for (i = 0; i < 4; i++) n[i] = current.naxis[i];
        numfiles = n[3];
        if (!create(n[1], n[2])) return closeAndReturnFALSE(current);
        CopyHeader(current);

        nfiles = n[3];
        dp_output("Doing quantile of %i images ", nfiles);
        fflush(stdout);

        nrows = 1;

// allocate no more than MEDIAN_MEM

        while (nrows*nfiles*n[1]*(long)sizeof(double) < MEDIAN_MEM) nrows *= 2;
        if (nrows > n[2]) nrows = n[2];
        if (!current.allocateMemory(nrows*nfiles*n[1]*sizeof(double))) return closeAndReturnFALSE(current);
        sortbuffer = (float *)malloc(nfiles * sizeof(float));
        lowerleft[2] = 1;
        upperright[2] = n[3];
        lowerleft[0] = 1;
        upperright[0] = n[1];
        dp_output("doing %i rows at a time\n", nrows);

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

// now go through array and read data
        for (m = 0; m < n[2]; m += nrows) {
//                current.dpseek(current.hdr, SEEK_SET);
                dp_output(".at row %i\n", m);
                fflush(stdout);
                lowerleft[1] = m + 1;
                upperright[1] = lowerleft[1] + nrows - 1;
                if (upperright[1] > n[2]) {
                        upperright[1] = n[2];
                        nrows = upperright[1] - lowerleft[1] + 1;
                }
                if (!current.ReadFITSData(lowerleft[0], upperright[0], lowerleft[1], upperright[1], lowerleft[2], upperright[2])) return closeAndReturnFALSE(current);

                for (i = 0; i < n[1]; i++) {
                        for (j = m; j < m + nrows; j++) {
                                if (j < n[2]) {
                                        nvalues = 0;
                                        for (k = 0; k < nfiles; k++) {
                                                value = (current.ValueAt(i + (j - m) * n[1] + k * n[1] * nrows));
                                                sortbuffer[nvalues] = value;
                                                nvalues++;
                                        }
                                        if (nvalues > 0) {
                                                if (nvalues == 1)
                                                        r4data[C_I(i, j)] = sortbuffer[0];
                                                else {
                                                        gsl_sort_float(sortbuffer, 1, nvalues);
                                                        r4data[C_I(i, j)] = gsl_stats_float_quantile_from_sorted_data(sortbuffer, 1, nvalues, quantile);
                                                }
                                        }
                                }
                        }
                }
                if (upperright[1] == n[2]) break;
        }
        free(sortbuffer);
        current.CloseFITS();
        dp_output("\n");
//	dpFitsProgress();

        return TRUE;
}

/*!
Go through the cube and select the quantile of each pixel in the 3rd dimension. Returns an image.
*/

bool Fits::CubeQuantile(const Fits & cube, double quantile) {
        long x, y, z, count;
        float *sortbuffer;
        double value;

        if (!create(cube.naxis[1], cube.naxis[2])) return FALSE;
        CopyHeader(cube);
        if ((sortbuffer = (float *)malloc(sizeof(float) * cube.naxis[3])) == NULL)
                return FALSE;

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

        dpProgress(-cube.Naxis(1), "CubeQuantile");
        for (x = 1; x <= naxis[1]; x++) {
                dpProgress(x, "");
                for (y = 1; y <= naxis[2]; y++) {
                        count = 0;
                        for (z = 1; z <= cube.naxis[3]; z++) {
                                value = (cube.ValueAt(cube.F_I(x, y, z)));
                                if (gsl_finite(value)) {
                                        sortbuffer[count] = value;
                                        count++;
                                }
                        }
                        if (count == 0) r4data[F_I(x, y)] = 0.0;
                        else if (count == 1) r4data[F_I(x, y)] = sortbuffer[0];
                        else {
                            std::nth_element(sortbuffer, sortbuffer + (int)(count*quantile), sortbuffer + count);
                            r4data[F_I(x, y)] = sortbuffer[(int)(count*quantile)];
//                                gsl_sort_float(sortbuffer, 1, count);
//                                r4data[F_I(x, y)] = gsl_stats_float_quantile_from_sorted_data(sortbuffer, 1, count, quantile);
                        }
                }
        }
        free(sortbuffer);
        return TRUE;
}

#define PRECOLLAPSEACTION
#define COLLAPSEACTION(source,dest) ((dest)+=(source))
#define POSTCOLLAPSEACTION(data,n)

Fits &Fits::collapse(Fits &source, int axis) {
#include "collapse_helper.cpp"
	return *this;
}

#define COLLAPSEACTION(source,dest) if ((source)!=ignore) ((dest)+=(source))

Fits &Fits::collapse(Fits &source, int axis, double ignore) {
#include "collapse_helper.cpp"
        return *this;
}

#define COLLAPSEACTION(source,dest) ((dest)+=(source))
#define POSTCOLLAPSEACTION(data,n) ((data)/=(n))

Fits &Fits::collapse_average(Fits &source, int axis) {
#include "collapse_helper.cpp"
	return *this;
}

#define COLLAPSEACTION(source,dest) if((source)!=ignore)((dest)+=(source)); else _ne--

Fits &Fits::collapse_average(Fits &source, int axis, double ignore) {
#include "collapse_helper.cpp"
	return *this;
}

#define COLLAPSEACTION(source,dest) ((dest)+=((source)-average.r8data[_index])*((source)-average.r8data[_index]))
#define POSTCOLLAPSEACTION(data,n) ((data)/=((n)-1))

Fits &Fits::collapse_variance(Fits &source, int axis) {
    Fits average;
    average.collapse_average(source, axis);

#include "collapse_helper.cpp"

    return *this;
}

#define COLLAPSEACTION(source,dest) if((source)!=ignore)((dest)+=((source)-average.r8data[_index])*((source)-average.r8data[_index])); else _ne--

Fits &Fits::collapse_variance(Fits &source, int axis, double ignore) {
    Fits average;
    average.collapse_average(source, axis, ignore);

#include "collapse_helper.cpp"

    return *this;
}

Fits &Fits::collapse_stddev(Fits &source, int axis) {
    collapse_variance(source, axis);
    Sqrt();

    return *this;
}

Fits &Fits::collapse_stddev(Fits &source, int axis, double ignore) {
    collapse_variance(source, axis, ignore);
    Sqrt();

    return *this;
}

#define PRECOLLAPSEACTION (first=TRUE)
#define COLLAPSEACTION(source,dest) if(first){(dest)=(source);first=FALSE;} else if(((source)<(dest))) (dest)=(source)
#define POSTCOLLAPSEACTION(data,n)

Fits &Fits::collapse_min(Fits &source, int axis) {
    bool first;
#include "collapse_helper.cpp"

    return *this;
}

#define COLLAPSEACTION(source,dest) if(first){(dest)=(source);first=FALSE;} else if(((source)<(dest)&&(source!=ignore))) (dest)=(source)
#define POSTCOLLAPSEACTION(data,n)

Fits &Fits::collapse_min(Fits &source, int axis, double ignore) {
    bool first;
#include "collapse_helper.cpp"

    return *this;
}

#define COLLAPSEACTION(source,dest) if(first){(dest)=(source);first=FALSE;} else if(((source)>(dest))) (dest)=(source)
#define POSTCOLLAPSEACTION(data,n)

Fits &Fits::collapse_max(Fits &source, int axis) {
    bool first;
#include "collapse_helper.cpp"

    return *this;
}

#define COLLAPSEACTION(source,dest) if(first){(dest)=(source);first=FALSE;} else if(((source)>(dest)&&(source!=ignore))) (dest)=(source)
#define POSTCOLLAPSEACTION(data,n)

Fits &Fits::collapse_max(Fits &source, int axis, double ignore) {
    bool first;
#include "collapse_helper.cpp"

    return *this;
}

#define PRECOLLAPSEACTION (counter=0)
#define COLLAPSEACTION(source,dest) (sortdata[counter])=(source);counter++
#define POSTCOLLAPSEACTION(data,n) {gsl_sort(sortdata, 1, counter);(data)=gsl_stats_median_from_sorted_data(sortdata, 1, counter);}

Fits &Fits::collapse_median(Fits &source, int axis) {
    long counter;

    double *sortdata;
    switch (axis) {
        case 1: sortdata = (double *)malloc(source.Naxis(1)*sizeof(double)); break;
        case 2: sortdata = (double *)malloc(source.Naxis(2)*sizeof(double)); break;
        case 3: sortdata = (double *)malloc(source.Naxis(3)*sizeof(double)); break;
        case 12: sortdata = (double *)malloc(source.Naxis(1)*source.Naxis(2)*sizeof(double)); break;
        case 13: sortdata = (double *)malloc(source.Naxis(1)*source.Naxis(3)*sizeof(double)); break;
        case 23: sortdata = (double *)malloc(source.Naxis(2)*source.Naxis(3)*sizeof(double)); break;
        default: break;
    }
#include "collapse_helper.cpp"
    free(sortdata);

    return *this;
}

#define PRECOLLAPSEACTION (counter=0)
#define COLLAPSEACTION(source,dest) if((source)!=ignore){sortdata[counter]=(source);counter++;}
#define POSTCOLLAPSEACTION(data,n) {gsl_sort(sortdata, 1, counter);(data)=gsl_stats_median_from_sorted_data(sortdata, 1, counter);}

Fits &Fits::collapse_median(Fits &source, int axis, double ignore) {
    long counter;

    double *sortdata;
    switch (axis) {
        case 1: sortdata = (double *)malloc(source.Naxis(1)*sizeof(double)); break;
        case 2: sortdata = (double *)malloc(source.Naxis(2)*sizeof(double)); break;
        case 3: sortdata = (double *)malloc(source.Naxis(3)*sizeof(double)); break;
        case 12: sortdata = (double *)malloc(source.Naxis(1)*source.Naxis(2)*sizeof(double)); break;
        case 13: sortdata = (double *)malloc(source.Naxis(1)*source.Naxis(3)*sizeof(double)); break;
        case 23: sortdata = (double *)malloc(source.Naxis(2)*source.Naxis(3)*sizeof(double)); break;
        default: break;
    }
#include "collapse_helper.cpp"
    free(sortdata);

    return *this;
}

#define PRECOLLAPSEACTION (counter=0)
#define COLLAPSEACTION(source,dest) (sortdata[counter])=fabs((source)-median.r8data[_index]);counter++
#define POSTCOLLAPSEACTION(data,n) {gsl_sort(sortdata, 1, counter);(data)=gsl_stats_median_from_sorted_data(sortdata, 1, counter);}

Fits &Fits::collapse_meddev(Fits &source, int axis) {
    long counter;
    Fits median;
    median.collapse_median(source, axis);

    double *sortdata;
    switch (axis) {
        case 1: sortdata = (double *)malloc(source.Naxis(1)*sizeof(double)); break;
        case 2: sortdata = (double *)malloc(source.Naxis(2)*sizeof(double)); break;
        case 3: sortdata = (double *)malloc(source.Naxis(3)*sizeof(double)); break;
        case 12: sortdata = (double *)malloc(source.Naxis(1)*source.Naxis(2)*sizeof(double)); break;
        case 13: sortdata = (double *)malloc(source.Naxis(1)*source.Naxis(3)*sizeof(double)); break;
        case 23: sortdata = (double *)malloc(source.Naxis(2)*source.Naxis(3)*sizeof(double)); break;
        default: break;
    }
#include "collapse_helper.cpp"

    free(sortdata);

    return *this;
}

void Fits::rmedian(char *prefix, int first, int last, int length, char *suffix)
{
	float *tmpbuffer;
	Fits current;
	char fname[255];
	int i, nfiles, nrows;
	long n, j;
	
	makefname(fname, prefix, first, length, suffix);
	if (!ReadFITS(fname)) return;
	if (!setType(R4)) return;
	
	nfiles = last - first + 1;
	nrows = 1;
	
// allocate no more than 4 MB
	
        while (nrows*nfiles*naxis[1]*(long)sizeof(double) < MEDIAN_MEM) nrows *= 2;
	if (nrows > naxis[2]) nrows = naxis[2];
        tmpbuffer = (float *)malloc(nrows * naxis[1] * nfiles * sizeof(double));
	for (n = 0; n < naxis[2]; n += nrows) {
		dp_output("Doing %i rows at a time...at row %li...\n", nrows, n);
		for (i = first; i <= last; i++) {
			makefname(fname, prefix, i, length, suffix);
//			current.io_rfits(fname, n*naxis1, nrows*naxis1);
			current.ReadFITS(fname);
			for (j = 0; j < naxis[1]*nrows; j++) {
				tmpbuffer[i-first+nfiles*j] = (float)(current.ValueAt(j+n*naxis[1]));
			}
		}
		
/*	Sort the arrays and take the value in the middle --> median
 *  Remember: No use in fiddling around with which value to take
 *  if the sky background for median sky generation varies rapidly,
 *  you will have a too high (or low) value of sky on positions where
 *  a star is in some of the frames. Discussed it with Hacki and found
 *  a solution: You need to know how the sky varies, then you can take
 *  into account that situation. I did not do this here because it is
 *  highly non-trivial.
 *
 *  There is another solution which makes pretty pictures but at the
 *  cost of a higher background: Instead of taking the median, you
 *  can take the minimum of all values (i.e. replace in the 3rd line
 *  tmpbuffer[i*nfiles+nfiles/2-1] with tmpbuffer[i*nfiles]. This is
 *  performed in the following routine.
 */
		
		for (i = 0; i < naxis[1]*nrows; i++) {
			qsort((char *)(tmpbuffer+i*nfiles), nfiles, sizeof(float), &floatcmp);
//			data[C_I(i, n)] = tmpbuffer[i*nfiles+nfiles/2-1];
			r4data[C_I(i, n)] = tmpbuffer[i*nfiles+nfiles/2];
		}
	}
	free(tmpbuffer);
}

void Fits::minimum(char *prefix, int first, int last, int length, char *suffix)
{
	Fits current;
	char fname[255];
	int n;
    dpint64 i;
	
	makefname(fname, prefix, first, length, suffix);
	dp_output("reading file %s...\n", fname);
	if (!ReadFITS(fname)) return;
	if (!setType(R4)) return;
	
	for (n = first+1; n <= last; n++) {
		makefname(fname, prefix, n, length, suffix);
		dp_output("reading file %s...\n", fname);
		current.ReadFITS(fname);
		for (i = 0; i < n_elements; i++) {
			if (current.ValueAt(i) < (double)r4data[i]) r4data[i] = (float)(current.ValueAt(i));
		}
	}
}

bool Fits::cube2single(char *cfname, char *prefix, int begin, char *suffix)
{
	int i, j, n, z = 0;
	char fname[256], ns[5];
	
	if (!OpenFITS(cfname)) return FALSE;
    if (ReadFitsHeader() < 0) return closeAndReturnFALSE(*this);
	getHeaderInformation();
	if (Naxis(3) < 2) {
 		dp_output("File %s is not a fits cube.\n", cfname);
		CloseFITS();
 		return FALSE;
 	}
	j = begin;
	for (i = 1; i <= z; i++) {
        if (!ReadFITSCubeImage(i)) return closeAndReturnFALSE(*this);
		sprintf(ns, "%4i", i + begin);	
		for (n = 0; n < 4; n++) {
			if (ns[n] == ' ') ns[n] = '0';
		}
		strcpy(fname, prefix);
		strcat(fname, ns);
		strcat(fname, suffix);
		dp_output("Writing data to file %s.\n", fname);
        if (!WriteFITS(fname)) return closeAndReturnFALSE(*this);
	}
    CloseFITS();
	return TRUE;
}

bool spiffishift(Fits &in, Fits &result, int band) {
	int kshifts[] = {0,16,1,17,2,18,2,18,3,19,4,19,4,19,4,40,56,19,4,19,4,19,3,18,3,18,2,17,1,16,0,15};
    int hkshifts[] = {0,18,0,18,0,18,1,18,0,18,1,18,1,18,1,42,60,18,1,18,1,18,1,18,0,18,0,18,0,18,0,18};
	int *shifts;
	int maxshift;
	int i;
	Fits slit;

	switch (band) {
		case 1:
			shifts = kshifts;
			maxshift = 56;
			break;
		case 2:
			shifts = hkshifts;
			maxshift = 60;
			break;
		default:
			shifts = kshifts;
			maxshift = 56;
			break;
	}

	if (!result.copy(in)) return FALSE;
	if (!result.resize(-1, in.Naxis(2) + maxshift)) return FALSE;
	if (!result.ishift(0, maxshift, 0)) return FALSE;
	 
	for (i = 1; i <= 32; i++) {
		result.extractRange(slit, (i-1)*32+1, i*32, -1, -1, -1, -1);
		slit.ishift(0, -shifts[i-1], 0);
		result.setRange(slit, (i-1)*32+1, i*32, -1, -1, -1, -1);
	}
  
	return TRUE;
}

bool spifficube(Fits &in, Fits &result, int band) {
	int kshifts[] = {0,16,1,17,2,18,2,18,3,19,4,19,4,19,4,40,56,19,4,19,4,19,3,18,3,18,2,17,1,16,0,15};
    int hkshifts[] = {0,18,0,18,0,18,1,18,0,18,1,18,1,18,1,42,60,18,1,18,1,18,1,18,0,18,0,18,0,18,0,18};
	int ord[] = {9,8,10,7,11,6,12,5,13,4,14,3,15,2,16,1,32,17,31,18,30,19,29,20,28,21,27,22,26,23,25,24};
	int *shifts;
	int maxshift;
	int x, y, i;
	Fits image, slitlet, slit;

	switch (band) {
		case 1:
			shifts = kshifts;
			maxshift = 56;
			break;
		case 2:
			shifts = hkshifts;
			maxshift = 60;
			break;
		default:
			shifts = kshifts;
			maxshift = 56;
			break;
	}

	if (!result.create(32, 32, in.Naxis(2) + maxshift)) return FALSE;
	if (!image.copy(in)) return FALSE;
	if (!image.resize(-1, in.Naxis(2) + maxshift)) return FALSE;
	if (!image.ishift(0, maxshift, 0)) return FALSE;
	if (!slitlet.copy(image)) return FALSE;
	 
	for (i = 1; i <= 32; i++) {
		image.extractRange(slit, (i-1)*32+1, i*32, -1, -1, -1, -1);
		slit.ishift(0, -shifts[i-1], 0);
		image.setRange(slit, (i-1)*32+1, i*32, -1, -1, -1, -1);
	}
  
	for (i=0; i < 32; i++) {
		image.extractRange(slit, 1+i*32, (i+1)*32, -1, -1, -1, -1);
		slitlet.setRange(slit, ord[i]*32-31, ord[i]*32, -1, -1, -1, -1);
	}
	
	for (x = 1; x <= 32; x++) {
		for (y = 1; y <= 32; y++) {
			slitlet.extractRange(slit, x+(y-1)*32, x+(y-1)*32, -1, -1, -1, -1);
			result.setRange(slit, x, x, y, y, -1, -1);
		}
	}

	return TRUE;
}

bool spiffiuncube(Fits &in, Fits &result, int band) {
	int kshifts[] = {0,16,1,17,2,18,2,18,3,19,4,19,4,19,4,40,56,19,4,19,4,19,3,18,3,18,2,17,1,16,0,15};
    int hkshifts[] = {0,18,0,18,0,18,1,18,0,18,1,18,1,18,1,42,60,18,1,18,1,18,1,18,0,18,0,18,0,18,0,18};
	int ord[] = {9,8,10,7,11,6,12,5,13,4,14,3,15,2,16,1,32,17,31,18,30,19,29,20,28,21,27,22,26,23,25,24};
	int *shifts;
	int maxshift;
	int x, y, i;
	Fits image, slitlet, slit;

	switch (band) {
		case 1:
			shifts = kshifts;
			maxshift = 56;
			break;
		case 2:
			shifts = hkshifts;
			maxshift = 60;
			break;
		default:
			shifts = kshifts;
			maxshift = 56;
			break;
	}

	if (!slitlet.create(1024, in.Naxis(3))) return FALSE;

	for (x = 1; x <= 32; x++) {
		for (y = 1; y <= 32; y++) {
			in.extractRange(slit, x, x, y, y, -1, -1);
			slitlet.setRange(slit, x+(y-1)*32, x+(y-1)*32, -1, -1, -1, -1);
		}
	}
	if (!image.copy(slitlet)) return FALSE;

	for (i = 0; i < 32; i++) {
		slitlet.extractRange(slit, ord[i]*32-31, ord[i]*32, -1, -1, -1, -1);
		image.setRange(slit, 1+i*32, (i+1)*32, -1, -1, -1, -1);
	}

	for (i = 1; i <= 32; i++) {
		image.extractRange(slit, (i-1)*32+1, i*32, -1, -1, -1, -1);
		slit.ishift(0, shifts[i-1], 0);
		slitlet.setRange(slit, (i-1)*32+1, i*32, -1, -1, -1, -1);
	}

	slitlet.extractRange(result, -1, -1, maxshift+1, slitlet.Naxis(2), -1, -1);

	return TRUE;
}
