// Copyright (C) 1999-2014
// Smithsonian Astrophysical Observatory, Cambridge, MA, USA
// For conditions of distribution and use, see copyright notice in "copyright"

#include "fitsimage.h"
#include "framebase.h"

#include "context.h"
#include "mmap.h"
#include "smmap.h"
#include "mmapincr.h"
#include "alloc.h"
#include "allocgz.h"
#include "channel.h"
#include "share.h"
#include "sshare.h"
#include "socket.h"
#include "socketgz.h"
#include "var.h"
#include "iis.h"
#include "hist.h"
#include "compress.h"
#include "analysis.h"
#include "photo.h"
#include "colorscale.h"

#define MAXPV 99
#define MAXCO 13

// this is kluge to speed up doug minks wcssubs 'ksearch' routine
extern "C" {
  FitsHead* wcshead = NULL;
  FitsHead* wcsprim = NULL;
  char* ksearchh(char*, char*);

  char* findit(char* cards, char* key)
  {
    char* rr = NULL;
    if (wcshead) {
      if (rr = wcshead->find(key))
	return rr;

      if (wcsprim)
	if (rr = wcsprim->find(key))
	  return rr;

      return NULL;
    }
    else
      return ksearchh(cards, key);
  }
};

FitsImage::FitsImage(Base* p)
{
  parent = p;
  objectName = NULL;
  rootBaseFileName = NULL;
  fullBaseFileName = NULL;
  rootFileName = NULL;
  fullFileName = NULL;
  rootFileName3d = NULL;
  fullFileName3d = NULL;
  iisFileName = NULL;

  fits_ = NULL;
  post_ = NULL;
  hist_ = NULL;
  hpx_ = NULL;

  base_ = NULL;
  basedata_ = NULL;

  manageOrder_ =0;
  order_ =NULL;
  orderdata_ =NULL;

  manageBlock_ =0;
  block_ =NULL;
  blockdata_ =NULL;

  manageAnalysis_ =0;
  analysis_ =NULL;
  analysisdata_ =NULL;

  image_ = NULL;
  data_ = NULL;

  nextMosaic_ = NULL;
  nextSlice_ = NULL;

  doBin_ =0;

  binSliceFilter =NULL;

  keyLTMV = 0;
  keyATMV = 0;
  keyDTMV = 0;
  keyDATASEC = 0;

  jyBeam_ =0;

  imageToData = Translate(-.5, -.5);
  dataToImage = Translate( .5,  .5);

  imageToData3d = Translate3d(-.5, -.5, -.5);
  dataToImage3d = Translate3d( .5,  .5,  .5);

  wcs =NULL;
  wcsHeader =NULL;

  ast =NULL;

  iisMode_ = 0;
  iiszt_ = 0;

  mparams =NULL;

  for (int ii=0; ii<FTY_MAXAXES; ii++)
    address[ii] =1;
}

FitsImage::~FitsImage()
{
  if (objectName)
    delete [] objectName;

  if (rootBaseFileName)
    delete [] rootBaseFileName;

  if (fullBaseFileName)
    delete [] fullBaseFileName;

  if (rootFileName)
    delete [] rootFileName;

  if (fullFileName)
    delete [] fullFileName;

  if (rootFileName3d)
    delete [] rootFileName3d;

  if (fullFileName3d)
    delete [] fullFileName3d;

  if (iisFileName)
    delete [] iisFileName;

  if (fits_)
    delete fits_;

  if (post_)
    delete post_;

  if (hist_)
    delete hist_;

  if (hpx_)
    delete hpx_;

  if (basedata_)
    delete basedata_;

  if (manageOrder_) {
    if (order_)
      delete order_;
    if (orderdata_)
      delete orderdata_;
  }

  if (manageBlock_) {
    if (block_)
      delete block_;
    if (blockdata_)
      delete blockdata_;
  }

  if (manageAnalysis_) {
    if (analysis_)
      delete analysis_;
    if (analysisdata_)
      delete analysisdata_;
  }

  if (wcs) {
    for (int ii=0; ii<MULTWCS; ii++)
      if (wcs[ii])
	wcsfree(wcs[ii]);
    delete [] wcs;
  }  

  if (ast) {
    // we have a problem with windows being too slow
    // look at it later
# ifndef _GWIN32
    for (int ii=0; ii<MULTWCS; ii++)
      if (ast[ii])
 	astAnnul(ast[ii]);
#endif
    delete [] ast;
  }

  if (wcsHeader)
    delete wcsHeader;
}

// Fits

FitsImageFitsAlloc::FitsImageFitsAlloc(Base* p, 
				       const char* ch, const char* fn, 
				       FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsAlloc(ch, FitsFile::RELAX, flush);
  process(fn,id);
}

FitsImageFitsAllocGZ::FitsImageFitsAllocGZ(Base* p, 
					   const char* ch, const char* fn, 
					   FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsAllocGZ(ch, FitsFile::RELAX, flush);
  process(fn,id);
}

FitsImageFitsChannel::FitsImageFitsChannel(Base* p, Tcl_Interp* interp, 
					   const char* ch, const char* fn, 
					   FitsFile::FlushMode flush, int id) 
  : FitsImage(p)
{
  fits_ = new FitsFitsChannel(interp, ch, fn, FitsFile::RELAX, flush);
  process(fn,id);
}

FitsImageFitsMMap::FitsImageFitsMMap(Base* p, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsMMap(fn, FitsFile::RELAX);
  process(fn,id);
}

FitsImageFitsSMMap::FitsImageFitsSMMap(Base* p, const char* hdr, 
				       const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsSMMap(hdr, fn);
  process(fn,id);
}

FitsImageFitsMMapIncr::FitsImageFitsMMapIncr(Base* p, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsMMapIncr(fn, FitsFile::RELAX);
  process(fn,id);
}

FitsImageFitsShare::FitsImageFitsShare(Base* p, Base::ShmType type, 
				       int sid, const char* fn, int id)
  : FitsImage(p)
{
  switch (type) {
  case Base::SHMID:
    fits_ = new FitsFitsShareID(sid, fn, FitsFile::RELAX);
    break;
  case Base::KEY:
    fits_ = new FitsFitsShareKey(sid, fn, FitsFile::RELAX);
    break;
  }
  process(fn,id);
}

FitsImageFitsSShare::FitsImageFitsSShare(Base* p, Base::ShmType type,
					 int hdr, int sid, 
					 const char* fn, int id)
  : FitsImage(p)
{
  switch (type) {
  case Base::SHMID:
    fits_ = new FitsFitsSShareID(hdr, sid, fn);
    break;
  case Base::KEY:
    fits_ = new FitsFitsSShareKey(hdr, sid, fn);
    break;
  }
  process(fn,id);
}

FitsImageFitsSocket::FitsImageFitsSocket(Base* p, int s, const char* fn, 
					 FitsFile::FlushMode flush, int id) 
  : FitsImage(p)
{
  fits_ = new FitsFitsSocket(s, fn, FitsFile::RELAX, flush);
  process(fn,id);
}

FitsImageFitsSocketGZ::FitsImageFitsSocketGZ(Base* p, int s, 
					     const char* fn, 
					     FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsSocketGZ(s, fn, FitsFile::RELAX, flush);
  process(fn,id);
}

FitsImageFitsVar::FitsImageFitsVar(Base* p, Tcl_Interp* interp, 
				   const char* var, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsVar(interp, var, fn, FitsFile::RELAX);
  process(fn,id);
}

// Fits Next

FitsImageFitsNextAlloc::FitsImageFitsNextAlloc(Base* p, 
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextAlloc(prev);
  process(fn,id);
}

FitsImageFitsNextAllocGZ::FitsImageFitsNextAllocGZ(Base* p, 
						   const char* fn,
						   FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextAllocGZ(prev);
  process(fn,id);
}

FitsImageFitsNextChannel::FitsImageFitsNextChannel(Base* p, 
						   const char* fn,
						   FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextChannel(prev);
  process(fn,id);
}

FitsImageFitsNextMMap::FitsImageFitsNextMMap(Base* p, 
					     const char* fn,
					     FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextMMap(prev);
  process(fn,id);
}

FitsImageFitsNextSMMap::FitsImageFitsNextSMMap(Base* p, 
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextSMMap(prev);
  process(fn,id);
}

FitsImageFitsNextMMapIncr::FitsImageFitsNextMMapIncr(Base* p, 
						     const char* fn,
						     FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextMMapIncr(prev);
  process(fn,id);
}

FitsImageFitsNextShare::FitsImageFitsNextShare(Base* p, 
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextShare(prev);
  process(fn,id);
}

FitsImageFitsNextSShare::FitsImageFitsNextSShare(Base* p, 
						 const char* fn,
						 FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextSShare(prev);
  process(fn,id);
}

FitsImageFitsNextSocket::FitsImageFitsNextSocket(Base* p, 
						 const char* fn,
						 FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextSocket(prev);
  process(fn,id);
}

FitsImageFitsNextSocketGZ::FitsImageFitsNextSocketGZ(Base* p, 
						     const char* fn, 
						     FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextSocketGZ(prev);
  process(fn,id);
}

FitsImageFitsNextVar::FitsImageFitsNextVar(Base* p, 
					   const char* fn,
					   FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsFitsNextVar(prev);
  process(fn,id);
}

FitsImageFitsNextHist::FitsImageFitsNextHist(Base* p, 
					     FitsImage* fi,
					     FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsHistNext(prev);
  process(NULL,id);

  fits_->setpFilter(fi->getHistFilter());
  rootBaseFileName = dupstr(fi->getRootBaseFileName());
  fullBaseFileName = dupstr(fi->getFullBaseFileName());
  iisFileName = dupstr(fi->getFullBaseFileName());
}

FitsImageFitsNextPost::FitsImageFitsNextPost(Base* p, 
					     FitsImage* fi,
					     FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsPostNext(prev);
  process(NULL,id);

  rootBaseFileName = dupstr(fi->getRootBaseFileName());
  fullBaseFileName = dupstr(fi->getFullBaseFileName());
  iisFileName = dupstr(fi->getFullBaseFileName());
}

// Array

FitsImageArrAlloc::FitsImageArrAlloc(Base* p, 
				     const char* ch, const char* fn,
				     FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrAlloc(ch, flush);
  process(fn,id);
}

FitsImageArrAllocGZ::FitsImageArrAllocGZ(Base* p, 
					 const char* ch, const char* fn,
					 FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrAllocGZ(ch, flush);
  process(fn,id);
}

FitsImageArrChannel::FitsImageArrChannel(Base* p, Tcl_Interp* interp, 
					 const char* ch, const char* fn,
					 FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrChannel(interp, ch, fn, flush);
  process(fn,id);
}

FitsImageArrMMap::FitsImageArrMMap(Base* p, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrMMap(fn);
  process(fn,id);
}

FitsImageArrMMapIncr::FitsImageArrMMapIncr(Base* p, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrMMapIncr(fn);
  process(fn,id);
}

FitsImageArrShare::FitsImageArrShare(Base* p, Base::ShmType type, 
				     int sid, const char* fn, int id) 
  : FitsImage(p)
{
  switch (type) {
  case Base::SHMID:
    fits_ = new FitsArrShareID(sid, fn);
    break;
  case Base::KEY:
    fits_ = new FitsArrShareKey(sid, fn);
    break;
  }
  process(fn,id);
}

FitsImageArrSocket::FitsImageArrSocket(Base* p, int s, const char* fn,
				       FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrSocket(s, fn, flush);
  process(fn,id);
}

FitsImageArrSocketGZ::FitsImageArrSocketGZ(Base* p, int s, const char* fn,
					   FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrSocketGZ(s, fn, flush);
  process(fn,id);
}

FitsImageArrVar::FitsImageArrVar(Base* p, Tcl_Interp* interp, 
				 const char* var, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsArrVar(interp, var, fn);
  process(fn,id);
}

// ENVI

FitsImageENVISMMap::FitsImageENVISMMap(Base* p, const char* hdr,
				       const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsENVISMMap(hdr,fn);
  process(fn,id);
}

// NRRD

FitsImageNRRDAlloc::FitsImageNRRDAlloc(Base* p, 
				     const char* ch, const char* fn,
				     FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsNRRDAlloc(ch, flush);
  process(fn,id);
}

FitsImageNRRDChannel::FitsImageNRRDChannel(Base* p, Tcl_Interp* interp, 
					 const char* ch, const char* fn,
					 FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsNRRDChannel(interp, ch, fn, flush);
  process(fn,id);
}

FitsImageNRRDMMap::FitsImageNRRDMMap(Base* p, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsNRRDMMap(fn);
  process(fn,id);
}

FitsImageNRRDShare::FitsImageNRRDShare(Base* p, Base::ShmType type, 
				     int sid, const char* fn, int id) 
  : FitsImage(p)
{
  switch (type) {
  case Base::SHMID:
    fits_ = new FitsNRRDShareID(sid, fn);
    break;
  case Base::KEY:
    fits_ = new FitsNRRDShareKey(sid, fn);
    break;
  }
  process(fn,id);
}

