// Copyright (C) 1999-2015
// 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 "order.h"
#include "iis.h"
#include "hist.h"
#include "compress.h"
#include "analysis.h"
#include "photo.h"
#include "colorscale.h"

// 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);
  }
};

WCSx::WCSx()
{
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    crpix[ii] =0;
    crval[ii] =0;
    cd[ii] =0;
  }
}

FitsImage::FitsImage(Context* cx, Tcl_Interp* pp)
{
  context_ =cx;
  interp_ =pp;

  objectKeyword_ =NULL;
  fileName =NULL;
  rootBaseFileName =NULL;
  fullBaseFileName =NULL;
  iisFileName =NULL;

  fits_ =NULL;
  post_ =NULL;
  hist_ =NULL;
  hpx_ =NULL;
  base_ =NULL;
  basedata_ =NULL;

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

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

  image_ =NULL;
  data_ =NULL;

  nextMosaic_ =NULL;
  nextSlice_ =NULL;

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

  jyBeam_ =1;

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

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

  manageWCS_ =1;
  wcs_ =NULL;
  wcsx_ =NULL;
  ast_ =NULL;
  wcsHeader_ =NULL;
  altHeader_ =NULL;

  iisMode_ =0;
  iiszt_ =0;

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

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

  if (fileName)
    delete [] fileName;

  if (rootBaseFileName)
    delete [] rootBaseFileName;

  if (fullBaseFileName)
    delete [] fullBaseFileName;

  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 (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<MULTWCSA; ii++)
      if (manageWCS_ && wcs_[ii])
	wcsfree(wcs_[ii]);
    delete [] wcs_;
  }  

  if (wcsx_) {
    for (int ii=0; ii<MULTWCS; ii++)
      if (manageWCS_ && wcsx_[ii])
	delete wcsx_[ii];
    delete [] wcsx_;
  }  

  if (ast_) {
    for (int ii=0; ii<MULTWCSA; ii++)
      if (manageWCS_ && ast_[ii])
 	astAnnul(ast_[ii]);
    delete [] ast_;
  }

  if (wcsHeader_)
    delete wcsHeader_;
  if (altHeader_)
    delete altHeader_;
}

// Fits

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

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

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

FitsImageFitsMMap::FitsImageFitsMMap(Context* cx, Tcl_Interp* pp, 
				     const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsMMap(fn, FitsFile::RELAX);
  process(fn,id);
}

FitsImageFitsSMMap::FitsImageFitsSMMap(Context* cx, Tcl_Interp* pp, 
				       const char* hdr, 
				       const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsSMMap(hdr, fn);
  process(fn,id);
}

FitsImageFitsMMapIncr::FitsImageFitsMMapIncr(Context* cx, Tcl_Interp* pp, 
					     const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsMMapIncr(fn, FitsFile::RELAX);
  process(fn,id);
}

