/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/fits_exc.cpp
 * Purpose:  Fits class methods to change image scales and the like
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 13.08.1999: file created
 ******************************************************************/

#include "fits.h"
#include <gsl/gsl_spline.h>

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

/*!
Enlarge the array by an integer amount
Keep flux constant
flag can have 2 values:
    0: Center original image
    1: Center original image, edge values are mean of edge
    2: Blow up pixels
*/

bool Fits::enlarge(int scale, int flag)
{
	int i, j, ii, jj, isum = 0;
    dpint64 n;
    dpint64 oldx, oldy;
    dpint64 old_length, new_length;
	double sum = 0.0;
    unsigned char *old_pointer, *new_pointer;
	
	if (naxis[0] != 2) return fits_error("Fits::enlarge: Only 2D images allowed");
	oldx = naxis[1];
    oldy = naxis[2];

	switch(flag) {
		case 1:
// calculate mean edge value
			for (i = 0; i < naxis[1]; i++) {
				sum += ValueAt(C_I(i, 0));
				sum += ValueAt(C_I(i, naxis[2]-1));
				isum += 2;
			}
			for (j = 1; j < naxis[2]-1; j++) {
				sum += ValueAt(C_I(0, j));
				sum += ValueAt(C_I(naxis[1]-1, j));
				isum += 2;
			}
			sum /= (double)isum;
// no break here, intended since cases 0 and 1 share the same code
        case 0:
            if (!recreate(naxis[1] * scale, naxis[2] * scale)) return FALSE;
// move array to correct position
			old_length = oldx * bytesPerPixel;
            new_length = naxis[1] * bytesPerPixel;
            old_pointer = (unsigned char *)((long long)i1data + (oldy - 1) * oldx * bytesPerPixel);
            new_pointer = (unsigned char *)((long long)i1data + ((naxis[2] / 2 + oldy / 2 - 1) * naxis[1] + naxis[1] / 2 - oldx / 2) * bytesPerPixel);
            for (i = 0; i < oldy; i++) {
                memcpy(new_pointer, old_pointer, old_length);
				new_pointer -= new_length;
				old_pointer -= old_length;
            }

            // cumbersome

            switch (membits) {
                case I1: {
                    unsigned char v = (unsigned char)((sum - bzero) / bscale);
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) i1data[n] = v;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) i1data[n] = v;
                    for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
                        for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
                            i1data[C_I(i, j)] = v;
                        }
                        for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
                            i1data[C_I(i, j)] = v;
                        }
                    }
                }
                break;
                case I2: {
                    short v = (short)((sum - bzero) / bscale);
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) i2data[n] = v;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) i2data[n] = v;
                    for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
                        for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
                            i2data[C_I(i, j)] = v;
                        }
                        for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
                            i2data[C_I(i, j)] = v;
                        }
                    }
                }
                break;
                case I4: {
                    int v = (int)((sum - bzero) / bscale);
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) i4data[n] = v;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) i4data[n] = v;
                    for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
                        for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
                            i4data[C_I(i, j)] = v;
                        }
                        for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
                            i4data[C_I(i, j)] = v;
                        }
                    }
                }
                break;
                case I8: {
                    long long v = (long long)((sum - bzero) / bscale);
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) i8data[n] = v;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) i8data[n] = v;
                    for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
                        for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
                            i8data[C_I(i, j)] = v;
                        }
                        for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
                            i8data[C_I(i, j)] = v;
                        }
                    }
                }
                break;
                case R4: {
					float v = (float)sum;
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) r4data[n] = v;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) r4data[n] = v;
					for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
						for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
							r4data[C_I(i, j)] = v;
						}
						for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
							r4data[C_I(i, j)] = v;
						}
					}
				}
					break;
                case R8: {
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) r8data[n] = sum;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) r8data[n] = sum;
                    for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
                        for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
                            r8data[C_I(i, j)] = sum;
                        }
                        for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
                            r8data[C_I(i, j)] = sum;
                        }
                    }
                }
                break;
                case C16: {
                    for (n = 0; n < naxis[1] * (naxis[2] - oldy) / 2; n++) cdata[n].r = cdata[n].i = sum;
                    for (n = n_elements - naxis[1] * (naxis[2] - oldy) / 2; n < n_elements; n++) cdata[n].r = cdata[n].i = sum;
                    for (j = naxis[2] / 2 - oldy / 2; j < naxis[2] / 2 + oldy / 2; j++) {
                        for (i = 0; i < naxis[1] / 2 - oldx / 2; i++) {
                            cdata[C_I(i, j)].r = cdata[C_I(i, j)].i = sum;
                        }
                        for (i = naxis[1] / 2 + oldx / 2; i < naxis[1]; i++) {
                            cdata[C_I(i, j)].r = cdata[C_I(i, j)].i = sum;
                        }
                    }
                }
                break;
                default: return FALSE; break;
            }
			break;
		case 2: 
			if (!recreate(naxis[1] * scale, naxis[2] * scale)) return FALSE;
			switch (membits) {
				case I1: {
					unsigned char v;
					for (j = oldy - 1; j >= 0; j--) {
						for (i = oldx - 1; i >= 0; i--) {
							v = i1data[i + j * oldx];
							for (ii = 0; ii < scale; ii++) {
								for (jj = 0; jj < scale; jj++) {
									i1data[C_I(i*scale+ii, j*scale+jj)] = v;
								}
							}
						}
					}
				}
					break;
				case I2: {
					short v;
					for (j = oldy - 1; j >= 0; j--) {
						for (i = oldx - 1; i >= 0; i--) {
							v = i2data[i + j * oldx];
							for (ii = 0; ii < scale; ii++) {
								for (jj = 0; jj < scale; jj++) {
									i2data[C_I(i*scale+ii, j*scale+jj)] = v;
								}
							}
						}
					}
				}
					break;
				case I4: {
					long v;
					for (j = oldy - 1; j >= 0; j--) {
						for (i = oldx - 1; i >= 0; i--) {
							v = i4data[i + j * oldx];
							for (ii = 0; ii < scale; ii++) {
								for (jj = 0; jj < scale; jj++) {
									i4data[C_I(i*scale+ii, j*scale+jj)] = v;
								}
							}
						}
					}
				}
					break;
                case I8: {
                    long long v;
                    for (j = oldy - 1; j >= 0; j--) {
                        for (i = oldx - 1; i >= 0; i--) {
                            v = i8data[i + j * oldx];
                            for (ii = 0; ii < scale; ii++) {
                                for (jj = 0; jj < scale; jj++) {
                                    i8data[C_I(i*scale+ii, j*scale+jj)] = v;
                                }
                            }
                        }
                    }
                }
                    break;
                case R4: {
					float v;
					for (j = oldy - 1; j >= 0; j--) {
						for (i = oldx - 1; i >= 0; i--) {
							v = r4data[i + j * oldx];
							for (ii = 0; ii < scale; ii++) {
								for (jj = 0; jj < scale; jj++) {
									r4data[C_I(i*scale+ii, j*scale+jj)] = v;
								}
							}
						}
					}
				}
					break;
				case R8: {
					double v;
					for (j = oldy - 1; j >= 0; j--) {
						for (i = oldx - 1; i >= 0; i--) {
							v = r8data[i + j * oldx];
							for (ii = 0; ii < scale; ii++) {
								for (jj = 0; jj < scale; jj++) {
									r8data[C_I(i*scale+ii, j*scale+jj)] = v;
								}
							}
						}
					}
				}
					break;
				case C16: {
					double re, im;
					for (j = oldy - 1; j >= 0; j--) {
						for (i = oldx - 1; i >= 0; i--) {
							re = cdata[i + j * oldx].r;
							im = cdata[i + j * oldx].i;
							for (ii = 0; ii < scale; ii++) {
								for (jj = 0; jj < scale; jj++) {
									cdata[C_I(i*scale+ii, j*scale+jj)].r = re;
									cdata[C_I(i*scale+ii, j*scale+jj)].i = im;
								}
							}
						}
					}
				}
					break;
				default: return FALSE; break;
			}
			div((double)(scale*scale));
			break;
		default: break;
	}

	if (hasRefPix()) {
		double delta;

		if (flag == 2) {
//			SetFloatKey("CRVAL1", getCRVAL(1) - (double)scale/2.0);
//			SetFloatKey("CRVAL2", getCRVAL(2) - (double)scale/2.0);
			if (GetFloatKey("CDELT1", &delta)) SetFloatKey("CDELT1", delta / (double)scale);
			if (GetFloatKey("CDELT2", &delta)) SetFloatKey("CDELT2", delta / (double)scale);
			if (GetFloatKey("CD1_1", &delta)) SetFloatKey("CD1_1", delta / (double)scale);
			if (GetFloatKey("CD1_2", &delta)) SetFloatKey("CD1_2", delta / (double)scale);
			if (GetFloatKey("CD2_1", &delta)) SetFloatKey("CD2_1", delta / (double)scale);
			if (GetFloatKey("CD2_2", &delta)) SetFloatKey("CD2_2", delta / (double)scale);
			SetFloatKey("CRPIX1", getCRPIX(1) * scale - ((double)scale / 2. - 0.5));
			SetFloatKey("CRPIX2", getCRPIX(2) * scale - ((double)scale / 2. - 0.5));
		} else {
			SetFloatKey("CRPIX1", getCRPIX(1) + naxis[1] / 2 - naxis[1] / scale / 2);
			SetFloatKey("CRPIX2", getCRPIX(2) + naxis[2] / 2 - naxis[2] / scale / 2);

		}
	}

	return TRUE;
}