FitsImageNRRDSocket::FitsImageNRRDSocket(Base* p, int s, const char* fn,
				       FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsNRRDSocket(s, fn, flush);
  process(fn,id);
}

FitsImageNRRDVar::FitsImageNRRDVar(Base* p, Tcl_Interp* interp, 
				 const char* var, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsNRRDVar(interp, var, fn);
  process(fn,id);
}

// Photo

FitsImagePhoto::FitsImagePhoto(Base* p, Tcl_Interp* interp, 
				  const char* ph, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsPhoto(interp, ph);
  process(fn,id);
}

FitsImagePhotoCube::FitsImagePhotoCube(Base* p, Tcl_Interp* interp, 
				       const char* ph, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsPhotoCube(interp, ph);
  process(fn,id);
}

FitsImagePhotoCubeNext::FitsImagePhotoCubeNext(Base* p, const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsPhotoCubeNext(prev);
  process(fn,id);
}

// Mosaic

FitsImageMosaicAlloc::FitsImageMosaicAlloc(Base* p, 
					   const char* ch, 
					   const char* fn,
					   FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicAlloc(ch, flush);
  process(fn,id);
}

FitsImageMosaicAllocGZ::FitsImageMosaicAllocGZ(Base* p, 
					       const char* ch, 
					       const char* fn,
					       FitsFile::FlushMode flush, 
					       int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicAllocGZ(ch, flush);
  process(fn,id);
}

FitsImageMosaicChannel::FitsImageMosaicChannel(Base* p, 
					       Tcl_Interp* interp,
					       const char* ch, 
					       const char* fn,
					       FitsFile::FlushMode flush,
					       int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicChannel(interp, ch, flush);
  process(fn,id);
}

FitsImageMosaicMMap::FitsImageMosaicMMap(Base* p, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicMMap(fn);
  process(fn,id);
}

FitsImageMosaicMMapIncr::FitsImageMosaicMMapIncr(Base* p, const char* fn, 
						 int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicMMapIncr(fn);
  process(fn,id);
}

FitsImageMosaicShare::FitsImageMosaicShare(Base* p, 
					   Base::ShmType type,
					   int sid, const char* fn, int id)
  : FitsImage(p)
{
  switch (type) {
  case Base::SHMID:
    fits_ = new FitsMosaicShareID(sid);
    break;
  case Base::KEY:
    fits_ = new FitsMosaicShareKey(sid);
    break;
  }
  process(fn,id);
}

FitsImageMosaicSocket::FitsImageMosaicSocket(Base* p, 
					     int s, const char* fn,
					     FitsFile::FlushMode flush, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicSocket(s, flush);
  process(fn,id);
}

FitsImageMosaicSocketGZ::FitsImageMosaicSocketGZ(Base* p, 
						 int s, const char* fn,
						 FitsFile::FlushMode flush, 
						 int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicSocketGZ(s, flush);
  process(fn,id);
}

FitsImageMosaicVar::FitsImageMosaicVar(Base* p, Tcl_Interp* interp,
				       const char* var, const char* fn, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicVar(interp, var, fn);
  process(fn,id);
}

// Mosaic Next

FitsImageMosaicNextAlloc::FitsImageMosaicNextAlloc(Base* p, 
						   const char* fn,
						   FitsFile* prev,
						   FitsFile::FlushMode flush,
						   int id)

  : FitsImage(p)
{
  fits_ = new FitsMosaicNextAlloc(prev, flush);
  process(fn,id);
}

FitsImageMosaicNextAllocGZ::FitsImageMosaicNextAllocGZ(Base* p,
						       const char* fn,
						       FitsFile* prev,
						      FitsFile::FlushMode flush,
						       int id)

  : FitsImage(p)
{
  fits_ = new FitsMosaicNextAllocGZ(prev, flush);
  process(fn,id);
}

FitsImageMosaicNextChannel::FitsImageMosaicNextChannel(Base* p, 
						       const char* fn, 
						       FitsFile* prev,
						      FitsFile::FlushMode flush,
						       int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicNextChannel(prev, flush);
  process(fn,id);
}

FitsImageMosaicNextMMap::FitsImageMosaicNextMMap(Base* p, const char* fn, 
						 FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicNextMMap(prev);
  process(fn,id);
}

FitsImageMosaicNextMMapIncr::FitsImageMosaicNextMMapIncr(Base* p,
							 const char* fn, 
							 FitsFile* prev,
							 int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicNextMMapIncr(prev);
  process(fn,id);
}

FitsImageMosaicNextShare::FitsImageMosaicNextShare(Base* p, 
						   const char* fn,
						   FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicNextShare(prev);
  process(fn,id);
}

FitsImageMosaicNextSocket::FitsImageMosaicNextSocket(Base* p, 
						     const char* fn, 
						     FitsFile* prev,
						     FitsFile::FlushMode flush,
						     int id)

  : FitsImage(p)
{
  fits_ = new FitsMosaicNextSocket(prev, flush);
  process(fn,id);
}

FitsImageMosaicNextSocketGZ::FitsImageMosaicNextSocketGZ(Base* p,
							 const char* fn, 
							 FitsFile* prev,
						      FitsFile::FlushMode flush,
							 int id)

  : FitsImage(p)
{
  fits_ = new FitsMosaicNextSocketGZ(prev, flush);
  process(fn,id);
}

FitsImageMosaicNextVar::FitsImageMosaicNextVar(Base* p, 
					       const char* fn, 
					       FitsFile* prev, int id)
  : FitsImage(p)
{
  fits_ = new FitsMosaicNextVar(prev);
  process(fn,id);
}

// IIS

FitsImageIIS::FitsImageIIS(Base* p, int w, int h) : FitsImage(p)
{
  fits_ = new FitsIIS(w, h);
  process("",1);
  iisMode_ = 1;
}

void FitsImageIIS::iisErase()
{
  ((FitsIIS*)fits_)->erase();
}

char* FitsImageIIS::iisGet(int xx, int yy, int dx, int dy)
{
  return ((FitsIIS*)fits_)->get(xx, yy, dx, dy);
}

void FitsImageIIS::iisSet(const char* src, int xx, int yy, int dx, int dy)
{
  ((FitsIIS*)fits_)->set(src, xx, yy, dx, dy);
}

void FitsImageIIS::iisWCS(const Matrix& mm, const Vector& z, int zt)
{
  Matrix& mx = (Matrix&)mm;
  double sx = mx[0][0];
  double sy = mx[1][1];
  double tx = mx[2][0];
  double ty = mx[2][1];

  imageToPhysical = 
    Translate(0,-height()/2.) *
    FlipY() * 
    Translate(0,height()/2.) *
    Translate(-1,0) * 
    Scale(sx,sy) * 
    Translate(tx,ty);
  physicalToImage = imageToPhysical.invert();
  iisz_ = z;
  iiszt_ = zt;
}

// FitsImage

void FitsImage::analysis()
{
  if (manageAnalysis_) {
    if (analysis_)
      delete analysis_;
    if (analysisdata_)
      delete analysisdata_;
  }
  manageAnalysis_ =0;
  analysis_ = block_;
  analysisdata_ = blockdata_;

  if (parent->currentContext->hasSmooth()) {
    analysis_ = new FitsAnalysis(block_);
    if (analysis_->isValid()) {
      analysisdata_ = new FitsDatam<double>(analysis_, parent);

      if (smooth())
	manageAnalysis_ =1;
      else {
	delete analysis_;
	analysis_ = block_;
	delete analysisdata_;
	analysisdata_ = blockdata_;
      }
    }
    else {
      delete analysis_;
      analysis_ = block_;
    }
  }

  image_ = analysis_;
  data_ = analysisdata_;
}

void FitsImage::appendWCS(FitsHead* hh)
{
  // process OBJECT keyword

  char* obj = hh->getString("OBJECT");
  if (obj) {
    if (objectName)
      delete [] objectName;
    objectName = obj;
  }

  // Process WCS keywords
  FitsHead* head = image_->head();

  // append wcs keywords to the end of the header
  int ll = head->headbytes()+hh->headbytes();
  char* cards = new char[ll];

  // copy default wcs
  memcpy(cards, head->cards(), head->headbytes());

  // find first END and zero out
  for (int i=0; i<head->headbytes(); i+=80)
    if (!strncmp(cards+i,"END",3)) {
      memcpy(cards+i, "   ",3);
      break;
    }

  // copy appended wcs
  memcpy(cards+head->headbytes(), hh->cards(), hh->headbytes());

  if (wcsHeader)
    delete wcsHeader;
  wcsHeader = new FitsHead(cards,ll,FitsHead::ALLOC);
  initWCS(wcsHeader, 
	  (image_->primary() && image_->inherit() ? image_->primary() : NULL));
}

void FitsImage::block()
{
  if (manageBlock_) {
    if (block_)
      delete block_;
    if (blockdata_)
      delete blockdata_;
  }
  manageBlock_ = 0;
  block_ = order_;
  blockdata_ = orderdata_;

  //  if (parent->doBin_) {
    //    block_ = new FitsBin(order_);
    //    if (block_->isValid()) {
    //      blockdata_ = new FitsDatam<double>(block_, parent);
    //manageBlock_ =1;
    // bin();
  //  }
}

char* FitsImage::display(FitsHead* head)
{
  int size = head->ncard() * (FTY_CARDLEN+1);
  char* lbuf = new char[size+1];

  char* lptr = lbuf;
  char* cptr = head->cards();
  for (int i=0; i<head->ncard(); i++,cptr+=FTY_CARDLEN) {
    memcpy(lptr, cptr, FTY_CARDLEN);
    lptr+=FTY_CARDLEN;
    *(lptr++) = '\n';
  }

  lbuf[size] = '\0';
  return lbuf;
}

char* FitsImage::displayHeader()
{
  if (DebugBin || DebugCompress)
    return display(image_->head());
  else
    return display(fits_->head());
}

char* FitsImage::displayPrimary()
{
  return display(fits_->primary());
}

char* FitsImage::displayWCS()
{
  if (wcsHeader)
    return display(wcsHeader);
  else
    return display(image_->head());
}

int FitsImage::findKeyword(const char* key)
{
  return fits_->find(key);
}

FitsBound* FitsImage::getDataParams(FrScale::ScanMode which)
{
  switch (which) {
  case FrScale::IMGSEC:
    return &iparams;
  case FrScale::DATASEC:
    return &dparams;
  case FrScale::CROPSEC:
    return &cparams;
  }
}

const char* FitsImage::getValue(const Vector& v)
{
  if (!isIIS())
    return data_->getValue(v);
  else {
    double val = data_->getValueDouble(v);

    ostringstream str;
    if (val == 0)
      str << ends;
    else if (val == 1)
      str << '<' << iisz_[0] << ends;
    else if (val == IISMAX)
      str << '>' << iisz_[1] << ends;
    else if (val > IISMAX)
      str << ends;
    else
      str << ((val-1) * (iisz_[1]-iisz_[0]))/(IISMAX-1) + iisz_[0] << ends;

    memcpy(buf,str.str().c_str(), str.str().length());
    return buf;
  }
}

void FitsImage::iisSetFileName(const char* fn)
{
  if (iisFileName)
    delete [] iisFileName;
  iisFileName = dupstr(fn);
}