FitsImageFitsShare::FitsImageFitsShare(Context* cx, Tcl_Interp* pp, 
				       Base::ShmType type, 
				       int sid, const char* fn, int id)
  : FitsImage(cx, pp)
{
  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(Context* cx, Tcl_Interp* pp, 
					 Base::ShmType type,
					 int hdr, int sid, 
					 const char* fn, int id)
  : FitsImage(cx, pp)
{
  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(Context* cx, Tcl_Interp* pp, 
					 int s, const char* fn, 
					 FitsFile::FlushMode flush, int id) 
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsSocket(s, fn, FitsFile::RELAX, flush);
  process(fn,id);
}

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

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

FitsImageFitsOrder::FitsImageFitsOrder(Context* cx, Tcl_Interp* pp, 
				       FitsImage* fi,
				       FitsHead* hdr, char* data, size_t sz,
				       int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsOrder(fi->fitsFile(), hdr, data, sz);
  process(NULL,id);

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

// Fits Next

FitsImageFitsNextAlloc::FitsImageFitsNextAlloc(Context* cx, Tcl_Interp* pp, 
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextAlloc(prev);
  process(fn,id);
}

FitsImageFitsNextAllocGZ::FitsImageFitsNextAllocGZ(Context* cx, Tcl_Interp* pp, 
						   const char* fn,
						   FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextAllocGZ(prev);
  process(fn,id);
}

FitsImageFitsNextChannel::FitsImageFitsNextChannel(Context* cx, Tcl_Interp* pp, 
						   const char* fn,
						   FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextChannel(prev);
  process(fn,id);
}

FitsImageFitsNextMMap::FitsImageFitsNextMMap(Context* cx, Tcl_Interp* pp, 
					     const char* fn,
					     FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextMMap(prev);
  process(fn,id);
}

FitsImageFitsNextSMMap::FitsImageFitsNextSMMap(Context* cx, Tcl_Interp* pp, 
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextSMMap(prev);
  process(fn,id);
}

FitsImageFitsNextMMapIncr::FitsImageFitsNextMMapIncr(Context* cx, Tcl_Interp* pp, 
						     const char* fn,
						     FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextMMapIncr(prev);
  process(fn,id);
}

FitsImageFitsNextShare::FitsImageFitsNextShare(Context* cx, Tcl_Interp* pp, 
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextShare(prev);
  process(fn,id);
}

FitsImageFitsNextSShare::FitsImageFitsNextSShare(Context* cx, Tcl_Interp* pp, 
						 const char* fn,
						 FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextSShare(prev);
  process(fn,id);
}

FitsImageFitsNextSocket::FitsImageFitsNextSocket(Context* cx, Tcl_Interp* pp, 
						 const char* fn,
						 FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextSocket(prev);
  process(fn,id);
}

FitsImageFitsNextSocketGZ::FitsImageFitsNextSocketGZ(Context* cx, Tcl_Interp* pp, 
						     const char* fn, 
						     FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextSocketGZ(prev);
  process(fn,id);
}

FitsImageFitsNextVar::FitsImageFitsNextVar(Context* cx, Tcl_Interp* pp, 
					   const char* fn,
					   FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsFitsNextVar(prev);
  process(fn,id);
}

FitsImageFitsNextHist::FitsImageFitsNextHist(Context* cx, Tcl_Interp* pp, 
					     FitsImage* fi,
					     FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsHistNext(prev);
  process(NULL,id);

  fits_->setpFilter(fi->getHistFilter());
  fits_->setpBinX(fi->getHistX());
  fits_->setpBinY(fi->getHistY());
  fits_->setpBinZ(fi->getHistZ());

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

FitsImageFitsNextPost::FitsImageFitsNextPost(Context* cx, Tcl_Interp* pp, 
					     FitsImage* fi,
					     FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsPostNext(prev);
  process(NULL,id);

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

FitsImageFitsNextOrder::FitsImageFitsNextOrder(Context* cx, Tcl_Interp* pp, 
					       FitsImage* fi,
					       FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsOrderNext(prev);
  process(NULL,id);

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

// Array

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

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

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

FitsImageArrMMap::FitsImageArrMMap(Context* cx, Tcl_Interp* pp, 
				   const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsArrMMap(fn);
  process(fn,id);
}

FitsImageArrMMapIncr::FitsImageArrMMapIncr(Context* cx, Tcl_Interp* pp, 
					   const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsArrMMapIncr(fn);
  process(fn,id);
}

FitsImageArrShare::FitsImageArrShare(Context* cx, Tcl_Interp* pp, 
				     Base::ShmType type, 
				     int sid, const char* fn, int id) 
  : FitsImage(cx, pp)
{
  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(Context* cx, Tcl_Interp* pp, 
				       int s, const char* fn,
				       FitsFile::FlushMode flush, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsArrSocket(s, fn, flush);
  process(fn,id);
}

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

FitsImageArrVar::FitsImageArrVar(Context* cx, Tcl_Interp* pp, 
				 const char* var, const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsArrVar(pp, var, fn);
  process(fn,id);
}

// ENVI

FitsImageENVISMMap::FitsImageENVISMMap(Context* cx, Tcl_Interp* pp,
				       const char* hdr,
				       const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsENVISMMap(hdr,fn);
  process(fn,id);
}

// NRRD

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

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

FitsImageNRRDMMap::FitsImageNRRDMMap(Context* cx, Tcl_Interp* pp,
				     const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsNRRDMMap(fn);
  process(fn,id);
}

FitsImageNRRDShare::FitsImageNRRDShare(Context* cx, Tcl_Interp* pp,
				       Base::ShmType type, 
				       int sid, const char* fn, int id) 
  : FitsImage(cx, pp)
{
  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(Context* cx, Tcl_Interp* pp,
					 int s, const char* fn,
					 FitsFile::FlushMode flush, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsNRRDSocket(s, fn, flush);
  process(fn,id);
}

FitsImageNRRDVar::FitsImageNRRDVar(Context* cx, Tcl_Interp* pp,
				   const char* var, const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsNRRDVar(pp, var, fn);
  process(fn,id);
}

// Photo

FitsImagePhoto::FitsImagePhoto(Context* cx, Tcl_Interp* pp,
			       const char* ph, const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsPhoto(pp, ph);
  process(fn,id);
}

FitsImagePhotoCube::FitsImagePhotoCube(Context* cx, Tcl_Interp* pp,
				       const char* ph, const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsPhotoCube(pp, ph);
  process(fn,id);
}

FitsImagePhotoCubeNext::FitsImagePhotoCubeNext(Context* cx, Tcl_Interp* pp,
					       const char* fn,
					       FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsPhotoCubeNext(prev);
  process(fn,id);
}

// Mosaic

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

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

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

FitsImageMosaicMMap::FitsImageMosaicMMap(Context* cx, Tcl_Interp* pp,
					 const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicMMap(fn);
  process(fn,id);
}

FitsImageMosaicMMapIncr::FitsImageMosaicMMapIncr(Context* cx, Tcl_Interp* pp,
						 const char* fn, 
						 int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicMMapIncr(fn);
  process(fn,id);
}

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

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

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

FitsImageMosaicVar::FitsImageMosaicVar(Context* cx, Tcl_Interp* pp,
				       const char* var, const char* fn, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicVar(pp, var, fn);
  process(fn,id);
}

// Mosaic Next

FitsImageMosaicNextAlloc::FitsImageMosaicNextAlloc(Context* cx, Tcl_Interp* pp,
						   const char* fn,
						   FitsFile* prev,
						   FitsFile::FlushMode flush,
						   int id)

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

FitsImageMosaicNextAllocGZ::FitsImageMosaicNextAllocGZ(Context* cx, Tcl_Interp* pp,
						       const char* fn,
						       FitsFile* prev,
						      FitsFile::FlushMode flush,
						       int id)

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

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

FitsImageMosaicNextMMap::FitsImageMosaicNextMMap(Context* cx, Tcl_Interp* pp,
						 const char* fn, 
						 FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicNextMMap(prev);
  process(fn,id);
}

FitsImageMosaicNextMMapIncr::FitsImageMosaicNextMMapIncr(Context* cx, Tcl_Interp* pp,
							 const char* fn, 
							 FitsFile* prev,
							 int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicNextMMapIncr(prev);
  process(fn,id);
}

FitsImageMosaicNextShare::FitsImageMosaicNextShare(Context* cx, Tcl_Interp* pp, 
						   const char* fn,
						   FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicNextShare(prev);
  process(fn,id);
}

FitsImageMosaicNextSocket::FitsImageMosaicNextSocket(Context* cx, 
						     Tcl_Interp* pp,
						     const char* fn, 
						     FitsFile* prev,
						     FitsFile::FlushMode flush,
						     int id)

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

FitsImageMosaicNextSocketGZ::FitsImageMosaicNextSocketGZ(Context* cx, 
							 Tcl_Interp* pp,
							 const char* fn, 
							 FitsFile* prev,
						      FitsFile::FlushMode flush,
							 int id)

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

FitsImageMosaicNextVar::FitsImageMosaicNextVar(Context* cx, Tcl_Interp* pp,
					       const char* fn, 
					       FitsFile* prev, int id)
  : FitsImage(cx, pp)
{
  fits_ = new FitsMosaicNextVar(prev);
  process(fn,id);
}

// IIS

FitsImageIIS::FitsImageIIS(Context* cx, Tcl_Interp* pp, int w, int h)
  : FitsImage(cx, pp)
{
  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::altWCS(istream& str)
{
  FitsHead* hh = parseWCS(str);

  // Process OBJECT keyword
  if (objectKeyword_)
    delete [] objectKeyword_;
  objectKeyword_ = hh->getString("OBJECT");

  // Process WCS keywords
  if (altHeader_)
    delete altHeader_;

  altHeader_ = hh;
  initWCS();
}

void FitsImage::appendWCS(istream& str)
{
  FitsHead* hh = parseWCS(str);

  // process OBJECT keyword
  char* obj = hh->getString("OBJECT");
  if (obj) {
    if (objectKeyword_)
      delete [] objectKeyword_;
    objectKeyword_ = 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());

  delete hh;
  if (wcsHeader_)
    delete wcsHeader_;

  wcsHeader_ = new FitsHead(cards,ll,FitsHead::ALLOC);
  initWCS();
}

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()
{
  Vector blockFactor = context_->blockFactor();
  if (blockFactor[0] != 1 && blockFactor[1] != 1)
    return display(image_->head());

  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 if (altHeader_)
    return display(altHeader_);
  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()
{
  // free up wcs and ast arrays
  if (wcs_) {
    for (int ii=0; ii<MULTWCSA; ii++)
      if (manageWCS_ && wcs_[ii])
	wcsfree(wcs_[ii]);
    delete [] wcs_;
  }
  wcs_ = new WorldCoor*[MULTWCSA];
  for (int ii=0; ii<MULTWCSA; ii++)
    wcs_[ii] = NULL;

  if (wcsx_) {
    for (int ii=0; ii<MULTWCS; ii++)
      if (manageWCS_ && wcsx_[ii])
	delete wcsx_[ii];
    delete [] wcsx_;
  }  
  wcsx_ = new WCSx*[MULTWCS];
  for (int ii=0; ii<MULTWCS; ii++)
    wcsx_[ii] = NULL;

  if (ast_) {
    for (int ii=0; ii<MULTWCSA; ii++)
      if (manageWCS_ && ast_[ii])
	astAnnul(ast_[ii]);
    delete [] ast_;
  }
  ast_ = new AstFrameSet*[MULTWCSA];
  for (int ii=0; ii<MULTWCSA; ii++)
    ast_[ii] = NULL;

  // shareWCS?
  manageWCS_ =1;
  if (context_->shareWCS()) {
    FitsImage* ptr = context_->fits;
    while (ptr) {
      if (ptr == this)
	break;

      FitsImage* sptr = ptr->nextSlice();
      while (sptr) {
	if (sptr == this) {
	  for (int ii=0; ii<MULTWCSA; ii++)
	    wcs_[ii] = ptr->wcs_[ii];
	  for (int ii=0; ii<MULTWCS; ii++)
	    wcsx_[ii] = ptr->wcsx_[ii];
	  for (int ii=0; ii<MULTWCSA; ii++)
	    ast_[ii] = ptr->ast_[ii];

	  initWCSPhysical();
	  manageWCS_ =0;
	  return;
	}
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }

  // WCSx
  FitsHead* head =NULL;
  FitsHead* prim =NULL;
  if (wcsHeader_)
    head = wcsHeader_;
  else if (altHeader_)
    head = altHeader_;
  else {
    head = image_->head();
    prim = image_->primary() && image_->inherit() ? image_->primary() : NULL;
  }

  // wcsinit is sloooowwww! so try to figure it out first
  // look first for default WCS. Let wcsinit figure it out since there can
  // be many different non-standard wcs's present
  wcshead = head;
  wcsprim = prim;
  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 = prim;
      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, (char*)"AST--WCS", (char*)"AST--WCS", 
			 0, 0, 0, 0, NULL, 1, 1, 0, 2000, 0);
      wcs_[0]->longpole = 999;
      wcs_[0]->latpole = 999;
    }
  }

  // AST
  for (int ii=0; ii<MULTWCSA; ii++) {
    if (wcs_[ii])
      astinit(ii, head, prim);
  }

  // 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;
    }
  }

  // WCSx
  char scrpix[] = "CRPIX  ";
  char scrval[] = "CRVAL  ";
  char scd[] = "CD _  ";
  char scdelt[] = "CDELT  ";
  for (int ii=0; ii<MULTWCS; ii++) {
    for (int jj=2; jj<FTY_MAXAXES; jj++) {
    
      scrpix[5] = '1'+jj;
      scrpix[6] = !ii ? ' ' : '@'+jj;
      scrval[5] = '1'+jj;
      scrval[6] = !ii ? ' ' : '@'+jj;
      scd[2] = '1'+jj;
      scd[4] = '1'+jj;
      scd[5] = !ii ? ' ' : '@'+jj;
      scdelt[5] = '1'+jj;
      scdelt[6] = !ii ? ' ' : '@'+jj;

      if (head->find(scrpix) && head->find(scrval)) {
	if (!wcsx_[ii])
	  wcsx_[ii] = new WCSx();
	wcsx_[ii]->crpix[jj] = head->getReal(scrpix,0);
	wcsx_[ii]->crval[jj] = head->getReal(scrval,0);
	wcsx_[ii]->cd[jj] = head->getReal(scd,0);
	if (!wcsx_[ii]->cd[jj])
	  wcsx_[ii]->cd[jj] = head->getReal(scdelt,0);
      }
    }
  }

  initWCSPhysical();

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

      if (wcsx_[ii]) {
	for (int jj=0; jj<FTY_MAXAXES; jj++) {
	  if (wcsx_[ii]->cd[jj]) {
	    cerr << "wcsx" << (char)(!ii ? ' ' : '@'+ii) 
		 << "[" << ii << "]->crpix[" << jj << "]=" 
		 << wcsx_[ii]->crpix[jj] << endl;
	    cerr << "wcsx" << (char)(!ii ? ' ' : '@'+ii) 
		 << "[" << ii << "]->crval[" << jj << "]=" 
		 << wcsx_[ii]->crval[jj] << endl;
	    cerr << "wcsx" << (char)(!ii ? ' ' : '@'+ii) 
		 << "[" << ii << "]->cd[" << jj << "]=" 
		 << wcsx_[ii]->cd[jj] << endl;
	  }
	}
      }
    }
  }
}

void FitsImage::initWCSPhysical()
{
  // now see if we have a 'physical' wcs, if so, set LTMV keywords
  keyLTMV =0;
  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();
    }
  }
}

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, 
		       (char*)"RA---TAN", (char*)"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;
    astinit0(ii);

    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_, interp_);
    break;
  case 16:
    basedata_ = new FitsDatam<short>(base_, interp_);
    break;
  case -16:
    basedata_ = new FitsDatam<unsigned short>(base_, interp_);
    break;
  case 32:
    basedata_ = new FitsDatam<int>(base_, interp_);
    break;
  case 64:
    basedata_ = new FitsDatam<long long>(base_, interp_);
    break;
  case -32:
    basedata_ = new FitsDatam<float>(base_, interp_);
    break;
  case -64:
    basedata_ = new FitsDatam<double>(base_, interp_);
    break;
  }

  // block
  block_ = base_;
  blockdata_ = basedata_;

  // analysis
  analysis_ = block_;
  analysisdata_ = blockdata_;

  // image
  image_ = analysis_;
  data_ = analysisdata_;
}

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(interp_, xxname1, NULL, TCL_LEAVE_ERR_MSG);
  Tcl_Obj* listyy1 = 
    Tcl_GetVar2Ex(interp_, yyname1, NULL, TCL_LEAVE_ERR_MSG);
  Tcl_Obj* listxx2 = 
    Tcl_GetVar2Ex(interp_, xxname2, NULL, TCL_LEAVE_ERR_MSG);
  Tcl_Obj* listyy2 = 
    Tcl_GetVar2Ex(interp_, yyname2, NULL, TCL_LEAVE_ERR_MSG);

  // get objects
  int nxx1;
  Tcl_Obj **objxx1;
  Tcl_ListObjGetElements(interp_, listxx1, &nxx1, &objxx1);
  int nyy1;
  Tcl_Obj **objyy1;
  Tcl_ListObjGetElements(interp_, listyy1, &nyy1, &objyy1);
  int nxx2;
  Tcl_Obj **objxx2;
  Tcl_ListObjGetElements(interp_, listxx2, &nxx2, &objxx2);
  int nyy2;
  Tcl_Obj **objyy2;
  Tcl_ListObjGetElements(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(interp_, objxx1[ii], ixx1+ii);
  double* iyy1 = new double[nyy1];
  for (int ii=0 ; ii<nyy1 ; ii++)
    Tcl_GetDoubleFromObj(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(interp_, objxx2[ii], ixx2+ii);
  double* iyy2 = new double[nyy2];
  for (int ii=0 ; ii<nyy2 ; ii++)
    Tcl_GetDoubleFromObj(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(interp_, objrr, list);
	cnt++;
      }
    }
  }

  Tcl_SetVar2Ex(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::matrixToData(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WINDOW:
    return windowToData;
  case Coord::CANVAS:
    return canvasToData;
  case Coord::WIDGET:
    return widgetToData;
  case Coord::PANNER:
    return pannerToData;
  case Coord::MAGNIFIER:
    return magnifierToData;
  case Coord::PS:
    return psToData;
  case Coord::USER:
    return userToData;
  case Coord::REF:
    return refToData;
  }
}