void Fits::extract( void )
{
	long i, j;
	Fits a;
		
	if (!a.create(naxis[1]/2, naxis[2]/2, R4)) return;
	for (i = 0; i < naxis[1]/2; i++) {
		for (j = 0; j < naxis[2]/2; j++) {
			a.r4data[a.C_I(i, j)] = ValueAt(C_I(i+naxis[1]/4, j+naxis[2]/4));
		}
	}
    naxis[1] /= 2;
    naxis[2] /= 2;
	allocateMemory();
	copy(a);
}

/*!
Shrink the array such that 4 original pixel becomes one new pixel which is set to the sum
of those 4 pixels. This way the total flux in the array is kept constant.
*/

void Fits::shrink(int fact)
{
	long i, j, k, l, x, y;
	Fits a;

	if (fact < 2) return;
	if (!a.create(naxis[1]/fact, naxis[2]/fact)) return;
	a.CopyHeader(*this);
	for (i = 0, k = 0; i < naxis[1]/fact; i++, k += fact) {
		for (j = 0, l = 0; j < naxis[2]/fact; j++, l += fact) {
			for (x = 0; x < fact; x++) {
				for (y = 0; y < fact; y++) {
					a.r4data[a.C_I(i, j)] += ValueAt(C_I(k + x, l + y));
				}
			}
		}
	}
	copy(a);

	if (hasRefPix()) {
		double dvalue;
		bool b1 = FALSE, b2 = FALSE;

		b2 = GetFloatKey("CD1_1", &dvalue);
		if (!b2) b1 = GetFloatKey("CDELT1", &dvalue);
		if (b1 || b2) {
			SetFloatKey("CRVAL1", getCRVAL(1) + (1. - getCRPIX(1)) * dvalue);
			SetFloatKey("CRPIX1", 0.5);
		}
		b2 = GetFloatKey("CD2_2", &dvalue);
		if (!b2) b1 = GetFloatKey("CDELT2", &dvalue);
		if (b1 || b2) {
			SetFloatKey("CRVAL2", getCRVAL(2) + (1. - getCRPIX(2)) * dvalue);
			SetFloatKey("CRPIX2", 0.5);
		}
		
		if (GetFloatKey("CDELT1", &dvalue)) SetFloatKey("CDELT1", dvalue * (double)fact);
		if (GetFloatKey("CDELT2", &dvalue)) SetFloatKey("CDELT2", dvalue * (double)fact);
		if (GetFloatKey("CD1_1", &dvalue)) SetFloatKey("CD1_1", dvalue * (double)fact);
		if (GetFloatKey("CD1_2", &dvalue)) SetFloatKey("CD1_2", dvalue * (double)fact);
		if (GetFloatKey("CD2_1", &dvalue)) SetFloatKey("CD2_1", dvalue * (double)fact);
		if (GetFloatKey("CD2_2", &dvalue)) SetFloatKey("CD2_2", dvalue * (double)fact);
	}
}