void FitsImage::initWCS(FitsHead* head, FitsHead* primary)
{
  // free up wcs and ast arrays
  if (wcs) {
    for (int ii=0; ii<MULTWCS; ii++)
      if (wcs[ii])
	wcsfree(wcs[ii]);
    delete [] wcs;
  }

  if (ast) {
    for (int ii=0; ii<MULTWCS; ii++)
      if (ast[ii])
 	astAnnul(ast[ii]);
    delete [] ast;
  }

  // wcsinit is sloooowwww! so try to figure it out first
  wcs = new WorldCoor*[MULTWCS];
  for (int ii=0; ii<MULTWCS; ii++)
    wcs[ii] = NULL;

  // look first for default WCS. Let wcsinit figure it out since there can
  // be many different non-standard wcs's present
  wcshead = head;
  wcsprim = primary;
  wcs[0] = wcsinit(head->cards());
  wcshead = NULL;
  wcsprim = NULL;

  // now look for WCSA - WCSZ
  // we can take a short cut here, since only valid FITS wcs's are available
  for (int ii=1; ii<MULTWCS; ii++) {
    char str[] = "CTYPE1 ";
    str[6] = '@'+ii;
    if (head->find(str)) {
      wcshead = head;
      wcsprim = primary;
      wcs[ii] = wcsinitc(head->cards(),str+6);
      wcshead  = NULL;
      wcsprim = NULL;
    }
  }

  // finally, look for AST def
  if (!wcs[0]) {
    char str[] = "BEGAST_A";
    if (head->find(str)) {
      wcs[0] = wcskinit(100, 100, "AST--WCS", "AST--WCS", 
			 0, 0, 0, 0, NULL, 1, 1, 0, 2000, 0);
      wcs[0]->longpole = 999;
      wcs[0]->latpole = 999;
    }
  }

  // AST
  ast = new AstFrameSet*[MULTWCS];
  for (int ii=0; ii<MULTWCS; ii++) {
    ast[ii] = NULL;
    if (wcs[ii])
      astinit(ii, head);
  }

  // WCSDEP
  if (head->find("WCSDEP")) {
    char* str = head->getString("WCSDEP");
    if (str) {
      for (int ii=1; ii<MULTWCS; ii++) {
	if (wcs[ii]) {
	  if (wcs[ii]->wcsname) {
	    if (!strncmp(str,wcs[ii]->wcsname,strlen(wcs[ii]->wcsname))) {
	      if (ast[0] && ast[ii]) {
		AstFrameSet* dep = (AstFrameSet*)astCopy(ast[ii]);
		astInvert(ast[0]);
		astAddFrame(dep,2,astUnitMap(2,""),ast[0]);
		astSetI(dep,"current",4);
		astAnnul(ast[0]);
		ast[0] = dep;
	      }
	    }
	  }
	}
      }
      delete [] str;
    }
  }

  // xth axis wcs
  char scrpix[] = "CRPIX  ";
  char scrval[] = "CRVAL  ";
  char scd[] = "CD _  ";
  char scdelt[] = "CDELT  ";
  for (int jj=0; jj<WCSXMAX; jj++) {
    for (int ii=0; ii<MULTWCS; ii++) {
      int kk = WCSXMAX*jj+ii;
      wcsx[kk] = 0;
      crpixx[kk] = 0;
      crvalx[kk] = 0;
      cdx[kk] = 0;
    
      scrpix[5] = '3'+jj;
      scrpix[6] = !ii ? ' ' : '@'+jj;
      scrval[5] = '3'+jj;
      scrval[6] = !ii ? ' ' : '@'+jj;
      scd[2] = '3'+jj;
      scd[4] = '3'+jj;
      scd[5] = !ii ? ' ' : '@'+jj;
      scdelt[5] = '3'+jj;
      scdelt[6] = !ii ? ' ' : '@'+jj;

      if (head->find(scrpix) && head->find(scrval)) {
	wcsx[kk] = 1;
	crpixx[kk] = head->getReal(scrpix,0);
	crvalx[kk] = head->getReal(scrval,0);
	cdx[kk] = head->getReal(scd,0);
	if (!cdx[kk])
	  cdx[kk] = head->getReal(scdelt,0);
      }
    }
  }

  // now see if we have a 'physical' wcs, if so, set LTMV keywords
  for (int ii=1; ii<MULTWCS; ii++) {
    if (wcs[ii] && 
	wcs[ii]->wcsname && 
	!strncmp(wcs[ii]->wcsname, "PHYSICAL", 8)) {
      keyLTMV = 1;

      double ltm11 = wcs[ii]->cd[0] != 0 ? 1/wcs[ii]->cd[0] : 0;
      double ltm12 = wcs[ii]->cd[1] != 0 ? 1/wcs[ii]->cd[1] : 0;
      double ltm21 = wcs[ii]->cd[2] != 0 ? 1/wcs[ii]->cd[2] : 0;
      double ltm22 = wcs[ii]->cd[3] != 0 ? 1/wcs[ii]->cd[3] : 0;

      double ltv1 = wcs[ii]->crpix[0] -
	wcs[ii]->crval[0]*ltm11 - wcs[ii]->crval[1]*ltm21;
      double ltv2 = wcs[ii]->crpix[1] -
	wcs[ii]->crval[0]*ltm12 - wcs[ii]->crval[1]*ltm22;

      physicalToImage = Matrix(ltm11, ltm12, ltm21, ltm22, ltv1, ltv2);
      imageToPhysical = physicalToImage.invert();
    }
  }

  if (DebugWCS) {
    for (int ii=0; ii<MULTWCS; ii++) {
      if (wcs[ii])
	wcsShow(wcs[ii]);

      if (wcsx[ii]) {
	cerr << "wcs " << (char)(!ii ? ' ' : '@'+ii) << endl;

	cerr << "wcs->crpix3=" << crpixx[ii] << endl;
	cerr << "wcs->crval3=" << crvalx[ii] << endl;
	cerr << "wcs->cd3_3=" << cdx[ii] << endl;
      }
    }
  }
}

void FitsImage::initWCS0(const Vector& pix)
{
  int ii = Coord::WCS0-Coord::WCS;
  if (wcs[ii])
    wcsfree(wcs[ii]);
  wcs[ii] = NULL;

  if (wcs[0]) {
    Vector cc = mapFromRef(pix, Coord::IMAGE, Coord::FK5);
    WorldCoor* ww = wcs[0];
    wcs[ii] = wcskinit(ww->nxpix, ww->nypix, "RA---TAN", "DEC--TAN", 
		       cc[0], cc[1], 0, 0, ww->cd, 0, 0, 0, 2000, 2000);
    wcs[ii]->longpole = 999;
    wcs[ii]->latpole = 999;

    if (ast[ii])
      astAnnul(ast[ii]);
    ast[ii] = NULL;
    astinit(ii, NULL);

    if (DebugWCS) {
      if (wcs[ii])
	wcsShow(wcs[ii]);
    }
  }
}

void FitsImage::load()
{
  if (post_)
    base_ = post_;
  else if (hpx_)
    base_ = hpx_;
  else if (hist_)
    base_ = hist_;
  else
    base_ = fits_;

  if (basedata_)
    delete basedata_;

  switch (base_->head()->bitpix()) {
  case 8:
    basedata_ = new FitsDatam<unsigned char>(base_, parent);
    break;
  case 16:
    basedata_ = new FitsDatam<short>(base_, parent);
    break;
  case -16:
    basedata_ = new FitsDatam<unsigned short>(base_, parent);
    break;
  case 32:
    basedata_ = new FitsDatam<int>(base_, parent);
    break;
  case 64:
    basedata_ = new FitsDatam<long long>(base_, parent);
    break;
  case -32:
    basedata_ = new FitsDatam<float>(base_, parent);
    break;
  case -64:
    basedata_ = new FitsDatam<double>(base_, parent);
    break;
  }

  render(ORDER);

  resetWCS();

  iparams.reset();
  dparams.reset();
  cparams.reset();

  if (mparams)
    delete mparams;
  mparams = NULL;

  processKeywords();
}

void FitsImage::match(const char* xxname1, const char* yyname1,
		      Coord::CoordSystem sys1, Coord::SkyFrame sky1,
		      const char* xxname2, const char* yyname2,
		      Coord::CoordSystem sys2, Coord::SkyFrame sky2,
		      double rad, Coord::CoordSystem sys, Coord::SkyDist dist,
		      const char* rrname)
{
  astClearStatus;

  // get lists
  Tcl_Obj* listxx1 = 
    Tcl_GetVar2Ex(parent->interp, xxname1, NULL, TCL_LEAVE_ERR_MSG);
  Tcl_Obj* listyy1 = 
    Tcl_GetVar2Ex(parent->interp, yyname1, NULL, TCL_LEAVE_ERR_MSG);
  Tcl_Obj* listxx2 = 
    Tcl_GetVar2Ex(parent->interp, xxname2, NULL, TCL_LEAVE_ERR_MSG);
  Tcl_Obj* listyy2 = 
    Tcl_GetVar2Ex(parent->interp, yyname2, NULL, TCL_LEAVE_ERR_MSG);

  // get objects
  int nxx1;
  Tcl_Obj **objxx1;
  Tcl_ListObjGetElements(parent->interp, listxx1, &nxx1, &objxx1);
  int nyy1;
  Tcl_Obj **objyy1;
  Tcl_ListObjGetElements(parent->interp, listyy1, &nyy1, &objyy1);
  int nxx2;
  Tcl_Obj **objxx2;
  Tcl_ListObjGetElements(parent->interp, listxx2, &nxx2, &objxx2);
  int nyy2;
  Tcl_Obj **objyy2;
  Tcl_ListObjGetElements(parent->interp, listyy2, &nyy2, &objyy2);

  // sanity check
  if (nxx1 != nyy1 || nxx2 != nyy2)
    return;
    
  // get doubles
  double* ixx1 = new double[nxx1];
  for (int ii=0 ; ii<nxx1 ; ii++)
    Tcl_GetDoubleFromObj(parent->interp, objxx1[ii], ixx1+ii);
  double* iyy1 = new double[nyy1];
  for (int ii=0 ; ii<nyy1 ; ii++)
    Tcl_GetDoubleFromObj(parent->interp, objyy1[ii], iyy1+ii);

  double* oxx1 = new double[nxx1];
  memset(oxx1,0,sizeof(double)*nxx1);
  double* oyy1 = new double[nyy1];
  memset(oyy1,0,sizeof(double)*nyy1);

  double* ixx2 = new double[nxx2];
  for (int ii=0 ; ii<nxx2 ; ii++)
    Tcl_GetDoubleFromObj(parent->interp, objxx2[ii], ixx2+ii);
  double* iyy2 = new double[nyy2];
  for (int ii=0 ; ii<nyy2 ; ii++)
    Tcl_GetDoubleFromObj(parent->interp, objyy2[ii], iyy2+ii);

  double* oxx2 = new double[nxx2];
  memset(oxx2,0,sizeof(double)*nxx2);
  double* oyy2 = new double[nyy2];
  memset(oyy2,0,sizeof(double)*nyy2);

  // map from wcs to image
  {
    int ss = sys1-Coord::WCS;
    if (!(ss>=0 && ast && ast[ss]))
      return;

    if (astIsASkyFrame(astGetFrame(ast[0], AST__CURRENT))) {
      setAstSkyFrame(ast[ss],sky1);
      for (int ii=0; ii<nxx1; ii++) {
	ixx1[ii] *= M_PI/180.;
	iyy1[ii] *= M_PI/180.;
      }	
      astTran2(ast[ss], nxx1, ixx1, iyy1, 0, oxx1, oyy1);
    }
  }

  {
    int ss = sys2-Coord::WCS;
    if (!(ss>=0 && ast && ast[ss]))
      return;

    if (astIsASkyFrame(astGetFrame(ast[ss], AST__CURRENT))) {
      setAstSkyFrame(ast[ss],sky2);
      for (int ii=0; ii<nxx2; ii++) {
	ixx2[ii] *= M_PI/180.;
	iyy2[ii] *= M_PI/180.;
      }	
      astTran2(ast[ss], nxx2, ixx2, iyy2, 0, oxx2, oyy2);
    }
  }

  // radius
  Vector cd = getWCScdelt(sys);
  switch (dist) {
  case Coord::DEGREE:
    break;
  case Coord::ARCMIN:
    rad /= 60;
    break;
  case Coord::ARCSEC:
    rad /= 60*60;
    break;
  }
  double rx = rad/cd[0];
  double ry = rad/cd[1];
  double rr = (rx*rx + ry*ry)/2;

  // now compare
  int cnt=0;
  Tcl_Obj* objrr = Tcl_NewListObj(0,NULL);
  for(int jj=0; jj<nxx2; jj++) {
    for (int ii=0; ii<nxx1; ii++) {
      double dx = oxx2[jj]-oxx1[ii];
      double dy = oyy2[jj]-oyy1[ii];

      if (dx*dx + dy*dy < rr) {
	Tcl_Obj* obj[2];
	obj[0] = Tcl_NewIntObj(ii+1);
	obj[1] = Tcl_NewIntObj(jj+1);
	Tcl_Obj* list = Tcl_NewListObj(2,obj);
	Tcl_ListObjAppendElement(parent->interp, objrr, list);
	cnt++;
      }
    }
  }

  Tcl_Obj* listrr = 
    Tcl_SetVar2Ex(parent->interp, rrname, NULL, objrr, TCL_LEAVE_ERR_MSG);

  // clean up
  delete [] ixx1;
  delete [] iyy1;
  delete [] oxx1;
  delete [] oyy1;

  delete [] ixx2;
  delete [] iyy2;
  delete [] oxx2;
  delete [] oyy2;
}

Matrix& FitsImage::matrixFromData(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WIDGET:
    return dataToWidget;
  case Coord::PANNER:
    return dataToPanner;
  case Coord::MAGNIFIER:
    return dataToMagnifier;
  case Coord::PS:
    return dataToPS;
  }
}

Matrix& FitsImage::matrixToData(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WIDGET:
    return widgetToData;
  case Coord::PANNER:
    return pannerToData;
  case Coord::MAGNIFIER:
    return magnifierToData;
  case Coord::PS:
    return psToData;
  }
}

Matrix3d& FitsImage::matrixFromData3d(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WIDGET:
    return dataToWidget3d;
  case Coord::PANNER:
    return dataToPanner3d;
  case Coord::MAGNIFIER:
    return dataToMagnifier3d;
  case Coord::PS:
    return dataToPS3d;
  }
}