Matrix& FitsImage::matrixFromData(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WINDOW:
    return dataToWindow;
  case Coord::CANVAS:
    return dataToCanvas;
  case Coord::WIDGET:
    return dataToWidget;
  case Coord::PANNER:
    return dataToPanner;
  case Coord::MAGNIFIER:
    return dataToMagnifier;
  case Coord::PS:
    return dataToPS;
  case Coord::USER:
    return dataToUser;
  case Coord::REF:
    return dataToRef;
  }
}

Matrix3d& FitsImage::matrixFromData3d(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WINDOW:
    return dataToWindow3d;
  case Coord::CANVAS:
    return dataToCanvas3d;
  case Coord::WIDGET:
    return dataToWidget3d;
  case Coord::PANNER:
    return dataToPanner3d;
  case Coord::MAGNIFIER:
    return dataToMagnifier3d;
  case Coord::PS:
    return dataToPS3d;
  case Coord::USER:
    return dataToUser3d;
  case Coord::REF:
    return dataToRef3d;
  }
}

Matrix3d& FitsImage::matrixToData3d(Coord::InternalSystem sys)
{
  switch (sys) {
  case Coord::WINDOW:
    return windowToData3d;
  case Coord::CANVAS:
    return canvasToData3d;
  case Coord::WIDGET:
    return widgetToData3d;
  case Coord::PANNER:
    return pannerToData3d;
  case Coord::MAGNIFIER:
    return magnifierToData3d;
  case Coord::PS:
    return psToData3d;
  case Coord::USER:
    return userToData3d;
  case Coord::REF:
    return refToData3d;
  }
}