/*
Shrink specified axis by integer factor. The flux is summed up thus keepting the
total flux constant. The array is converted to R8.
*/
void Fits::shrink(int fact, int axis) {
	Fits _tmp;
	long x, y, z, c, cc;
	double dvalue, delta;
	bool b1 = FALSE, b2 = FALSE;

	switch (axis) {
		case 1:
			_tmp.create(Naxis(1) / fact, Naxis(2), Naxis(3), R8);
			_tmp.CopyHeader(*this);
			b1 = GetFloatKey("CDELT1", &dvalue);
			if (!b1) b2 = GetFloatKey("CD1_1", &dvalue);
			if (b1 || b2) {
				_tmp.SetFloatKey("CRVAL1", _tmp.getCRVAL(1) + (1. - _tmp.getCRPIX(1)) * dvalue);
				_tmp.SetFloatKey("CRPIX1", 0.5);
				if (b1) _tmp.SetFloatKey("CDELT1", dvalue * (double)fact);
				if (b2) _tmp.SetFloatKey("CD1_1", dvalue * (double)fact);
				if (GetFloatKey("CD1_2", &delta)) SetFloatKey("CD1_2", delta * (double)fact);
			}
			for (z = 0; z < Naxis(3); z++) {
				for (y = 0; y < Naxis(2); y++) {
					for (x = 0, cc = 0; x < Naxis(1) / fact; x++, cc += fact) {
						for (c = 0; c < fact; c++) {
							_tmp.r8data[_tmp.C_I(x, y, z)] += ValueAt(C_I(cc+c, y, z));
						}
					}
				}
			}
			break;
		case 2:
			_tmp.create(Naxis(1), Naxis(2) / fact, Naxis(3), R8);
			_tmp.CopyHeader(*this);
			b1 = GetFloatKey("CDELT2", &dvalue);
			if (!b1) b2 = GetFloatKey("CD2_2", &dvalue);
			if (b1 || b2) {
				_tmp.SetFloatKey("CRVAL2", _tmp.getCRVAL(2) + (1. - _tmp.getCRPIX(2)) * dvalue);
				_tmp.SetFloatKey("CRPIX2", 0.5);
				if (b1) _tmp.SetFloatKey("CDELT2", dvalue * (double)fact);
				if (b2) _tmp.SetFloatKey("CD2_2", dvalue * (double)fact);
				if (GetFloatKey("CD2_1", &delta)) SetFloatKey("CD2_1", delta * (double)fact);
			}
			for (z = 0; z < Naxis(3); z++) {
				for (x = 0; x < Naxis(1); x++) {
					for (y = 0, cc = 0; y < Naxis(2) / fact; y++, cc += fact) {
						for (c = 0; c < fact; c++) {
							_tmp.r8data[_tmp.C_I(x, y, z)] += ValueAt(C_I(x, cc+c, z));
						}
					}
				}
			}
			break;
		case 3:
			_tmp.create(Naxis(1), Naxis(2), Naxis(3) / fact, R8);
			_tmp.CopyHeader(*this);
			b1 = GetFloatKey("CDELT3", &dvalue);
			if (!b1) b2 = GetFloatKey("CD3_3", &dvalue);
			if (b1 || b2) {
				_tmp.SetFloatKey("CRVAL3", _tmp.getCRVAL(3) + (1. - _tmp.getCRPIX(3)) * dvalue);
				_tmp.SetFloatKey("CRPIX3", 0.5);
				if (b1) _tmp.SetFloatKey("CDELT3", dvalue * (double)fact);
				if (b2) _tmp.SetFloatKey("CD3_3", dvalue * (double)fact);
			}
			for (x = 0; x < Naxis(1); x++) {
				for (y = 0; y < Naxis(2); y++) {
					for (z = 0, cc = 0; z < Naxis(3) / fact; z++, cc += fact) {
						for (c = 0; c < fact; c++) {
							_tmp.r8data[_tmp.C_I(x, y, z)] += ValueAt(C_I(x, y, cc+c));
						}
					}
				}
			}
			break;
	}
	copy(_tmp);
}