Matrix3d& FitsImage::matrixToData3d(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WIDGET:
    return widgetToData3d;
  case Coord::PANNER:
    return pannerToData3d;
  case Coord::MAGNIFIER:
    return magnifierToData3d;
  case Coord::PS:
    return psToData3d;
  }
}

void FitsImage::order()
{
  manageOrder_ = 0;
  order_ = base_;
  orderdata_ = basedata_;
}

int FitsImage::parseSection(char* lbuf, Vector* v1, Vector* v2)
{
  double x1, y1, x2, y2;
  char d1,d2,d3,d4,d5; // dummy char
  string x(lbuf);
  istringstream str(x);
  str >> d1 >> x1 >> d2 >> x2 >> d3 >> y1 >> d4 >> y2 >> d5;

  // verify input
  if (!(d1=='[' && d2==':' && d3==',' && d4==':' && d5==']'))
    return 0;

  // it looks ok
  *v1 = Vector(x1,y1);
  *v2 = Vector(x2,y2);

  return 1;
}

void FitsImage::process(const char* fn, int id)
{
  if (!fits_->isValid()) {
    reset();
    return;
  }

  if (fits_->isImage()) {
    switch (fits_->pEncoding()) {
    case FitsFile::RAW:
    case FitsFile::BSQ:
      break;
    case FitsFile::GZIP:
      initNRRD();
      if (!post_ || !post_->isValid()) {
	reset();
	return;
      }
      break;
    case FitsFile::BIL:
    case FitsFile::BIP:
      initENVI();
      if (!post_ || !post_->isValid()) {
	reset();
	return;
      }
      break;
    default:
      reset();
      return;
    }

    load();
  }
  else if (fits_->isBinTable()) {
    // Compress
    if (fits_->find("ZIMAGE")) {
      initCompress();
      if (!post_ || !post_->isValid()) {
	reset();
	return;
      }
      load();
    }
    // HEALPIX
    else if ((fits_->find("PIXTYPE") && (!strncmp(fits_->getString("PIXTYPE"),"HEALPIX",4))) || (fits_->find("NSIDE"))) {
      initHPX();
      if (!hpx_ || !hpx_->isValid()) {
	reset();
	return;
      }
      load();
    }
    else {
      // Bintable
      initHist();
      if (!hist_ || !hist_->isValid()) {
	reset();
	return;
      }
    }
  }
  else if (fits_->isAsciiTable()) {
    // HEALPIX
    if (fits_->find("NSIDE")) {
      initHPX();
      if (!hpx_ || !hpx_->isValid()) {
	reset();
	return;
      }
      load();
    }
  }

  // set slice address
  for (int ii=1; ii<id; ii++) {
    for (int jj=2; jj<FTY_MAXAXES; jj++) {
      if (address[jj]<naxis(jj)) {
	address[jj]++;
	break;
      }
      else
	address[jj]=1;
    }
  }

  // load() can call reset()
  if (fits_)
    setFileName(fn);
}

void FitsImage::processKeywords()
{
  // Physical to Image (LTM/LTV keywords) (with no wcsname already located)

  if (!keyLTMV) {
    if (image_->find("LTM1_1") ||
	image_->find("LTM1_2") ||
	image_->find("LTM2_1") ||
	image_->find("LTM2_2") ||
	image_->find("LTV1") ||
	image_->find("LTV2"))
      keyLTMV = 1;

    double ltm11 = image_->getReal("LTM1_1", 1);
    double ltm12 = image_->getReal("LTM1_2", 0);
    double ltm21 = image_->getReal("LTM2_1", 0);
    double ltm22 = image_->getReal("LTM2_2", 1);

    double ltv1 = image_->getReal("LTV1", 0);
    double ltv2 = image_->getReal("LTV2", 0);

    physicalToImage = Matrix(ltm11, ltm12, ltm21, ltm22, ltv1, ltv2);
    imageToPhysical = physicalToImage.invert();
  }

  // CDD to Detector (DTM/DTV keywords)

  if (image_->find("DTM1_1") ||
      image_->find("DTM1_2") ||
      image_->find("DTM2_1") ||
      image_->find("DTM2_2") ||
      image_->find("DTV1") ||
      image_->find("DTV2"))
    keyDTMV = 1;

  double dtm11 = image_->getReal("DTM1_1", 1);
  double dtm12 = image_->getReal("DTM1_2", 0);
  double dtm21 = image_->getReal("DTM2_1", 0);
  double dtm22 = image_->getReal("DTM2_2", 1);

  double dtv1 = image_->getReal("DTV1", 0);
  double dtv2 = image_->getReal("DTV2", 0);

  physicalToDetector = Matrix(dtm11, dtm12, dtm21, dtm22, dtv1, dtv2);
  detectorToPhysical = physicalToDetector.invert();

  // Physical to Amplifier (ATM/ATV keywords)

  if (image_->find("ATM1_1") ||
      image_->find("ATM1_2") ||
      image_->find("ATM2_1") ||
      image_->find("ATM2_2") ||
      image_->find("ATV1") ||
      image_->find("ATV2"))
    keyATMV = 1;

  double atm11 = image_->getReal("ATM1_1", 1);
  double atm12 = image_->getReal("ATM1_2", 0);
  double atm21 = image_->getReal("ATM2_1", 0);
  double atm22 = image_->getReal("ATM2_2", 1);

  double atv1 = image_->getReal("ATV1", 0);
  double atv2 = image_->getReal("ATV2", 0);

  physicalToAmplifier = Matrix(atm11, atm12, atm21, atm22, atv1, atv2);
  amplifierToPhysical = physicalToAmplifier.invert();

  if (DebugMosaic) {
    cerr << endl;
    cerr << "ATM/V: " << physicalToAmplifier << endl;
    cerr << "ATM/V-1: " << amplifierToPhysical << endl;
    cerr << "DTM/V: " << physicalToDetector << endl;
    cerr << "DTM/V-1: " << detectorToPhysical << endl;
    cerr << "LTM/V: " << physicalToImage << endl;
    cerr << "LTM/V-1: " << imageToPhysical << endl;
  }

  // Radio data?
  char* bunit = image_->getString("BUNIT");
  double cdelt1 = fabs(image_->getReal("CDELT1",0));
  char* cunit1 = image_->getString("CUNIT1");
  double cdelt2 = fabs(image_->getReal("CDELT2",0));
  char* cunit2 = image_->getString("CUNIT2");
  double bmaj = image_->getReal("BMAJ",0);
  double bmin = image_->getReal("BMIN",0);

#define GFACTOR (2.0*sqrt(2.0*log(2.0)))
  // ok we have a radio map
  if (bunit && cdelt1 && cdelt2 && bmaj && bmin)
    jyBeam_ = (2*M_PI*bmaj*bmin)/(GFACTOR*GFACTOR*cdelt1*cdelt2);

  if (bunit)
    delete [] bunit;
  if (cunit1)
    delete [] cunit1;
  if (cunit2)
    delete [] cunit2;

  // iparams is a BBOX in DATA coords 0-n
  iparams.xmin = 0;
  iparams.xmax = width();
  iparams.ymin = 0;
  iparams.ymax = height();
  iparams.zmin = 0;
  iparams.zmax = depth();

  {
    char* datstr = image_->getString("DATASEC");
    // default
    Vector v1(1,1);
    Vector v2(width(),height());

    if (datstr && *datstr && parseSection(datstr,&v1,&v2)) {
      // additional check
      if (v1[0]<1 || v1[1]<1 || 
	  v1[1]>width() || v2[1]>height() ||
	  v1[0]>v2[0] || v1[1]>v2[1]) {
	// default
	v1 = Vector(1,1);
	v2 = Vector(width(),height());
	keyDATASEC = 0;
      } else
	keyDATASEC = 1;
    }
    else
      keyDATASEC = 0;

    // dparams is a BBOX in DATA coords 0-n
    datasec = BBox(v1,v2);
    v1 -= Vector(1,1);
    dparams.xmin = v1[0];
    dparams.xmax = v2[0];
    dparams.ymin = v1[1];
    dparams.ymax = v2[1];
    dparams.zmin = iparams.zmin;
    dparams.zmax = iparams.zmax;

    if (datstr)
      delete [] datstr;
  }

  // FITS SECTION
  Vector ll(dparams.xmin,dparams.ymin);
  Vector ur(dparams.xmax,dparams.ymax);
  int zmin =iparams.zmin;
  int zmax =iparams.zmax;

  if (fits_->coord() && fits_->xvalid() && fits_->yvalid()) {
    ll[0] = fits_->xmin();
    ur[0] = fits_->xmax();
    ll[1] = fits_->ymin();
    ur[1] = fits_->ymax();

    ll = ll*physicalToImage*Translate(-1,-1);
    ur = ur*physicalToImage;

    parent->setScanMode(FrScale::CROPSEC);
  }
  if (!fits_->coord() && fits_->xvalid()) {
    ll[0] = fits_->xmin()-1;
    ur[0] = fits_->xmax();
    parent->setScanMode(FrScale::CROPSEC);
  }
  if (!fits_->coord() && fits_->yvalid()) {
    ll[1] = fits_->ymin()-1;
    ur[1] = fits_->ymax();
    parent->setScanMode(FrScale::CROPSEC);
  }
  if (!fits_->coord() && fits_->zvalid()) {
    zmin = fits_->zmin()-1;
    zmax = fits_->zmax();
    parent->setScanMode(FrScale::CROPSEC);
  }

  // params is a BBOX in DATA coords 0-n
  setCropParams(ll,ur,0);
  setCrop3dParams(zmin,zmax);

  // clear the flags so that rebinning tables will not see them
  //  fits_->setCoord(0);
  //  fits_->setXValid(0);
  //  fits_->setYValid(0);
  //  fits_->setZValid(0);

  // DEBUG
  if (DebugCrop) {
    cerr << "iparams " << iparams << endl;
    cerr << "dparams " << dparams << endl;
    cerr << "cparams " << cparams << endl;
  }
}

int FitsImage::processKeywordsIRAF(FitsImage* fits)
{
  // DETSEC
  Coord::Orientation orientation = Coord::NORMAL;
  Coord::Orientation mo = Coord::NORMAL;

  char* detstr =  image_->getString("DETSEC");
  Vector dv1,dv2;
  if (!(detstr && *detstr && parseSection(detstr,&dv1,&dv2))) {
    if (detstr)
      delete [] detstr;
    return 0;
  }
  delete [] detstr;
  BBox detsec = BBox(dv1,dv2);

  int xx = (dv1[0] < dv2[0]);
  int yy = (dv1[1] < dv2[1]);

  if (xx && yy)
    orientation = Coord::NORMAL;
  else if (!xx && yy)
    orientation = Coord::XX;
  else if (!xx && !yy)
    orientation = Coord::XY;
  else if (xx && !yy)
    orientation = Coord::YY;

  // DETSIZE
  char* sizestr = image_->getString("DETSIZE");
  Vector sv1(1,1);
  Vector sv2(10000,10000);
  if (sizestr && *sizestr) {
    if (!(parseSection(sizestr,&sv1,&sv2))) {
      delete [] sizestr;
      return 0;
    }
  }
  if (sizestr)
    delete [] sizestr;
  BBox detsize = BBox(sv1,sv2);
  
  // CCDSUM
  Vector ccdsum(1,1);
  char* ccdstr = image_->getString("CCDSUM");
  if (ccdstr && *ccdstr) {
    double Ns, Np, Ns1, Np1;
    string x(ccdstr);
    istringstream str(x);

    str >> Ns >> Np >> Ns1 >> Np1;
    ccdsum = Vector(1/Ns, 1/Np);
  }
  if (ccdstr)
    delete [] ccdstr;
  
  // origin
  Vector origin = detsec.ll * Scale(ccdsum) * Translate(-datasec.ll);

  // matrix
  // if the segment is flipped, we can have a discontinuity at
  // the edges, due to round off errors, so we 'nudge' it

  Matrix flip;
  switch (orientation) {
  case Coord::NORMAL:
    break;
  case Coord::XX:
    flip = FlipX();
      break;
  case Coord::YY:
    flip = FlipY();
      break;
  case Coord::XY:
    flip = FlipXY();
      break;
  }

  // internal flip
  Matrix mflip;
  switch (parent->IRAFOrientation(orientation)) {
  case Coord::NORMAL:
    break;
  case Coord::XX:
    mflip = FlipX();
    break;
  case Coord::YY:
    mflip = FlipY();
    break;
  case Coord::XY:
    mflip = FlipXY();
    break;
  }

  Vector center = datasec.center() * imageToData;
  Vector mcenter = detsize.center() * imageToData * Scale(ccdsum);

  wcsToRef =
    Translate(-center) *
    flip *
    Translate(center) *
    Translate(origin) * 
    Translate(-mcenter) *
    mflip *
    Translate(mcenter);

  // we do this to shift the origin to the middle of the image to match
  // the wcs case. Needed by imageBBox()
  // first? reset wcsToRef
  if (fits == this) {
    irafToRef = wcsToRef.invert();
    wcsToRef = Matrix();
  }
  else
    wcsToRef *= fits->irafToRef;

  if (DebugMosaic) {
    cerr << "ProcessKeywordsIRAF" << endl
	 << " datasec: " << datasec << endl
	 << " ccdsum : " << ccdsum << endl
	 << " detsize: " << detsize << endl
	 << " detsec : " << detsec << endl
	 << " matrix : " << wcsToRef << endl;
  }

  return 1;
}