FitsHead* FitsImage::parseWCS(istream& str)
{
  FitsHead* head = image_->head();
  FitsHead* rr = new FitsHead(head->naxis(0), head->naxis(1),
			     head->naxis(2), head->bitpix());

  // this works for both terminated (\n) and non-terminated (fits) headers
  while (!str.eof()) {
    char buf[256];
    str.get(buf,81,'\n');
    if (strlen(buf) > 0) {
      string x(buf);
      istringstream sstr(x);

      char keyword[32];
      sstr >> keyword;

      if (strchr(buf,'=')) {
	char dummy;
	sstr >> dummy;
      }

      if (strchr(buf,'\'')) {
	char val[64];
	char* ss = strchr(buf,'\'')+1;
	char* ee = strrchr(buf,'\'');
	int ll = ee-ss;
	if (ll<0 || ll>63)
	  ll =0;
	strncpy(val,ss,ll);
	val[ll] = '\0';

	rr->appendString(keyword, val, "");
      }
      else {
	double val;
	sstr >> val;
	rr->appendReal(keyword, val, 15, "");
      }

      if (strlen(buf) <= 80) {
	// eat the \n
	char b;
	str.get(b);
      }
    }
    else
      break;
  }

  return rr;
}

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
      initBin();
      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::processKeywordsPhysical()
{
  // 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)

  keyDTMV =0;
  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)

  keyATMV =0;
  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);

  // ok we have a radio map
#define GFACTOR (2.0*sqrt(2.0*log(2.0)))
  if (!strncmp(bunit,"JY/BEAM",7) && cdelt1 && cdelt2 && bmaj && bmin) {
    // convert from deg to arcsec?
    cdelt1 *= 3600;
    cdelt2 *= 3600;
    bmaj *= 3600;
    bmin *= 3600;
    jyBeam_ = (2*M_PI*bmaj*bmin)/(GFACTOR*GFACTOR*cdelt1*cdelt2);
  }

  if (bunit)
    delete [] bunit;
  if (cunit1)
    delete [] cunit1;
  if (cunit2)
    delete [] cunit2;
  */
}

void FitsImage::processKeywordsParams()
{
  // iparams is a BBOX in DATA coords 0-n
  iparams.set(0, 0, width(), height());

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

    keyDATASEC =0;
    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(size());
	keyDATASEC = 0;
      } else
	keyDATASEC = 1;
    }

    // dparams is a BBOX in DATA coords 0-n
    datasec = BBox(v1,v2);
    v1 -= Vector(1,1);
    dparams.set(v1[0],v1[1],v2[0],v2[1]);

    if (datstr)
      delete [] datstr;
  }

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