/*!
Resize the array to new axis lengths
Image will be in lower left corner
*/

bool Fits::resize(int new1, int new2, int new3)
{
	Fits newarray;
	long i, j, k, l, x, y, z;

	if ((new1 == naxis[1]) && (new2 == naxis[2]) && (new3 == naxis[3])) return TRUE;
/*	naxis[1] = new1;
	data = (float *)realloc(data, new1 * sizeof(float));
	return TRUE;*/

	if (new1 != -1) i = new1;
	else i = naxis[1];
	if (new2 != -1) j = new2;
	else j = naxis[2];
	if (new3 != -1) k = new3;
	else k = naxis[3];
	l = (k > 1 ? 3 : 2);
	if (!newarray.create(i, j, k, membits)) return FALSE;
	newarray.CopyHeader(*this);
	if (membits > R4) {
		newarray.bscale = bscale;
		newarray.bzero = bzero;
	}
	l = newarray.naxis[3];
	if (l == 0) l = 1;
	x = naxis[1];
	if (x > newarray.naxis[1]) x = newarray.naxis[1];
	y = naxis[2];
	if (y > newarray.naxis[2]) y = newarray.naxis[2];
	z = naxis[3];
	if (z > newarray.naxis[3]) z = newarray.naxis[3];
	if (z < 1) z = 1;
	switch (membits) {
		case I1:
			for (i = 0; i < x; i++) {
				for (j = 0; j < y; j++) {
					for (k = 0; k < z; k++) {
						newarray.i1data[newarray.C_I(i, j, k)] = i1data[C_I(i, j, k)];
					}
				}
			}
			break;
		case I2:
			for (i = 0; i < x; i++) {
				for (j = 0; j < y; j++) {
					for (k = 0; k < z; k++) {
						newarray.i2data[newarray.C_I(i, j, k)] = i2data[C_I(i, j, k)];
					}
				}
			}
			break;
        case I4:
            for (i = 0; i < x; i++) {
                for (j = 0; j < y; j++) {
                    for (k = 0; k < z; k++) {
                        newarray.i4data[newarray.C_I(i, j, k)] = i4data[C_I(i, j, k)];
                    }
                }
            }
            break;
        case I8:
            for (i = 0; i < x; i++) {
                for (j = 0; j < y; j++) {
                    for (k = 0; k < z; k++) {
                        newarray.i8data[newarray.C_I(i, j, k)] = i8data[C_I(i, j, k)];
                    }
                }
            }
            break;
        case R4:
			for (i = 0; i < x; i++) {
				for (j = 0; j < y; j++) {
					for (k = 0; k < z; k++) {
						newarray.r4data[newarray.C_I(i, j, k)] = r4data[C_I(i, j, k)];
					}
				}
			}
			break;
		case R8:
			for (i = 0; i < x; i++) {
				for (j = 0; j < y; j++) {
					for (k = 0; k < z; k++) {
						newarray.r8data[newarray.C_I(i, j, k)] = r8data[C_I(i, j, k)];
					}
				}
			}
			break;
		case C16:
			for (i = 0; i < x; i++) {
				for (j = 0; j < y; j++) {
					for (k = 0; k < z; k++) {
						newarray.cdata[newarray.C_I(i, j, k)].r = cdata[C_I(i, j, k)].r;
						newarray.cdata[newarray.C_I(i, j, k)].i = cdata[C_I(i, j, k)].i;
					}
				}
			}
			break;
		default: return FALSE; break;
	}
	copy(newarray);

	return TRUE;
}