int FitsImage::processKeywordsWCS(FitsImage* fits, Coord::CoordSystem sys)
{
  if (!fits->hasWCS(sys))
    return 0;

  wcsToRef = parent->calcAlignWCS(fits, this, sys, sys, Coord::FK5);

  if (DebugMosaic)
    cerr << "ProcessKeywordsWCS " << endl
	 << " matrix : " << wcsToRef << endl;

  return 1;
}

void FitsImage::render(RenderStage stage)
{
  // note: lack of break is intentional
  switch (stage) {
  case ORDER:
    order();
  case BLOCK:
    block();
  case ANALYSIS:
    analysis();
  }
}

void FitsImage::replaceWCS(FitsHead* hh)
{
  // Process OBJECT keyword
  if (objectName)
    delete [] objectName;
  objectName = hh->getString("OBJECT");

  // Process WCS keywords
  if (wcsHeader)
    delete wcsHeader;
  wcsHeader = new FitsHead(*hh);
  initWCS(hh, NULL);
}

void FitsImage::reset()
{
  if (fits_)
    delete fits_;
  fits_ =NULL;

  if (post_)
    delete post_;
  post_ =NULL;
  if (hpx_)
    delete hpx_;
  hpx_ =NULL;
  if (hist_)
    delete hist_;
  hist_ =NULL;

  base_ =NULL;
  if (basedata_)
    delete basedata_;
  basedata_ =NULL;

  if (manageOrder_) {
    if (order_)
      delete order_;
    if (orderdata_)
      delete orderdata_;
  }
  manageOrder_ =0;
  order_ =NULL;
  orderdata_ =NULL;

  if (manageBlock_) {
    if (block_)
      delete block_;
    if (blockdata_)
      delete blockdata_;
  }
  manageBlock_ =0;
  block_ =NULL;
  blockdata_ =NULL;

  if (manageAnalysis_) {
    if (analysis_)
      delete analysis_;
    if (analysisdata_)
      delete analysisdata_;
  }
  manageAnalysis_ =0;
  analysis_ =NULL;
  analysisdata_ =NULL;

  image_ =NULL;
  data_ =NULL;
}

void FitsImage::resetWCS()
{
  // Process OBJECT keyword
  if (objectName)
    delete [] objectName;
  objectName = image_->getString("OBJECT");

  // Process WCS keywords
  if (wcsHeader)
    delete wcsHeader;
  wcsHeader = NULL;
  initWCS(image_->head(), 
	  (image_->primary() && image_->inherit() ? image_->primary() : NULL));
}

void FitsImage::resetWCS0()
{
  int ii = Coord::WCS0-Coord::WCS;
  if (wcs[ii])
    wcsfree(wcs[ii]);
  wcs[ii] = NULL;
}

char* FitsImage::root(const char* fn)
{
  if (fn) {
    const char* ptr = fn;           // init the ptr
    while(*ptr++);                  // walk it forward to end of string
    ptr--;                          // backup one
    while(*ptr != '/' && ptr != fn) // walk it backward til last / or beginning
      ptr--;
    if (*ptr == '/')                // step it over the last '/'
      ptr++;
    return dupstr(ptr);             // got it!
  }
  else
    return NULL;
}

void FitsImage::setBinSliceFilter(const char* ff)
{
  if (binSliceFilter)
    delete [] binSliceFilter;

  binSliceFilter = dupstr(ff);
}

void FitsImage::setCropParams(int datasec)
{
  FitsBound* params;
  if (!datasec)
    params = &iparams;
  else
    params = &dparams;

  cparams.xmin = params->xmin;
  cparams.xmax = params->xmax;
  cparams.ymin = params->ymin;
  cparams.ymax = params->ymax;
}

void FitsImage::setCropParams(const Vector& ss, const Vector& tt, int datasec)
{
  // Coord are in DATA
  Vector ll = ss;
  Vector ur = tt;

  int xmin = ll[0];
  int xmax = ur[0];
  int ymin = ll[1];
  int ymax = ur[1];

  if (xmin>xmax) {
    xmin = ur[0];
    xmax = ll[0];
  }
  if (ymin>ymax) {
    ymin = ur[1];
    ymax = ll[1];
  }

  setCropParams(xmin,ymin,xmax,ymax,datasec);
}

void FitsImage::setCropParams(int x0, int y0, int x1, int y1, int datasec)
{
  FitsBound* params;
  if (!datasec)
    params = &iparams;
  else
    params = &dparams;

  // Coords are in DATA
  if (x0<params->xmin)
    x0=params->xmin;
  if (x0>params->xmax)
    x0=params->xmax;
  if (x1<params->xmin)
    x1=params->xmin;
  if (x1>params->xmax)
    x1=params->xmax;

  if (y0<params->ymin)
    y0=params->ymin;
  if (y0>params->ymax)
    y0=params->ymax;
  if (y1<params->ymin)
    y1=params->ymin;
  if (y1>params->ymax)
    y1=params->ymax;

  cparams.xmin = x0;
  cparams.xmax = x1;
  cparams.ymin = y0;
  cparams.ymax = y1;
}

void FitsImage::setCrop3dParams()
{
  // params is a BBOX in DATA coords 0-n
  cparams.zmin = iparams.zmin;
  cparams.zmax = iparams.zmax;
}

void FitsImage::setCrop3dParams(double z0, double z1)
{
  // params is a BBOX in DATA coords 0-n
  double zmin = z0;
  double zmax = z1;

  // always have at least 1
  if (zmin+1>zmax)
    zmax = z0+1;

  // round to int
  setCrop3dParams(int(zmin+.5), int(zmax+.5));
}

void FitsImage::setCrop3dParams(int z0, int z1)
{
  // params is a BBOX in DATA coords 0-n
  if (z0<iparams.zmin) {
    z0=iparams.zmin;
    if (z0+1>z1)
      z1=z0+1;
  }

  if (z1>iparams.zmax) {
    z1=iparams.zmax;
    if (z0+1>z1)
      z0=z1-1;
  }

  cparams.zmin = z0;
  cparams.zmax = z1;
}

void FitsImage::setMinMaxParams()
{
  if (mparams)
    delete mparams;
  mparams = NULL;
}

void FitsImage::setMinMaxParams(int x0, int y0, int x1, int y1)
{
  if (mparams)
    delete mparams;
  mparams = new FitsBound(x0,y0,x1,y1);
}

void FitsImage::setFileName(const char* fn)
{
  if (fullBaseFileName)
    delete [] fullBaseFileName;
  fullBaseFileName = NULL;

  if (rootBaseFileName)
    delete [] rootBaseFileName;
  rootBaseFileName = NULL;

  if (iisFileName)
    delete [] iisFileName;
  iisFileName = NULL;

  // no filename to set
  if (!fn)
    return;

  // strip any '[]'
  char* ffn = strip(fn);

  FitsFile* ptr = post_ ? post_ : fits_;
  if (!ptr)
    return;

  const char* ext = ptr->extname();
  if (ext) {
    {
      ostringstream str;
      str << ffn << '[' << ext << ']' << ends;
      fullBaseFileName = dupstr(str.str().c_str());
    }
    {
      char* m = root(ffn);
      ostringstream str;
      str << m << '[' << ext << ']' << ends;
      rootBaseFileName = dupstr(str.str().c_str());
      delete [] m;
    }
  }
  else if (ptr->ext()) {
    {
      ostringstream str;
      str << ffn << '[' << ptr->ext() << ']' << ends;
      fullBaseFileName = dupstr(str.str().c_str());
    }
    {
      char* m = root(ffn);
      ostringstream str;
      str << m << '[' << ptr->ext() << ']' << ends;
      rootBaseFileName = dupstr(str.str().c_str());
      delete [] m;
    }
  }
  else {
    fullBaseFileName = dupstr(ffn);
    rootBaseFileName = root(ffn);
  }
  
  // by default, iisFileName is fullBaseFileName
  if (fullBaseFileName)
    iisFileName = dupstr(fullBaseFileName);

  delete [] ffn;
  updateFileName();
}

void FitsImage::setObjectName(const char* obj)
{
  if (objectName)
    delete [] objectName;
  objectName = dupstr(obj);
}

char* FitsImage::strip(const char* fn)
{
  if (fn) {
    char* r = dupstr(fn);           // dup the string
    char* ptr = r;                  // init the ptr
    while(*ptr != '[' && *ptr)      // walk it forward til '[' or end
      ptr++;
    *ptr = '\0';                    // zero out rest

    return r;                       // got it!
  }
  else
    return NULL;
}

int FitsImage::nhdu()
{
  int dd =1;
  for (int ii=2; ii<FTY_MAXAXES; ii++)
    if (naxis(ii))
      dd *= naxis(ii);
  return dd;
}

void FitsImage::updateClip(FrScale* fr)
{
  if (data_) 
    if (mparams)
      data_->updateClip(fr,mparams);
    else
      data_->updateClip(fr,getDataParams(fr->scanMode()));
}

void FitsImage::updateFileName()
{
  if (fullFileName)
    delete [] fullFileName;
  fullFileName = NULL;

  if (rootFileName)
    delete [] rootFileName;
  rootFileName = NULL;

  if (fullFileName3d)
    delete [] fullFileName3d;
  fullFileName3d = NULL;

  if (rootFileName3d)
    delete [] rootFileName3d;
  rootFileName3d = NULL;

  // note: 3d bin'd tables will be of type isImage()

  if (isImage() && !binSliceFilter) {
    char* sec =NULL;
    char* sec3d =NULL;
    char* add =NULL;

    // 2d/3d section
    switch (parent->scanMode()) {
    case FrScale::IMGSEC:
    case FrScale::DATASEC:
      break;
    case FrScale::CROPSEC:
      {
	FitsBound* params =getDataParams(FrScale::CROPSEC);

	// params is a BBOX in DATA coords 0-n
	//   xlate to 1-n
	Vector3d ll= Vector3d(params->xmin,params->ymin,params->zmin)*
	  Translate3d(1,1,1);
	Vector3d ur(params->xmax,params->ymax,params->zmax);
	{
	  ostringstream str;
	  str << ll[0] << ':' << ur[0] << ',' 
	      << ll[1] << ':' << ur[1] << ends;
	  sec = dupstr(str.str().c_str());
	}

	{
	  ostringstream str;
	  str << ll[0] << ':' << ur[0] << ',' 
	      << ll[1] << ':' << ur[1] << ','
	      << ll[2] << ':' << ur[2] << ends;
	  sec3d = dupstr(str.str().c_str());
	}
      }    
      break;
    }

    // address
    {
      int doAdd =0;
      ostringstream str;

      int jj;
      for (jj=FTY_MAXAXES-1; jj>=2; jj--) {
	if (address[jj]!=1)
	  break;
      }	
      jj++;
      for (int ii=2; ii<jj; ii++) {
	doAdd =1;
	if (ii==2)
	  str << "plane=";
	else
	  str << ':';
	str << address[ii];
      }
      if (doAdd) {
	str << ends;
	add = dupstr(str.str().c_str());
      }
    }

    if (fullBaseFileName) {
      ostringstream str;

      if (add && sec)
	str << fullBaseFileName << '[' << add << ']' << '[' << sec << ']';
      else if (add && !sec)
	str << fullBaseFileName << '[' << add << ']';
      else if (!add && sec)
	str << fullBaseFileName << '[' << sec << ']';
      else
	str << fullBaseFileName;
      str << ends;

      fullFileName = dupstr(str.str().c_str());
    }

    if (rootBaseFileName) {
      ostringstream str;

      if (add && sec)
	str << rootBaseFileName << '[' << add << ']' << '[' << sec << ']';
      else if (add && !sec)
	str << rootBaseFileName << '[' << add << ']';
      else if (!add && sec)
	str << rootBaseFileName << '[' << sec << ']';
      else
	str << rootBaseFileName;
      str << ends;

      rootFileName = dupstr(str.str().c_str());
    }

    if (fullBaseFileName) {
      ostringstream str;

      if (sec3d)
	str << fullBaseFileName << '[' << sec3d << ']';
      else
	str << fullBaseFileName;
      str << ends;

      fullFileName3d = dupstr(str.str().c_str());
    }

    if (rootBaseFileName) {
      ostringstream str;

      if (sec3d)
	str << rootBaseFileName << '[' << sec3d << ']';
      else
	str << rootBaseFileName;
      str << ends;

      rootFileName3d = dupstr(str.str().c_str());
    }

    if (sec)
      delete [] sec;
    if (sec3d)
      delete [] sec3d;
    if (add)
      delete [] add;
  }
  else if (isBinTable() || binSliceFilter) {
    char* sec = NULL;
    char* filter = (char*)fits_->pFilter();
    int doFilter = (filter && *filter);
    int doSlice = (binSliceFilter && *binSliceFilter);
    
    switch (parent->scanMode()) {
    case FrScale::IMGSEC:
    case FrScale::DATASEC:
      break;
    case FrScale::CROPSEC:
      {
	ostringstream secstr;
	FitsBound* params =getDataParams(FrScale::CROPSEC);

	// dataToPhysical not set yet
	Vector ll = Vector(params->xmin,params->ymin) *
	  dataToImage * imageToPhysical;
	Vector ur = Vector(params->xmax,params->ymax) * 
	  dataToImage * imageToPhysical;
	secstr << fits_->pBinX() << ">=" << ll[0] << ',' 
	       << fits_->pBinX() << "<=" << ur[0] << ','
	       << fits_->pBinY() << ">=" << ll[1] << ',' 
	       << fits_->pBinY() << "<=" << ur[1] << ends;
	sec = dupstr(secstr.str().c_str());
      }    
      break;
    }

    if (fullBaseFileName) {
      ostringstream str;

      str << fullBaseFileName;
      if (doFilter && sec && doSlice)
	str << '[' << filter << ',' << sec << ',' << binSliceFilter << ']';
      else if (doFilter && sec && !doSlice)
	str << '[' << filter << ',' << sec << ']';
      else if (doFilter && !sec && doSlice)
	str << '[' << filter << ',' << binSliceFilter << ']';
      else if (doFilter && !sec && !doSlice)
	str << '[' << filter << ']';
      else if (!doFilter && sec && doSlice)
	str << '[' << sec << ',' << binSliceFilter << ']';
      else if (!doFilter && sec && !doSlice)
	str << '[' << sec << ']';
      else if (!doFilter && !sec && doSlice)
	str << '[' << binSliceFilter << ']';
      str << ends;

      fullFileName = dupstr(str.str().c_str());
      fullFileName3d = dupstr(fullFileName);
    }

    if (rootBaseFileName) {
      ostringstream str;

      str << rootBaseFileName;
      if (doFilter && sec && doSlice)
	str << '[' << filter << ',' << sec << ',' << binSliceFilter << ']';
      else if (doFilter && sec && !doSlice)
	str << '[' << filter << ',' << sec << ']';
      else if (doFilter && !sec && doSlice)
	str << '[' << filter << ',' << binSliceFilter << ']';
      else if (doFilter && !sec && !doSlice)
	str << '[' << filter << ']';
      else if (!doFilter && sec && doSlice)
	str << '[' << sec << ',' << binSliceFilter << ']';
      else if (!doFilter && sec && !doSlice)
	str << '[' << sec << ']';
      else if (!doFilter && !sec && doSlice)
	str << '[' << binSliceFilter << ']';
      str << ends;

      rootFileName = dupstr(str.str().c_str());
      rootFileName3d = dupstr(rootFileName);
    }
  }
}