void FitsImage::processKeywordsFitsSection()
{
  Vector ll(dparams.xmin,dparams.ymin);
  Vector ur(dparams.xmax,dparams.ymax);

  if (fits_->pcoord() && fits_->pxvalid() && fits_->pyvalid()) {
    ll[0] = fits_->pxmin();
    ur[0] = fits_->pxmax();
    ll[1] = fits_->pymin();
    ur[1] = fits_->pymax();

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

    context_->frScale.setScanMode(FrScale::CROPSEC);
  }
  if (!fits_->pcoord() && fits_->pxvalid()) {
    ll[0] = fits_->pxmin()-1;
    ur[0] = fits_->pxmax();
    context_->frScale.setScanMode(FrScale::CROPSEC);
  }
  if (!fits_->pcoord() && fits_->pyvalid()) {
    ll[1] = fits_->pymin()-1;
    ur[1] = fits_->pymax();
    context_->frScale.setScanMode(FrScale::CROPSEC);
  }

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

  // DEBUG
  if (DebugCrop)
    cerr << "cparams " << cparams << endl;

  if (fits_->pzvalid()) {
    int zmin = fits_->pzmin()-1;
    int zmax = fits_->pzmax();
    context_->frScale.setScanMode(FrScale::CROPSEC);
    context_->setCrop3dParams(zmin,zmax);
  }
}

int FitsImage::processKeywordsIRAF(FitsImage* fits)
{
  // DETSEC
  Coord::Orientation orientation = 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 (context_->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;
}

void FitsImage::replaceWCS(istream& str)
{
  FitsHead* hh = parseWCS(str);

  // Process OBJECT keyword
  if (objectKeyword_)
    delete [] objectKeyword_;
  objectKeyword_ = hh->getString("OBJECT");

  // Process WCS keywords
  if (wcsHeader_)
    delete wcsHeader_;

  wcsHeader_ = hh;
  initWCS();
}

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 (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 (objectKeyword_)
    delete [] objectKeyword_;
  objectKeyword_ = image_->getString("OBJECT");

  // Process WCS keywords
  if (wcsHeader_)
    delete wcsHeader_;

  wcsHeader_ = NULL;
  initWCS();
}

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

  if (ast_[ii])
    astAnnul(ast_[ii]);
  ast_[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::setCropParams(int datasec)
{
  if (!datasec)
    cparams = iparams;
  else
    cparams = dparams;
}

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.set(x0,y0,x1,y1);
}

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

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

  if (fullBaseFileName)
    delete [] fullBaseFileName;
  fullBaseFileName = 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;
}

void FitsImage::setObjectKeyword(const char* obj)
{
  if (objectKeyword_)
    delete [] objectKeyword_;
  objectKeyword_ = 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_)
    data_->updateClip(fr,getDataParams(fr->scanMode()));
}

const char* FitsImage::getFileName(Base::FileNameType type)
{
  switch (type) {
  case Base::ROOTBASE:
    return rootBaseFileName;
  case Base::FULLBASE:
    return fullBaseFileName;
  case Base::ROOT:
  case Base::FULL:
    // clear the buffer
    if (fileName)
      delete [] fileName;
    fileName =NULL;

    // generate filename
    // if FITS bin table cube, be sure to check the first slice
    if (context_->fits->isHist())
      return updateFileNameBin(type);
    else
      return updateFileNameImage(type);
  }
}

const char* FitsImage::updateFileNameImage(Base::FileNameType type)
{
  // 2d/3d section
  char* sec =NULL;
  switch (context_->frScale.scanMode()) {
  case FrScale::IMGSEC:
  case FrScale::DATASEC:
    {
      Vector blockFactor = context_->blockFactor();

      if (blockFactor[0] != 1) {
	ostringstream str;
	str << "*," << blockFactor[0] << ends;
	sec = dupstr(str.str().c_str());
      }
    }    
    break;
  case FrScale::CROPSEC:
    {
      FitsBound* params =getDataParams(FrScale::CROPSEC);

      // params is a BBOX in DATA coords 0-n
      //   xlate to 1-n
      Vector ll= Vector(params->xmin,params->ymin) * Translate(1,1);
      Vector ur(params->xmax,params->ymax);
      Vector blockFactor = context_->blockFactor();

      if (blockFactor[0] != 1) {
	ostringstream str;
	str << ll[0] << ':' << ur[0] << ',' 
	    << ll[1] << ':' << ur[1] << ','
	    << blockFactor[0] << ends;
	sec = dupstr(str.str().c_str());
      }
      else {
	ostringstream str;
	str << ll[0] << ':' << ur[0] << ',' 
	    << ll[1] << ':' << ur[1] << ends;
	sec = dupstr(str.str().c_str());
      }
    }    
    break;
  }

  // address
  char* add =NULL;
  {
    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());
    }
  }

  switch (type) {
  case Base::ROOTBASE:
  case Base::FULLBASE:
    // better not get here
    return NULL;

  case Base::ROOT:
    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;
      fileName = dupstr(str.str().c_str());
    }
    break;

  case Base::FULL:
    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;
      fileName = dupstr(str.str().c_str());
    }
    break;
  }

  if (sec)
    delete [] sec;
  if (add)
    delete [] add;

  return fileName;
}

const char* FitsImage::updateFileNameBin(Base::FileNameType type)
{
  char* filter = (char*)fits_->pFilter();
  int doFilter = (filter && *filter);
    
  // x,y filter
  char* sec = NULL;
  {
    switch (context_->frScale.scanMode()) {
    case FrScale::IMGSEC:
    case FrScale::DATASEC:
      break;
    case FrScale::CROPSEC:
      {
	ostringstream str;
	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;

	str << fits_->pBinX() << ">=" << ll[0] << ',' 
	    << fits_->pBinX() << "<=" << ur[0] << ','
	    << fits_->pBinY() << ">=" << ll[1] << ',' 
	    << fits_->pBinY() << "<=" << ur[1] << ends;
	sec = dupstr(str.str().c_str());
      }    
      break;
    }
  }

  // z filter
  char* slice =NULL;
  {
    char* zcol = (char*)fits_->pBinZ();
    int bd = context_->binDepth();
    if (bd>1 && zcol) {
      // only the first slice will have this
      FitsImage* first = context_->fits;
      if (first) {
	Vector zlim = first->fits_->getColMinMax(zcol);
	double zlen = zlim[1]-zlim[0];
	double zdelta = zlen/bd;

	double zptr = zlim[0] + (address[2]-1)*zdelta;
	ostringstream str;
	str << zcol << ">=" << zptr << '&' << zcol << '<' << zptr+zdelta <<ends;
	slice = dupstr(str.str().c_str());
      }
    }
  }

  switch (type) {
  case Base::ROOTBASE:
  case Base::FULLBASE:
    // better not get here
    return NULL;

  case Base::ROOT:
    if (rootBaseFileName) {
      ostringstream str;
      str << rootBaseFileName;
      if (doFilter && sec && slice)
	str << '[' << filter << ',' << sec << ',' << slice << ']';
      else if (doFilter && sec && !slice)
	str << '[' << filter << ',' << sec << ']';
      else if (doFilter && !sec && slice)
	str << '[' << filter << ',' << slice << ']';
      else if (doFilter && !sec && !slice)
	str << '[' << filter << ']';
      else if (!doFilter && sec && slice)
	str << '[' << sec << ',' << slice << ']';
      else if (!doFilter && sec && !slice)
	str << '[' << sec << ']';
      else if (!doFilter && !sec && slice)
	str << '[' << slice << ']';
      str << ends;
      fileName = dupstr(str.str().c_str());
    }
  case Base::FULL:
    if (fullBaseFileName) {
      ostringstream str;
      str << fullBaseFileName;
      if (doFilter && sec && slice)
	str << '[' << filter << ',' << sec << ',' << slice << ']';
      else if (doFilter && sec && !slice)
	str << '[' << filter << ',' << sec << ']';
      else if (doFilter && !sec && slice)
	str << '[' << filter << ',' << slice << ']';
      else if (doFilter && !sec && !slice)
	str << '[' << filter << ']';
      else if (!doFilter && sec && slice)
	str << '[' << sec << ',' << slice << ']';
      else if (!doFilter && sec && !slice)
	str << '[' << sec << ']';
      else if (!doFilter && !sec && slice)
	str << '[' << slice << ']';
      str << ends;
      fileName = dupstr(str.str().c_str());
    }
    break;
  }

  return fileName;
}