/*!
Resize the array to new axis lengths. The aspect ratio is NOT preserved.
Intermediate Pixels will be interpolated by bilinear interpolation.
At the moment only works for 2-dimensional arrays. 3-dim will loop over the 3rd dimension.
*/

bool Fits::rebin(int new1, int new2, int new3)
{
	Fits newarray;
    long j, k, l, x, y, z;
    double x1, x2, x3, dx1, dx2, dx3, fy[4], t, u, v;

    if ((new1 == naxis[1]) && (new2 == naxis[2]) && (new3 == naxis[3])) return TRUE;

	dx1 = (double)naxis[1] / (double)new1;
	dx2 = (double)naxis[2] / (double)new2;
	
    // rebin x or y axis?
    if ((new1 != naxis[1]) || (new2 != naxis[2])) {
        if (!newarray.create(new1, new2, naxis[3])) return FALSE;
        newarray.CopyHeader(*this);
        for (z = 1; z <= newarray.naxis[3]; z++) {
            for (x = 1; x <= new1; x++) {
                for (y = 1; y <= new2; y++) {
                    x1 = x * dx1;
                    x2 = y * dx2;
                    j = (int)x1 + 1;
                    k = (int)x2 + 1;
                    if (j < 1) j = 1;
                    else if (j >= naxis[1]) j = naxis[1] - 1;
                    if (k < 1) k = 1;
                    else if (k >= naxis[2]) k = naxis[2] - 1;
                    fy[0] = ValueAt(F_I(j, k, z));
                    fy[1] = ValueAt(F_I(j+1, k, z));
                    fy[2] = ValueAt(F_I(j+1, k+1, z));
                    fy[3] = ValueAt(F_I(j, k+1, z));
                    t = (x1 - j + 1.0);
                    u = (x2 - k + 1.0);
                    newarray.r4data[newarray.F_I(x, y, z)] = (1.0 - t) * (1.0 - u) * fy[0] + t * (1.0 - u) * fy[1] + t * u * fy[2] + (1.0 - t) * u * fy[3];
                }
            }
        }

        newarray.mul(dx1 * dx2);

        copy(newarray);
        if (hasRefPix()) {
            double delta;

            dx1 = 1. / dx1;
            dx2 = 1. / dx2;
            dp_debug("%f %f\n", dx1, dx2);
            if (GetFloatKey("CDELT1", &delta)) SetFloatKey("CDELT1", delta / dx1);
            if (GetFloatKey("CDELT2", &delta)) SetFloatKey("CDELT2", delta / dx1);
            if (GetFloatKey("CD1_1", &delta)) SetFloatKey("CD1_1", delta / dx1);
            if (GetFloatKey("CD1_2", &delta)) SetFloatKey("CD1_2", delta / dx1);
            if (GetFloatKey("CD2_1", &delta)) SetFloatKey("CD2_1", delta / dx2);
            if (GetFloatKey("CD2_2", &delta)) SetFloatKey("CD2_2", delta / dx2);
            SetFloatKey("CRPIX1", getCRPIX(1) * dx1 - (dx1 / 2. - 0.5));
            SetFloatKey("CRPIX2", getCRPIX(2) * dx2 - (dx2 / 2. - 0.5));
        }
    }

    // rebin also the z-axis?
    if ((naxis[3] > 1) && (new3 != naxis[3])) {
        dx3 = (double)naxis[3] / (double)new3;

        if (!newarray.create(new1, new2, new3)) return FALSE;
        newarray.CopyHeader(*this);
        for (x = 1; x <= new1; x++) {
            for (y = 1; y <= new2; y++) {
                for (z = 1; z <= new3; z++) {
                    x3 = z * dx3;
                    l = (int)x3 + 1;
                    if (l < 1) l = 1;
                    else if (l >= naxis[3]) l = naxis[3] - 1;
                    fy[0] = ValueAt(F_I(x, y, l));
                    fy[1] = ValueAt(F_I(x, y, l+1));
                    v = (x3 - l + 1.0);
                    newarray.r4data[newarray.F_I(x, y, z)] = (1.0 - v) * fy[0] + v * fy[1];
                }
            }
        }
        newarray.mul(dx3);

        copy(newarray);
        if (hasRefPix()) {
            double delta;

            dx3 = 1. / dx3;
            dp_debug("%f\n", dx3);
            if (GetFloatKey("CDELT3", &delta)) SetFloatKey("CDELT3", delta / dx3);
            if (GetFloatKey("CD3_3", &delta)) SetFloatKey("CD3_3", delta / dx3);
            SetFloatKey("CRPIX3", getCRPIX(3) * dx3 - (dx3 / 2. - 0.5));
        }
    }

	return TRUE;
}