void FitsImage::updateMatrices(Matrix& rgbToRef,
			       Matrix& refToWidget,
			       Matrix& widgetToCanvas)
{
  dataToRef = wcsToRef * rgbToRef;
  refToData = dataToRef.invert();

  dataToWidget = dataToRef * refToWidget;
  widgetToData = dataToWidget.invert();

  refToCanvas = refToWidget * widgetToCanvas;
  canvasToRef = refToCanvas.invert();

  imageToRef = imageToData * dataToRef;
  refToImage = imageToRef.invert();

  imageToWidget = imageToRef * refToWidget;
  widgetToImage = imageToWidget.invert();

  physicalToRef = physicalToImage * imageToData * dataToRef;
  refToPhysical = physicalToRef.invert();

  amplifierToRef = amplifierToPhysical * physicalToRef;
  refToAmplifier = amplifierToRef.invert();

  detectorToRef = detectorToPhysical * physicalToRef;
  refToDetector = detectorToRef.invert();
}

void FitsImage::updateMatrices(Matrix3d& refToWidget3d)
{
  dataToWidget3d = dataToRef3d * refToWidget3d;
  widgetToData3d = dataToWidget3d.invert();
}

void FitsImage::updatePannerMatrices(Matrix& refToPanner)
{
  dataToPanner = dataToRef * refToPanner;
  pannerToData = dataToPanner.invert();
}

void FitsImage::updatePannerMatrices(Matrix3d& refToPanner3d)
{
  dataToPanner3d = dataToRef3d * refToPanner3d;
  pannerToData3d = dataToPanner3d.invert();
}

void FitsImage::updateMagnifierMatrices(Matrix& refToMagnifier)
{
  dataToMagnifier = dataToRef * refToMagnifier;
  magnifierToData = dataToMagnifier.invert();
}

void FitsImage::updateMagnifierMatrices(Matrix3d& refToMagnifier3d)
{
  dataToMagnifier3d = dataToRef3d * refToMagnifier3d;
  magnifierToData3d = dataToMagnifier3d.invert();
}

void FitsImage::updatePS(Matrix ps)
{
  dataToPS = dataToRef * ps;
  psToData = dataToPS.invert();
}

void FitsImage::updatePS(Matrix3d ps)
{
  dataToPS3d = dataToRef3d * ps;
  psToData3d = dataToPS3d.invert();
}

// WCS

Vector FitsImage::getWCScrpix(Coord::CoordSystem sys)
{
  if (hasWCS(sys)) {
    int ii = sys-Coord::WCS;

    if (!wcs[ii]->coorflip)
      return Vector(wcs[ii]->crpix[0], wcs[ii]->crpix[1]);
    else
      return Vector(wcs[ii]->crpix[1], wcs[ii]->crpix[0]);
  }
  else
    return Vector();
}

Vector FitsImage::getWCScrval(Coord::CoordSystem sys)
{
  if (hasWCS(sys)) {
    int ii = sys-Coord::WCS;

    if (!wcs[ii]->coorflip)
      return Vector(wcs[ii]->crval[0], wcs[ii]->crval[1]);
    else
      return Vector(wcs[ii]->crval[1], wcs[ii]->crval[0]);
  }
  else
    return Vector();
}

Vector FitsImage::getWCScdelt(Coord::CoordSystem sys)
{
  if (hasWCS(sys)) {
    int ii = sys-Coord::WCS;

    if (!wcs[ii]->coorflip)
      return Vector(wcs[ii]->cdelt[0], wcs[ii]->cdelt[1]);
    else
      return Vector(wcs[ii]->cdelt[1], wcs[ii]->cdelt[0]);
  }
  else
    return Vector();
}

Coord::Orientation FitsImage::getWCSOrientation(Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  if (hasWCS(sys)) {
    Vector orpix = center();
    Vector orval = pix2wcs(orpix, sys, sky);
    Vector delta = getWCScdelt(sys).abs();
    Vector npix = wcs2pix(Vector(orval[0],orval[1]+delta[1]), sys, sky);
    Vector north = (npix-orpix).normalize();
    Vector epix = wcs2pix(Vector(orval[0]+delta[0],orval[1]), sys, sky);
    Vector east = (epix-orpix).normalize();

    // sanity check
    Vector diff = (north-east).abs();
    if ((north[0]==0 && north[1]==0) ||
	(east[0]==0 && east[1]==0) ||
	(diff[0]<.01 && diff[1]<.01))
      return Coord::NORMAL;

    // take the cross product and see which way the 3rd axis is pointing
    double w = east[0]*north[1]-east[1]*north[0];

    if (!hasWCSCel(sys))
      return w>0 ? Coord::NORMAL : Coord::XX;
    else
      return w<0 ? Coord::NORMAL : Coord::XX;
  }
}

double FitsImage::getWCSRotation(Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  if (hasWCS(sys)) {
    Vector orpix = center();
    Vector orval = pix2wcs(orpix, sys, sky);
    Vector pix = wcs2pix(orval, sys, sky);
    Vector delta = getWCScdelt(sys).abs();
    Vector npix = wcs2pix(Vector(orval[0],orval[1]+delta[1]), sys, sky);
    Vector north = (npix-orpix).normalize();
    Vector epix = wcs2pix(Vector(orval[0]+delta[0],orval[1]), sys, sky);
    Vector east = (epix-orpix).normalize();

    // sanity check
    Vector diff = (north-east).abs();
    if ((north[0]==0 && north[1]==0) ||
	(east[0]==0 && east[1]==0) ||
	(diff[0]<.01 && diff[1]<.01))
      return 0;

    if (wcs[sys-Coord::WCS]->imflip)
      return  (north.angle()-M_PI_2);
    else 
      return -(north.angle()-M_PI_2);
  }
  return 0;
}

// AST
Vector FitsImage::ASTpix2wcs(Vector in, Coord::CoordSystem sys, 
			     Coord::SkyFrame sky)
{
  astClearStatus;

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii]) {
    double xx =0;
    double yy =0;
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      setAstSkyFrame(ast[ii],sky);
      astTran2(ast[ii], 1, in.v, in.v+1, 1, &xx, &yy);
      if (astOK)
	if (checkAst(xx,yy))
	  return Vector(radToDeg(xx),yy*180./M_PI);
    }
    else {
      astTran2(ast[ii], 1, in.v, in.v+1, 1, &xx, &yy);
      if (astOK)
	if (checkAst(xx,yy))
	  return Vector(xx,yy);
    }
  }

  maperr =1;
  return Vector();
}

Vector* FitsImage::ASTpix2wcs(Vector* in, int num, 
			      Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  astClearStatus;
  double xin[num];
  double yin[num];
  double xout[num];
  double yout[num];

  Vector* out = new Vector[num];
  for (int ii=0; ii<num; ii++) {
    xin[ii] = (in[ii])[0];
    yin[ii] = (in[ii])[1];
  }

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii]) {
    double xx =0;
    double yy =0;
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      setAstSkyFrame(ast[ii],sky);
      astTran2(ast[ii], num, xin, yin, 1, xout, yout);
      if (astOK) {
	for (int ii=0; ii<num; ii++)
	  if (checkAst(xout[ii],yout[ii]))
	    out[ii] = Vector(radToDeg(xout[ii]),yout[ii]*180./M_PI);
	return out;
      }
    }
    else {
      astTran2(ast[ii], num, xin, yin, 1, xout, yout);
      if (astOK) {
	for (int ii=0; ii<num; ii++)
	  if (checkAst(xout[ii],yout[ii]))
	    out[ii] = Vector(xout[ii],yout[ii]);
	return out;
      }
    }
  }

  maperr =1;
  return out;
}

char* FitsImage::ASTpix2wcs(Vector in, Coord::CoordSystem sys, 
			    Coord::SkyFrame sky, Coord::SkyFormat format,
			    char* lbuf, int len)
{
  astClearStatus;

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii]) {
    double xx =0;
    double yy =0;
    ostringstream str;
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      setAstSkyFrame(ast[ii],sky);
      astTran2(ast[ii], 1, in.v, in.v+1, 1, &xx, &yy);
      if (!astOK || !checkAst(xx,yy)) {
	maperr =1;
	lbuf[0] = '\0';
	return lbuf;
      }

      switch (format) {
      case Coord::DEGREES:
	xx =radToDeg(xx); // 0 to 360
	yy *=180./M_PI;

	str << setprecision(8) << xx << ' ' << yy 
	    << ' ' << coord.skyFrameStr(sky) << ends;
	break;

      case Coord::SEXAGESIMAL:
	switch (sky) {
	case Coord::FK4:
	case Coord::FK4_NO_E:
	case Coord::FK5:
	case Coord::ICRS:
	  xx = zeroTWOPI(xx);
	  setAstFormat(ast[ii],1,"hms.3");
	  setAstFormat(ast[ii],2,"+dms.2");
	  break;
	case Coord::GALACTIC:
	case Coord::SUPERGALACTIC:
	case Coord::ECLIPTIC:
	case Coord::HELIOECLIPTIC:
	  xx = zeroTWOPI(xx);
	  setAstFormat(ast[ii],1,"+dms.3");
	  setAstFormat(ast[ii],2,"+dms.3");
	  break;
	}

	str << astFormat(ast[ii], 1, xx) << ' ' << astFormat(ast[ii], 2, yy) 
	    << ' ' << coord.skyFrameStr(sky) << ends;
	break;
      }
    }
    else {
      astTran2(ast[ii], 1, in.v, in.v+1, 1, &xx, &yy);
      if (!astOK || !checkAst(xx,yy)) {
	maperr =1;
	return lbuf;
      }
      str << setprecision(8) << xx << ' ' << yy << ends;
    }

    strncpy(lbuf, str.str().c_str(), str.str().length());
  }

  return lbuf;
}