void FitsImage::updateMatrices(Matrix& rgbToRef,
			       Matrix& refToUser,
			       Matrix& userToWidget,
			       Matrix& widgetToCanvas,
			       Matrix& canvasToWindow)
{
  dataToRef = wcsToRef_ * rgbToRef;
  refToData = dataToRef.invert();

  dataToUser = dataToRef * refToUser;
  userToData = dataToUser.invert();

  dataToWidget = dataToUser * userToWidget;
  widgetToData = dataToWidget.invert();

  dataToCanvas = dataToWidget * widgetToCanvas;
  canvasToData = dataToCanvas.invert();

  dataToWindow = dataToCanvas * canvasToWindow;
  windowToData = dataToWindow.invert();

  refToCanvas = refToUser * userToWidget * widgetToCanvas;
  canvasToRef = refToCanvas.invert();

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

  imageToWidget = imageToRef * refToUser * userToWidget;
  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& refToUser3d, 
			       Matrix3d& userToWidget3d,
			       Matrix3d& widgetToCanvas3d, 
			       Matrix3d& canvasToWindow3d)
{
  dataToUser3d = dataToRef3d * refToUser3d;
  userToData3d = dataToUser3d.invert();

  dataToWidget3d = dataToUser3d * userToWidget3d;
  widgetToData3d = dataToWidget3d.invert();

  dataToCanvas3d = dataToWidget3d * widgetToCanvas3d;
  canvasToData3d = dataToCanvas3d.invert();

  dataToWindow3d = dataToCanvas3d * canvasToWindow3d;
  windowToData3d = dataToWindow3d.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]->pc[0] == 1) {
      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 {
      // great, we got PC terms. The scaling factor could be encoded in
      // pc or cdelt or both
      if (!wcs_[ii]->coorflip)
	return Vector(wcs_[ii]->cdelt[0]*wcs_[ii]->pc[0], 
		      wcs_[ii]->cdelt[1]*wcs_[ii]->pc[3]);
      else
	return Vector(wcs_[ii]->cdelt[1]*wcs_[ii]->pc[3],
		      wcs_[ii]->cdelt[0]*wcs_[ii]->pc[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;
  }

  return Coord::NORMAL;
}

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]) {
    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]) {
    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",2))
	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 ii = sys-Coord::WCS;
  return (ss>=2&&ss<FTY_MAXAXES && sys>=Coord::WCS && wcsx_[ii]) ? 1 : 0;
}

double FitsImage::pix2wcsx(double in, Coord::CoordSystem sys, int ss)
{
  if (hasWCSx(sys,ss)) {
    int ii = sys-Coord::WCS;
    return (in-wcsx_[ii]->crpix[ss])*wcsx_[ii]->cd[ss] + wcsx_[ii]->crval[ss];
  }
  else
    return in;
}

double FitsImage::wcs2pixx(double in, Coord::CoordSystem sys, int ss)
{
  if (hasWCSx(sys,ss)) {
    int ii = sys-Coord::WCS;
    return (in-wcsx_[ii]->crval[ss])/wcsx_[ii]->cd[ss] + wcsx_[ii]->crpix[ss];
  }
  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* head, FitsHead* prim)
{
  if (!wcs_[ii]) {
    ast_[ii] = NULL;
    return;
  }

  // just in case
  if (!head)
    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,"XPH",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(head);
  }
  else
    ast_[ii] = buildast(ii, head, prim);

  if (!ast_[ii])
    return;

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

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

void FitsImage::astinit0(int ii)
{
  if (!wcs_[ii]) {
    ast_[ii] = NULL;
    return;
  }

  ast_[ii] = buildast0(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* head) 
{
  // 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) {
    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::buildast(int ii, FitsHead* head, FitsHead* prim) 
{
  if (DebugAST)
    cerr << endl << "buildast()" << endl;

  // read wcs struct into astChannel
  // 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");

  // basics (needed by fitschan.c)
  putFitsCard(chan, "NAXIS1", (int)naxis(0));
  putFitsCard(chan, "NAXIS2", (int)naxis(1));

  // simple test to see if we have complete WCS
  // if not (as in 3d cube reorder), wcs[] can be very unreliable
  int fromwcs =0;
  if (head->find("CTYPE1") &&
      head->find("CTYPE2") &&
      head->find("CRVAL1") &&
      head->find("CRVAL2") &&
      head->find("CRPIX1") &&
      head->find("CRPIX2")) {
    wcs2ast(ii,head,prim,chan);
    fromwcs =1;
  }
  else
    header2ast(head,chan);

  // 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 (fromwcs && wcs_[ii]->coorflip) {
    int orr[] = {2,1};
    astPermAxes(frameSet,orr);
  }

  // cleanup
  astAnnul(chan);

  return frameSet;
}

AstFrameSet* FitsImage::buildast0(int ii) 
{
  if (DebugAST)
    cerr << endl << "buildast0()" << endl;

  // read wcs struct into astChannel
  // 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");

  // basics (needed by fitschan.c)
  putFitsCard(chan, "NAXIS1", (int)naxis(0));
  putFitsCard(chan, "NAXIS2", (int)naxis(1));

  wcs2ast0(ii,chan);

  // 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_[ii]->coorflip) {
    int orr[] = {2,1};
    astPermAxes(frameSet,orr);
  }

  // cleanup
  astAnnul(chan);

  return frameSet;
}