/*
 * Do a cubic spline interpolation of a 1D array. Useful for rebinning spectra.
 */

bool Fits::rebin1d(const double xstart, const double xend, const double xinc) {
	Fits x, result;
	long i, n;
	double crval, crpix, cdelt;
	
	n = (long)((xend - xstart) / xinc);
	crval = getCRVAL(1);
	cdelt = getCDELT(1);
	crpix = getCRPIX(1);
	x.create(Nelements(), 1, R8);
	result.create(n, 1, R8);
	for (i = 1; i <= Nelements(); i++) x.r8data[i-1] = crval + ((double)i - crpix) * cdelt;
	setType(R8);
	
	gsl_interp_accel *acc = gsl_interp_accel_alloc ();
    gsl_spline *spline = gsl_spline_alloc (gsl_interp_cspline, Nelements());
	
    gsl_spline_init (spline, x.r8data, r8data, Nelements());
	
    for (i = 1; i <= n; i++) {
        result.r8data[i-1] = gsl_spline_eval (spline, xstart + (double)i * xinc, acc);
	}
    gsl_spline_free (spline);
    gsl_interp_accel_free (acc);
	copy(result);
	SetFloatKey("CRVAL1", xstart);
	SetFloatKey("CDELT1", xinc);
	SetFloatKey("CD1_1", xinc);
	SetIntKey("CRPIX1", 0);
	
	return TRUE;
}