Vector FitsImage::ASTwcs2pix(Vector in, Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  astClearStatus;

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii]) {
    double xx =0;
    double yy =0;
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      setAstSkyFrame(ast[ii],sky);
      Vector rr = in*M_PI/180.;
      astTran2(ast[ii], 1, rr.v, &(rr[1]), 0, &xx, &yy);
      if (astOK)
	if (checkAst(xx,yy))
	  return Vector(xx,yy);
    }
    else {
      astTran2(ast[ii], 1, in.v, in.v+1, 0, &xx, &yy);
      if (astOK)
	if (checkAst(xx,yy))
	  return Vector(xx,yy);
    }
  }

  maperr =1;
  return Vector();
}

Vector* FitsImage::ASTwcs2pix(Vector* in, int num, 
			      Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  astClearStatus;
  double xin[num];
  double yin[num];
  double xout[num];
  double yout[num];

  Vector* out = new Vector[num];
  for (int ii=0; ii<num; ii++) {
    xin[ii] = (in[ii])[0];
    yin[ii] = (in[ii])[1];
  }

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii]) {
    double xx =0;
    double yy =0;
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      setAstSkyFrame(ast[ii],sky);
      for (int ii=0; ii<num; ii++) {
	xin[ii] *= M_PI/180.;
	yin[ii] *= M_PI/180.;
      }
      astTran2(ast[ii], num, xin, yin, 0, xout, yout);
      if (astOK) {
	for (int ii=0; ii<num; ii++)
	  if (checkAst(xout[ii],yout[ii]))
	    out[ii] = Vector(xout[ii],yout[ii]);
	return out;
      }
    }
    else {
      astTran2(ast[ii], num, xin, yin, 0, xout, yout);
      if (astOK) {
	for (int ii=0; ii<num; ii++)
	  if (checkAst(xout[ii],yout[ii]))
	    out[ii] = Vector(xout[ii],yout[ii]);
	return out;
      }
    }
  }

  maperr =1;
  return out;
}

double FitsImage::ASTwcsdist(Vector a, Vector b, Coord::CoordSystem sys)
{
  astClearStatus;

  int ii = sys-Coord::WCS;
  double rr=0;
  if (ii>=0 && ast && ast[ii]) {
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      Vector aa = a*M_PI/180.;
      Vector bb = b*M_PI/180.;
      rr = astDistance(ast[ii], aa.v, bb.v) *180./M_PI;
    }
    else
      rr = astDistance(ast[ii], a.v, b.v);

    if (!astOK) {
      maperr =1;
      return 0;
    }
  }

  return rr;
}

// WCSSUB
int FitsImage::hasWCS(Coord::CoordSystem sys)
{
  int ii = sys-Coord::WCS;
  return (sys>=Coord::WCS && ast && ast[ii]) ? 1 : 0;
}

int FitsImage::hasWCSEqu(Coord::CoordSystem sys)
{
  astClearStatus;

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii])
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT))) {
      // special case of xLON/xLAT
      char* bb = &(wcs[ii]->c1type[1]);
      if (!strncmp(bb,"LON",3) || !strncmp(bb,"LAT",3))
	switch (wcs[ii]->c1type[0]) {
	case 'G':
	case 'H':
	case 'E':
	case 'S':
	  return 1;
	default:
	  return 0;
	}

      // special case of xyLN/xyLT
      char* cc = &(wcs[ii]->c1type[2]);
      if (!strncmp(cc,"LN",2) || !strncmp(cc,"LT",3))
	return 0;

      return 1;
    }

  return 0;
}

int FitsImage::hasWCSCel(Coord::CoordSystem sys)
{
  astClearStatus;

  int ii = sys-Coord::WCS;
  if (ii>=0 && ast && ast[ii])
    if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT)))
      return 1;

  return 0;
}

// WCSX

int FitsImage::hasWCSx(Coord::CoordSystem sys, int ss)
{
  int rr = ss-2;
  int ii = sys-Coord::WCS;
  int kk = WCSXMAX*rr+ii;
  return (rr<WCSXMAX && sys>=Coord::WCS && wcsx[kk]) ? 1 : 0;
}

double FitsImage::pix2wcsx(double in, Coord::CoordSystem sys, int ss)
{
  if (hasWCSx(sys,ss)) {
    int rr = ss-2;
    int ii = sys-Coord::WCS;
    int kk = WCSXMAX*rr+ii;
    return (in-crpixx[kk])*cdx[kk] + crvalx[kk];
  }
  else
    return in;
}

double FitsImage::wcs2pixx(double in, Coord::CoordSystem sys, int ss)
{
  if (hasWCSx(sys,ss)) {
    int rr = ss-2;
    int ii = sys-Coord::WCS;
    int kk = WCSXMAX*rr+ii;
    return (in-crvalx[kk])/cdx[kk] + crpixx[kk];
  }
  else
    return in;
}

// WCS/AST support

void FitsImage::wcsShow(WorldCoor* ww)
{
  if (!ww)
    return;

  int n = ww->naxes;
  int nn = n*n;

  cerr << "wcs->wcsname=" << (ww->wcsname ? ww->wcsname : "") << endl;
  cerr << "wcs->naxes=" << ww->naxes << endl;
  cerr << "wcs->naxis=" << ww->naxis << endl;

  cerr << "wcs->radecsys=" << ww->radecsys << endl;
  cerr << "wcs->equinox=" << ww->equinox << endl;
  cerr << "wcs->epoch=" << ww->epoch << endl;

  cerr << "wcs->ctype[0]=" << ww->ctype[0] << endl;
  cerr << "wcs->ctype[1]=" << ww->ctype[1] << endl;
  cerr << "wcs->c1type=" << ww->c1type << endl;
  cerr << "wcs->c2type=" << ww->c2type << endl;
  cerr << "wcs->ptype=" << ww->ptype << endl;

  for (int jj=0; jj<n; jj++)
    cerr << "wcs->crpix[" << jj << "]=" << ww->crpix[jj] << endl;
  for (int jj=0; jj<n; jj++)
    cerr << "wcs->crval[" << jj << "]=" << ww->crval[jj] << endl;
  for (int jj=0; jj<n; jj++)
    cerr << "wcs->cdelt[" << jj << "]=" << ww->cdelt[jj] << endl;

  for (int jj=0; jj<4; jj++)
    cerr << "wcs->cd[" << jj << "]=" << ww->cd[jj] << endl;
  for (int jj=0; jj<nn; jj++)
    cerr << "wcs->pc[" << jj << "]=" << ww->pc[jj] << endl;

  cerr << "wcs->longpole=" << ww->longpole << endl;
  cerr << "wcs->latpole=" << ww->latpole << endl;
  cerr << "wcs->prjcode=" << ww->prjcode << endl;

  cerr << "wcs->rot=" << ww->rot << endl;
  cerr << "wcs->imrot=" << ww->imrot << endl;
  cerr << "wcs->imflip=" << ww->imflip << endl;
  cerr << "wcs->pa_north=" << ww->pa_north << endl;
  cerr << "wcs->pa_east=" << ww->pa_east << endl;
  cerr << "wcs->coorflip=" << ww->coorflip << endl;

  cerr << "wcs->syswcs=" << ww->syswcs << endl;
  cerr << "wcs->wcsproj=" << ww->wcsproj << endl;
  cerr << "wcs->distcode=" << ww->distcode << endl;
}

void FitsImage::astinit(int ii, FitsHead* hh)
{
  if (!wcs[ii]) {
    ast[ii] = NULL;
    return;
  }

  // DSS,PLT,LIN goes straight to AST
  // we can't send 3D directly to AST
  if (wcs[ii]->prjcode==WCS_DSS || 
      wcs[ii]->prjcode==WCS_PLT || 
      (wcs[ii]->prjcode==WCS_LIN && !strncmp(wcs[ii]->ptype,"HPX",3)) ||
      (wcs[ii]->prjcode==WCS_LIN && !strncmp(wcs[ii]->ptype,"TAB",3)) ||
      (wcs[ii]->prjcode==WCS_LIN && !strncmp(wcs[ii]->c1type,"AST",3)))
    ast[ii] = fits2ast(hh);
  else
    ast[ii] = wcs2ast(ii);

  if (!ast[ii])
    return;

  // set default skyframe
  if (astIsASkyFrame(astGetFrame(ast[ii], AST__CURRENT)))
    setAstSkyFrame(ast[ii],Coord::FK5);

  if (DebugAST)
    astShow(ast[ii]);
}

int FitsImage::checkAst(double x, double y)
{
  // check for reasonable values
  return (fabs(x) < FLT_MAX && fabs(y) < FLT_MAX) ? 1 : 0;
}

void FitsImage::setAstFormat(AstFrameSet* aa, int id, const char* format)
{
  // is it already set?
  // ast is very slow when changing params
  {
    ostringstream str;
    str << "Format(" << id << ")" << ends;
    const char* out = astGetC(aa, str.str().c_str());
    if (!strcmp(out,format))
      return;
  }

  ostringstream str;
  str << "Format(" << id << ")=" << format << ends;
  astSet(aa, str.str().c_str());
}

void FitsImage::setAstSkyFrame(AstFrameSet* aa, Coord::SkyFrame sky)
{
  // is sky frame
  if (!astIsASkyFrame(astGetFrame(aa, AST__CURRENT)))
    return;

  // is it already set?
  // ast is very slow when changing system,equinox
  const char* str = astGetC(aa, "System");

  // TLON/XLON and HPX will do this
  if (!strncmp(str,"Unknown",3))
    return;

  switch (sky) {
  case Coord::FK4_NO_E:
    if (!strncmp(str,"FK4-NO-E",8))
      return;
    astSet(aa, "System=FK4-NO-E, Equinox=B1950");
    return;
  case Coord::FK4:
    if (!strncmp(str,"FK4",3))
      return;
    astSet(aa, "System=FK4, Equinox=B1950");
    return;
  case Coord::FK5:
    if (!strncmp(str,"FK5",3))
      return;
    astSet(aa, "System=FK5, Equinox=J2000");
    return;
  case Coord::ICRS:
    if (!strncmp(str,"ICRS",4))
      return;
    astSet(aa, "System=ICRS");
    return;
  case Coord::GALACTIC:
    if (!strncmp(str,"GALACTIC",8))
      return;
    astSet(aa, "System=GALACTIC");
    return;
  case Coord::SUPERGALACTIC:
    if (!strncmp(str,"SUPERGALACTIC",13))
      return;
    astSet(aa, "System=SUPERGALACTIC");
    return;
  case Coord::ECLIPTIC:
    if (!strncmp(str,"ECLIPTIC",8))
      return;
    astSet(aa, "System=ECLIPTIC");
    // get AST to agree with WCSSUBS
    astSetD(aa, "EQUINOX", astGetD(aa, "EPOCH"));
    return;
  case Coord::HELIOECLIPTIC:
    if (!strncmp(str,"HELIOECLIPTIC",13))
      return;
    astSet(aa, "System=HELIOECLIPTIC");
    return;
  }
}

AstFrameSet* FitsImage::fits2ast(FitsHead* hh) 
{
  FitsHead* head = hh;

  // we may have an error, just reset
  astClearStatus;

  // new fitschan
  AstFitsChan* chan = astFitsChan(NULL, NULL, "");
  if (!astOK || chan == AST__NULL)
    return NULL;

  // no warning messages
  astClear(chan,"Warnings");

  // fill up chan
  char* cards =NULL;
  int ncards =0;

  if (!head)
    if (image_)
      head = image_->head();

  if (head) {
    cards = head->cards();
    ncards = head->ncard();
  }

  if (cards == NULL || ncards == 0)
    return NULL;

  for (int i=0; i<ncards; i++) {
    char buf[81];
    strncpy(buf,cards+(i*80),80);
    buf[80] = '\0';

    astPutFits(chan, buf, 0);
    // sometimes, we get a bad parse, just ignore
    if (!astOK)
      astClearStatus;
  }

  // enable -TAB
  //astSetI(chan,"TabOK",1);

  // we may have an error, just reset
  astClearStatus;
  astClear(chan, "Card");

  // parse header
  AstFrameSet* frameSet = (AstFrameSet*)astRead(chan);

  // do we have anything?
  if (!astOK || frameSet == AST__NULL || 
      strncmp(astGetC(frameSet,"Class"), "FrameSet", 8))
    return NULL;

  return frameSet;
}