void FitsImage::header2ast(FitsHead* head, void* chan) 
{
  if (DebugAST)
    cerr << endl << "header2ast()" << endl;

  for (int ii=0; ii<MULTWCS; ii++) {
    char alt = (ii==0) ? ' ' : (char)('@'+ii);

    char key1[8];
    char key2[8];

    // CTYPE
    // We can't have RA/DEC without DEC/RA or GLON/GLAT without GLAT/GLON
    const char* linear = "LINEAR";
    strcpy(key1, "CTYPE1 ");
    strcpy(key2, "CTYPE2 ");
    key1[6] = key2[6] = alt;

    // do we have WCSa?
    if (!head->find(key1) && !head->find(key2))
      continue;

    char* ctype1 = head->getString(key1);
    char* ctype2 = head->getString(key2);

    if (ctype1 && !strncmp(ctype1,"GLON",4)) {
      if (!ctype2 || strncmp(ctype2,"GLAT",4)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype2 && !strncmp(ctype2,"GLON",4)) {
      if (!ctype1 || strncmp(ctype1,"GLAT",4)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype1 && !strncmp(ctype1,"GLAT",4)) {
      if (!ctype2 || strncmp(ctype2,"GLON",4)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype2 && !strncmp(ctype2,"GLAT",4)) {
      if (!ctype1 || strncmp(ctype1,"GLON",4)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype1 && !strncmp(ctype1,"RA",2)) {
      if (!ctype2 || strncmp(ctype2,"DEC",3)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype2 && !strncmp(ctype2,"RA",2)) {
      if (!ctype1 || strncmp(ctype1,"DEC",3)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype1 && !strncmp(ctype1,"DEC",3)) {
      if (!ctype2 || strncmp(ctype2,"RA",2)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else if (ctype2 && !strncmp(ctype2,"DEC",3)) {
      if (!ctype1 || strncmp(ctype1,"RA",2)) {
	ctype1 = (char*)linear;
	ctype2 = (char*)linear;
      }
    }
    else {
      if (!ctype1)
	ctype1 =(char*)linear;
      if (!ctype2)
	ctype2 =(char*)linear;
    }

    putFitsCard(chan, key1, ctype1);
    putFitsCard(chan, key2, ctype2);


    // CRPIX
    strcpy(key1, "CRPIX1 ");
    strcpy(key2, "CRPIX2 ");
    key1[6] = key2[6] = alt;
    putFitsCard(chan, key1, head->getReal(key1,0));
    putFitsCard(chan, key2, head->getReal(key2,0));

    // CRVAL
    strcpy(key1, "CRVAL1 ");
    strcpy(key2, "CRVAL2 ");
    key1[6] = key2[6] = alt;
    putFitsCard(chan, key1, head->getReal(key1,0));
    putFitsCard(chan, key2, head->getReal(key2,0));

    // CDELT/CD/PC
    strcpy(key1, "CDELT1 ");
    strcpy(key2, "CDELT2 ");
    key1[6] = key2[6] = alt;

    char pkey1[8];
    char pkey2[8];
    char pkey3[8];
    char pkey4[8];
    strcpy(pkey1, "PC1_1 ");
    strcpy(pkey2, "PC1_2 ");
    strcpy(pkey3, "PC2_1 ");
    strcpy(pkey4, "PC2_2 ");
    pkey1[5] = pkey2[5] = pkey3[5] = pkey4[5] = alt;

    char ckey1[8];
    char ckey2[8];
    char ckey3[8];
    char ckey4[8];
    strcpy(ckey1, "CD1_1 ");
    strcpy(ckey2, "CD1_2 ");
    strcpy(ckey3, "CD2_1 ");
    strcpy(ckey4, "CD2_2 ");
    ckey1[5] = ckey2[5] = ckey3[5] = ckey4[5] = alt;

    if (head->find(key1) || head->find(key2)) {
      putFitsCard(chan, key1, head->getReal(key1,1));
      putFitsCard(chan, key2, head->getReal(key2,1));

      if (head->find(pkey1) || 
	  head->find(pkey2) ||
	  head->find(pkey3) || 
	  head->find(pkey4)) {
	putFitsCard(chan, pkey1, head->getReal(pkey1,1));
	putFitsCard(chan, pkey2, head->getReal(pkey2,1));
	putFitsCard(chan, pkey3, head->getReal(pkey3,1));
	putFitsCard(chan, pkey4, head->getReal(pkey4,1));
      }
    }
    else if (head->find(ckey1) || 
	     head->find(ckey2) ||
	     head->find(ckey3) || 
	     head->find(ckey4)) {
      putFitsCard(chan, ckey1, head->getReal(ckey1,1));
      putFitsCard(chan, ckey2, head->getReal(ckey2,0));
      putFitsCard(chan, ckey3, head->getReal(ckey3,0));
      putFitsCard(chan, ckey4, head->getReal(ckey4,1));
    }
  }
}

void FitsImage::wcs2ast(int ww, FitsHead* head, FitsHead* prim, void* chan) 
{
  if (DebugAST)
    cerr << endl << "wcs2ast()" << endl;

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

  // CTYPE
  if (
      // special case (reorder 3D cube)
      (!strncmp(wcs_[ww]->c1type,"GLON",4) && 
       strncmp(wcs_[ww]->c2type,"GLAT",4)) || 
      (strncmp(wcs_[ww]->c1type,"GLON",4) && 
       !strncmp(wcs_[ww]->c2type,"GLAT",4)) || 
      (!strncmp(wcs_[ww]->c1type,"GLAT",4) && 
       strncmp(wcs_[ww]->c2type,"GLON",4)) || 
      (strncmp(wcs_[ww]->c1type,"GLAT",4) && 
       !strncmp(wcs_[ww]->c2type,"GLON",4)) || 
      (!strncmp(wcs_[ww]->c1type,"RA",2) && 
       strncmp(wcs_[ww]->c2type,"DEC",3)) ||
      (strncmp(wcs_[ww]->c1type,"RA",2) && 
       !strncmp(wcs_[ww]->c2type,"DEC",3)) ||
      (!strncmp(wcs_[ww]->c1type,"DEC",3) && 
       strncmp(wcs_[ww]->c2type,"RA",2)) ||
      (strncmp(wcs_[ww]->c1type,"DEC",3) && 
       !strncmp(wcs_[ww]->c2type,"RA",2)))
    {
      putFitsCard(chan, "CTYPE1", "LINEAR");
      putFitsCard(chan, "CTYPE2", "LINEAR");
    }
  else 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_LIN) && 
	   (strncmp(wcs_[ww]->ctype[0]+2,"LN",2)) &&
	   (strncmp(wcs_[ww]->ctype[0]+2,"LT",2)) &&
	   (strncmp(wcs_[ww]->ctype[0]+1,"LON",3)) &&
	   (strncmp(wcs_[ww]->ctype[0]+1,"LAT",3))) {
    // this is not a mistake
    putFitsCard(chan, "CTYPE1", wcs_[ww]->c1type);
    putFitsCard(chan, "CTYPE2", wcs_[ww]->c2type);
  }
  else if (wcs_[ww]->prjcode == WCS_PIX) {
    // this is not a mistake
    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 (head->find(cd.str().c_str()) || 
	(prim && prim->find(cd.str().c_str()))) {
      // 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]->imflip &&
	  !wcs_[ww]->coorflip &&
	  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 (head->find(cdelt.str().c_str()) || 
	     (prim && prim->find(cdelt.str().c_str()))) {
      putFitsCard(chan, "CDELT1", wcs_[ww]->cdelt[0]);
      putFitsCard(chan, "CDELT2", wcs_[ww]->cdelt[1]);

      if (head->find(pc.str().c_str()) ||
	  (prim && prim->find(pc.str().c_str()))) {
	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 && 
	       (head->find("PC001001") || (prim && prim->find("PC001001")))) {
	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 && 
	    (head->find("CROTA1") || (prim && prim->find("CROTA1"))))
	  putFitsCard(chan, "CROTA1", wcs_[ww]->rot);
	if (!ww && 
	    (head->find("CROTA2") || (prim && head->find("CROTA2"))))
	  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);

    ostringstream radesys;
    radesys << "RADESYS" << alt << ends;
    if (head->find(radesys.str().c_str())) {
      // if RADESYS present, use it
      putFitsCard(chan, "RADESYS", head->getString(radesys.str().c_str()));
    }
    else if (prim && prim->find(radesys.str().c_str())) {
      // if RADESYS present, use it
      putFitsCard(chan, "RADESYS", prim->getString(radesys.str().c_str()));
    }
    else if (head->find("RADECSYS")) {
      // look for old RADECSYS
      putFitsCard(chan, "RADESYS", head->getString("RADECSYS"));
    }
    else if (prim && prim->find("RADECSYS")) {
      // look for old RADECSYS
      putFitsCard(chan, "RADESYS", prim->getString("RADECSYS"));
    }
    else {
      // fall back on wcssubs
      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)
  // MAXPV defined in wcs.h
  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 (head->find(str.str().c_str())) {
	double val = head->getReal(str.str().c_str(),0);
	putFitsCard(chan, str2.str().c_str(), val);
      }
      else if (prim && prim->find(str.str().c_str())) {
	double val = prim->getReal(str.str().c_str(),0);
	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 (head->find(str.str().c_str())) {
	double val = head->getReal(str.str().c_str(),0);
	putFitsCard(chan, str2.str().c_str(), val);
      }
      else if (prim && prim->find(str.str().c_str())) {
	double val = prim->getReal(str.str().c_str(),0);
	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 (head->find(str.str().c_str())) {
	  char* val = head->getString(str.str().c_str());
	  if (val) {
	    putFitsCard(chan, str.str().c_str(), val);
	    delete [] val;
	  }
	}
	else if (prim && prim->find(str.str().c_str())) {
	  char* val = prim->getString(str.str().c_str());
	  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 (head->find("A_ORDER")) {
      int val = head->getInteger("A_ORDER",0);
      putFitsCard(chan, "A_ORDER", val);
    }
    else if (prim && prim->find("A_ORDER")) {
      int val = prim->getInteger("A_ORDER",0);
      putFitsCard(chan, "A_ORDER", val);
    }

    if (head->find("AP_ORDER")) {
      int val = head->getInteger("AP_ORDER",0);
      putFitsCard(chan, "AP_ORDER", val);
    }
    else if (prim && prim->find("AP_ORDER")) {
      int val = prim->getInteger("AP_ORDER",0);
      putFitsCard(chan, "AP_ORDER", val);
    }

    if (head->find("A_DMAX")) {
      double val = head->getReal("A_DMAX",0);
      putFitsCard(chan, "A_DMAX", val);
    }
    else if (prim && prim->find("A_DMAX")) {
      double val = prim->getReal("A_DMAX",0);
      putFitsCard(chan, "A_DMAX", val);
    }

    if (head->find("B_ORDER")) {
      int val = head->getInteger("B_ORDER",0);
      putFitsCard(chan, "B_ORDER", val);
    }
    else if (prim && prim->find("B_ORDER")) {
      int val = prim->getInteger("B_ORDER",0);
      putFitsCard(chan, "B_ORDER", val);
    }

    if (head->find("BP_ORDER")) {
      int val = head->getInteger("BP_ORDER",0);
      putFitsCard(chan, "BP_ORDER", val);
    }
    else if (prim && prim->find("BP_ORDER")) {
      int val = prim->getInteger("BP_ORDER",0);
      putFitsCard(chan, "BP_ORDER", val);
    }

    if (head->find("B_DMAX")) {
      double val = head->getReal("B_DMAX",0);
      putFitsCard(chan, "B_DMAX", val);
    }
    else if (prim && prim->find("B_DMAX")) {
      double val = prim->getReal("B_DMAX",0);
      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 (head->find(str.str().c_str())) {
	    double val = head->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	  else if (prim && prim->find(str.str().c_str())) {
	    double val = prim->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
	{
	  ostringstream str;
	  str << "AP_" << jj << "_" << ii << ends;
	  if (head->find(str.str().c_str())) {
	    double val = head->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	  else if (prim && prim->find(str.str().c_str())) {
	    double val = prim->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
	{
	  ostringstream str;
	  str << "B_" << jj << "_" << ii << ends;
	  if (head->find(str.str().c_str())) {
	    double val = head->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	  else if (prim && prim->find(str.str().c_str())) {
	    double val = prim->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
	{
	  ostringstream str;
	  str << "BP_" << jj << "_" << ii << ends;
	  if (head->find(str.str().c_str())) {
	    double val = head->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	  else if (prim && prim->find(str.str().c_str())) {
	    double val = prim->getReal(str.str().c_str(),0);
	    putFitsCard(chan, str.str().c_str(), val);
	  }
	}
      }
    }
  }
}

void FitsImage::wcs2ast0(int ww, void* chan) 
{
  if (DebugAST)
    cerr << endl << "wcs2ast0()" << endl;

  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]);

  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]);

  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);

  putFitsCard(chan, "RADESYS", wcs_[ww]->radecsys);
}

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;
}