/*
 * Do a cubic spline interpolation of a 1D array with x-values explicitly given.
 * Useful for rebinning spectra.
 */

bool Fits::rebin1d(const Fits & xvalues, const double xstart, const double xend, const double xinc) {
	Fits x, result;
	long i, n;
//	double crval, crpix, cdelt;
	
	n = (long)((xend - xstart) / xinc);
	result.create(n, 1, R8);
	setType(R8);
	x.copy(xvalues);
	x.setType(R8);
	
	gsl_interp_accel *acc = gsl_interp_accel_alloc ();
    gsl_spline *spline = gsl_spline_alloc (gsl_interp_cspline, Nelements());
	
    gsl_spline_init (spline, x.r8data, r8data, Nelements());
	
    for (i = 0; i < n; i++) {
        result.r8data[i] = gsl_spline_eval (spline, xstart + (double)i * xinc, acc);
	}
    gsl_spline_free (spline);
    gsl_interp_accel_free (acc);
	copy(result);
	SetFloatKey("CRVAL1", xstart);
	SetFloatKey("CDELT1", xinc);
	SetFloatKey("CD1_1", xinc);
	SetIntKey("CRPIX1", 0);
	
	return TRUE;
}

/*
 * Do a cubic spline interpolation of a 1D array.
 * Useful for rebinning spectra.
 */

bool Fits::rebin1d(const Fits & xvalues, const Fits &newxvalues) {
	Fits x, result;
	long i;//, n;
//	double crval, crpix, cdelt;
	
	result.create(newxvalues.Nelements(), 1, R8);
	setType(R8);
	x.copy(xvalues);
	x.setType(R8);
	
	gsl_interp_accel *acc = gsl_interp_accel_alloc ();
	gsl_spline *spline = gsl_spline_alloc (gsl_interp_cspline, Nelements());
	
	gsl_spline_init (spline, x.r8data, r8data, Nelements());
	
	for (i = 0; i < result.Nelements(); i++) {
		result.r8data[i] = gsl_spline_eval (spline, newxvalues.ValueAt(i), acc);
	}
	gsl_spline_free (spline);
	gsl_interp_accel_free (acc);
	copy(result);
	DeleteKey("CRPIX1");
	DeleteKey("CRVAL1");
	DeleteKey("CDELT1");
	DeleteKey("CD1_1");

	return TRUE;
}