AstFrameSet* FitsImage::wcs2ast(int ww) 
{
  // read wcs struct into astChannel
  // we may have an error, just reset
  astClearStatus;

  // new fitschan
  AstFitsChan* chan;
  chan = astFitsChan(NULL, NULL, "");
  if (!astOK || chan == AST__NULL)
    return NULL;

  // no warning messages
  astClear(chan,"Warnings");

  // fill up chan
  FitsFile* fits = image_;

  if (DebugAST)
    cerr << endl << "wcs2ast()" << endl;

  // Alt WCS
  char alt = (ww==0) ? ' ' : (char)('@'+ww);

  // CTYPE
  if (wcs[ww]->prjcode == WCS_TAN && wcs[ww]->distcode) {
    // SIP
    {
      ostringstream str;
      str << wcs[ww]->ctype[0] << "-SIP" << ends;
      putFitsCard(chan, "CTYPE1", str.str().c_str());
    }
    {
      ostringstream str;
      str << wcs[ww]->ctype[1] << "-SIP" << ends;
      putFitsCard(chan, "CTYPE2", str.str().c_str());
    }
  }
  else if (wcs[ww]->prjcode == WCS_PIX || wcs[ww]->prjcode == WCS_LIN) {
    // this is not a mistake, WCS manges the ctype[0] and ctype[1]
    putFitsCard(chan, "CTYPE1", wcs[ww]->c1type);
    putFitsCard(chan, "CTYPE2", wcs[ww]->c2type);
  }
  else {
    putFitsCard(chan, "CTYPE1", wcs[ww]->ctype[0]);
    putFitsCard(chan, "CTYPE2", wcs[ww]->ctype[1]);
  }

  // CRPIX/CRVAL
  putFitsCard(chan, "CRPIX1", wcs[ww]->crpix[0]);
  putFitsCard(chan, "CRPIX2", wcs[ww]->crpix[1]);
  putFitsCard(chan, "CRVAL1", wcs[ww]->crval[0]);
  putFitsCard(chan, "CRVAL2", wcs[ww]->crval[1]);

  // CD/CDELT/PC
  // This is very complicated. AST is very, very, very picky as to which
  // keywords it use...
  {
    ostringstream cd;
    cd << "CD1_1" << alt << ends;
    ostringstream cdelt;
    cdelt << "CDELT1" << alt << ends;
    ostringstream pc;
    pc << "PC1_1" << alt << ends;

    if (fits->find(cd.str().c_str(), wcsHeader)) {
      // simple case CD
      // no rotation, no poles, no LIN, no inner cd values
      if (!wcs[ww]->cd[1] && !wcs[ww]->cd[2] && 
	  !wcs[ww]->rot &&
	  wcs[ww]->latpole == 999 && 
	  wcs[ww]->longpole == 999 &&
	  wcs[ww]->prjcode != WCS_PIX && 
	  wcs[ww]->prjcode != WCS_LIN) {
	putFitsCard(chan, "CDELT1", wcs[ww]->cdelt[0]);
	putFitsCard(chan, "CDELT2", wcs[ww]->cdelt[1]);
      }
      else {
	putFitsCard(chan, "CD1_1", wcs[ww]->cd[0]);
	putFitsCard(chan, "CD1_2", wcs[ww]->cd[1]);
	putFitsCard(chan, "CD2_1", wcs[ww]->cd[2]);
	putFitsCard(chan, "CD2_2", wcs[ww]->cd[3]);
      }
    }
    // CDELT
    else if (fits->find(cdelt.str().c_str(), wcsHeader)) {
      putFitsCard(chan, "CDELT1", wcs[ww]->cdelt[0]);
      putFitsCard(chan, "CDELT2", wcs[ww]->cdelt[1]);

      if (fits->find(pc.str().c_str(), wcsHeader)) {
	putFitsCard(chan, "PC1_1", wcs[ww]->pc[0]);
	putFitsCard(chan, "PC1_2", wcs[ww]->pc[1]);
	putFitsCard(chan, "PC2_1", wcs[ww]->pc[2]);
	putFitsCard(chan, "PC2_2", wcs[ww]->pc[3]);
      }
      else if (!ww && fits->find("PC001001", wcsHeader)) {
	putFitsCard(chan, "PC001001", wcs[ww]->pc[0]);
	putFitsCard(chan, "PC001002", wcs[ww]->pc[1]);
	putFitsCard(chan, "PC002001", wcs[ww]->pc[2]);
	putFitsCard(chan, "PC002002", wcs[ww]->pc[3]);
      }
      else {
	if (!ww && fits->find("CROTA1", wcsHeader))
	  putFitsCard(chan, "CROTA1", wcs[ww]->rot);
	if (!ww && fits->find("CROTA2", wcsHeader))
	  putFitsCard(chan, "CROTA2", wcs[ww]->rot);
      }
    }
    // sanity check
    else if (!wcs[ww]->cd[0] && 
	     !wcs[ww]->cd[1] && 
	     !wcs[ww]->cd[2] && 
	     !wcs[ww]->cd[3]) {
      putFitsCard(chan, "CDELT1", wcs[ww]->cdelt[0]);
      putFitsCard(chan, "CDELT2", wcs[ww]->cdelt[1]);
      putFitsCard(chan, "PC1_1", wcs[ww]->pc[0]);
      putFitsCard(chan, "PC1_2", wcs[ww]->pc[1]);
      putFitsCard(chan, "PC2_1", wcs[ww]->pc[2]);
      putFitsCard(chan, "PC2_2", wcs[ww]->pc[3]);
    } 
    // fall back
    else {
      putFitsCard(chan, "CD1_1", wcs[ww]->cd[0]);
      putFitsCard(chan, "CD1_2", wcs[ww]->cd[1]);
      putFitsCard(chan, "CD2_1", wcs[ww]->cd[2]);
      putFitsCard(chan, "CD2_2", wcs[ww]->cd[3]);
    }
  }

  // equatorial keywords
  if (wcs[ww]->prjcode>0 && wcs[ww]->prjcode<34) {
    // equiniox
    putFitsCard(chan, "EQUINOX", wcs[ww]->equinox);

    // from wcssub/wcsinit.c line 800
    // wcs[ww]->epoch = 1900.0 + (mjd - 15019.81352) / 365.242198781;
    putFitsCard(chan, "MJD-OBS", 
		(wcs[ww]->epoch-1900)*365.242198781+15019.81352);

    if (!strncmp("RA",wcs[ww]->ctype[0],2) || 
	!strncmp("RA",wcs[ww]->ctype[1],2)) {
      if (!strncmp("FK4",wcs[ww]->radecsys,3) ||
	  !strncmp("FK4-NO-E",wcs[ww]->radecsys,8) ||
	  !strncmp("FK5",wcs[ww]->radecsys,3) ||
	  !strncmp("ICRS",wcs[ww]->radecsys,4))
	putFitsCard(chan, "RADESYS", wcs[ww]->radecsys);
    }
  }

  // ast is picky about latpole/longpole
  if ((wcs[ww]->latpole == 999 && wcs[ww]->longpole == 999) ||
      (wcs[ww]->latpole == 0 && wcs[ww]->longpole == 0))
    ;
  else {
    if (wcs[ww]->latpole != 999)
      putFitsCard(chan, "LATPOLE", wcs[ww]->latpole);
    if (wcs[ww]->longpole != 999)
      putFitsCard(chan, "LONPOLE", wcs[ww]->longpole);
  }

  // Projection parameters- PV, QV, WAT
  // TAN+PV (old SCAMP-backward compatibility)
  // TPV+PV (new SCAMP)
  // xxx+PV (ZPN generic)
  // xxx+QV (TAN AUTOASTROM)
  // TNX/ZPX+WAT (IRAF)
  // TAN/LIN-SIP (SIP)

  // PVx_y (old SCAMP, SCAMP, generic)
  for (int ii=1; ii<=2; ii++) {
    for (int mm=0; mm<=MAXPV; mm++) {
      ostringstream str,str2;
      str << "PV" << ii << '_' << mm << alt << ends;
      str2 << "PV" << ii << '_' << mm << ends;
      if (fits->find(str.str().c_str(), wcsHeader)) {
	double val = fits->getReal(str.str().c_str(),0, wcsHeader);
	putFitsCard(chan, str2.str().c_str(), val);
      }
    }
  }

  // QVx_y (Autoastrom)
  for (int ii=1; ii<=2; ii++) {
    for (int mm=0; mm<=MAXPV; mm++) {
      ostringstream str,str2;
      str << "QV" << ii << '_' << mm << alt << ends;
      str2 << "QV" << ii << '_' << mm << ends;
      if (fits->find(str.str().c_str(), wcsHeader)) {
	double val = fits->getReal(str.str().c_str(),0, wcsHeader);
	putFitsCard(chan, str2.str().c_str(), val);
      }
    }
  }

  // WATx_ (IRAF) (primary only)
  if ((wcs[ww]->prjcode == WCS_TNX || wcs[ww]->prjcode == WCS_ZPX) && !ww) {
    for (int jj=0; jj<=2; jj++) {
      for (int ii=1; ii<=9; ii++) {
	ostringstream str;
	str << "WAT" << jj << "_00" << ii << ends;
	if (fits->find(str.str().c_str(), wcsHeader)) {
	  char* val = fits->getString(str.str().c_str(), wcsHeader);
	  if (val) {
	    putFitsCard(chan, str.str().c_str(), val);
	    delete [] val;
	  }
	}
      }
    }
  }

  // SIP (TAN-SIP/LIN-SIP) (primary only)
  if ((wcs[ww]->prjcode == WCS_TAN || wcs[ww]->prjcode == WCS_LIN) && !ww && wcs[ww]->distcode) {
    if (fits->find("A_ORDER", wcsHeader)) {
      int val = fits->getInteger("A_ORDER",0, wcsHeader);
      putFitsCard(chan, "A_ORDER", val);
    }
    if (fits->find("AP_ORDER", wcsHeader)) {
      int val = fits->getInteger("AP_ORDER",0, wcsHeader);
      putFitsCard(chan, "AP_ORDER", val);
    }
    if (fits->find("A_DMAX", wcsHeader)) {
      double val = fits->getReal("A_DMAX",0, wcsHeader);
      putFitsCard(chan, "A_DMAX", val);
    }

    if (fits->find("B_ORDER", wcsHeader)) {
      int val = fits->getInteger("B_ORDER",0, wcsHeader);
      putFitsCard(chan, "B_ORDER", val);
    }
    if (fits->find("BP_ORDER", wcsHeader)) {
      int val = fits->getInteger("BP_ORDER",0, wcsHeader);
      putFitsCard(chan, "BP_ORDER", val);
    }
    if (fits->find("B_DMAX", wcsHeader)) {
      double val = fits->getReal("B_DMAX",0, wcsHeader);
      putFitsCard(chan, "B_DMAX", val);
    }

    for (int jj=0; jj<=9; jj++) {
      for (int ii=0; ii<=9; ii++) {
	{
	  ostringstream str;
	  str << "A_" << jj << "_" << ii << ends;
	  if (fits->find(str.str().c_str(), wcsHeader)) {
	    double val = fits->getReal(str.str().c_str(),0, wcsHeader);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
	{
	  ostringstream str;
	  str << "AP_" << jj << "_" << ii << ends;
	  if (fits->find(str.str().c_str(), wcsHeader)) {
	    double val = fits->getReal(str.str().c_str(),0, wcsHeader);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
	{
	  ostringstream str;
	  str << "B_" << jj << "_" << ii << ends;
	  if (fits->find(str.str().c_str(), wcsHeader)) {
	    double val = fits->getReal(str.str().c_str(),0, wcsHeader);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
	{
	  ostringstream str;
	  str << "BP_" << jj << "_" << ii << ends;
	  if (fits->find(str.str().c_str(), wcsHeader)) {
	    double val = fits->getReal(str.str().c_str(),0, wcsHeader);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
      }
    }
  }

  // rewind chan
  astClear(chan, "Card");

  // parse header
  AstFrameSet* frameSet = (AstFrameSet*)astRead(chan);

  // do we have anything?
  if (!astOK || frameSet == AST__NULL || 
      strncmp(astGetC(frameSet,"Class"), "FrameSet", 8))
    return NULL;

  if (wcs[ww]->coorflip) {
    int orr[] = {2,1};
    astPermAxes(frameSet,orr);
  }

  // cleanup
  astAnnul(chan);

  return frameSet;
}

void FitsImage::putFitsCard(void* chan, const char* key, const char* value)
{
  char buf[80];
  memset(buf,'\0', 80);

  ostringstream str;
  str.setf(ios::left,ios::adjustfield);
  str.width(8);
  str << key << "= '" << value << "'";
  memcpy(buf,str.str().c_str(),str.str().length());

  astPutFits(chan, buf, 0);
  astClearStatus;

  if (DebugAST)
    cerr << str.str().c_str() << endl;
}

void FitsImage::putFitsCard(void* chan, const char* key, int value)
{
  char buf[80];
  memset(buf,'\0', 80);

  ostringstream str;
  str.setf(ios::left,ios::adjustfield);
  str.width(8);
  str << key << "= " << value;
  memcpy(buf,str.str().c_str(),str.str().length());

  astPutFits(chan, buf, 0);
  astClearStatus;

  if (DebugAST)
    cerr << str.str().c_str() << endl;
}
void FitsImage::putFitsCard(void* chan, const char* key, double value)
{
  char buf[80];
  memset(buf,'\0', 80);

  ostringstream str;
  str.setf(ios::left,ios::adjustfield);
  str.setf(ios::scientific,ios::floatfield);
  str.width(8);
  str.precision(16);
  str << key << "= " << value;
  memcpy(buf,str.str().c_str(),str.str().length());

  astPutFits(chan, buf, 0);
  astClearStatus;

  if (DebugAST)
    cerr << str.str().c_str() << endl;
}

