Index: /trunk/extsrc/gpcsw/Makefile.in
===================================================================
--- /trunk/extsrc/gpcsw/Makefile.in	(revision 23923)
+++ /trunk/extsrc/gpcsw/Makefile.in	(revision 23924)
@@ -15,13 +15,22 @@
 	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/ThisIsTopLevel gpcsrc/ThisIsTopLevel
 	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/missing_protos.h gpcsrc/missing_protos.h
-# remove existing directories
-	rm -rf gpcsrc/fits
-	rm -rf gpcsrc/analysis
 # required subdirs
-	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/analysis/libpscoords gpcsrc/analysis/libpscoords
-	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/analysis/libpsf gpcsrc/analysis/libpsf
-	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/fits/libfh gpcsrc/fits/libfh
-	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/fits/libfhreg gpcsrc/fits/libfhreg
-	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/fits/burntool gpcsrc/fits/burntool
+	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/analysis/libpscoords gpcsrc/analysis/libpscoords.new
+	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/analysis/libpsf gpcsrc/analysis/libpsf.new
+	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/fits/libfh gpcsrc/fits/libfh.new
+	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/fits/libfhreg gpcsrc/fits/libfhreg.new
+	svn export https://svn.ifa.hawaii.edu/gpc/repos/sw/trunk/fits/burntool gpcsrc/fits/burntool.new
+# replace new files with those from the imported directories
+	cp -rf gpcsrc/analysis/libpscoords.new/* gpcsrc/analysis/libpscoords
+	cp -rf gpcsrc/analysis/libpsf.new/* gpcsrc/analysis/libpsf
+	cp -rf gpcsrc/fits/libfh.new/* gpcsrc/fits/libfh
+	cp -rf gpcsrc/fits/libfhreg.new/* gpcsrc/fits/libfhreg
+	cp -rf gpcsrc/fits/burntool.new/* gpcsrc/fits/burntool
+# remove the temp directory
+	rm -rf gpcsrc/analysis/libpscoords.new
+	rm -rf gpcsrc/analysis/libpsf.new
+	rm -rf gpcsrc/fits/libfh.new
+	rm -rf gpcsrc/fits/libfhreg.new
+	rm -rf gpcsrc/fits/burntool.new
 # replace the standard gpc/Make.Common with our repaired version (g77 -> gfortran)
 	cp -f Make.Common.fixed gpcsrc/Make.Common
Index: /trunk/extsrc/gpcsw/burntool.nopsf/Makefile
===================================================================
--- /trunk/extsrc/gpcsw/burntool.nopsf/Makefile	(revision 23924)
+++ /trunk/extsrc/gpcsw/burntool.nopsf/Makefile	(revision 23924)
@@ -0,0 +1,40 @@
+include ../Make.Common
+
+VERSION=0.00
+# CCWARN+=$(WERROR)
+CCDEFS+=$(VERSIONDEFS)
+CCLINK += -lm
+# $(EXECNAME): $(OBJS) libfh.a libpsf.a libpscoords.a
+$(EXECNAME): $(OBJS) libfh.a libpscoords.a
+
+include ../Make.Common
+
+# Dependencies by Make.Common $Revision: 2.17 $
+
+$(OBJ)/burnfix.o: burnfix.c burntool.h burnparams.h
+
+$(OBJ)/burntool.o: burntool.c fh/fh.h fhreg/general.h \
+  fhreg/macros.h fhreg/gpc_detector.h \
+  burntool.h burnparams.h persist_fits.h
+
+$(OBJ)/burnutils.o: burnutils.c burntool.h burnparams.h
+
+$(OBJ)/cheapfits.o: cheapfits.c
+
+$(OBJ)/persist_fits.o: persist_fits.c fh/fh.h burntool.h \
+  burnparams.h persist_fits.h
+
+$(OBJ)/persistfix.o: persistfix.c burntool.h burnparams.h
+
+$(OBJ)/persistio.o: persistio.c burntool.h burnparams.h
+
+$(OBJ)/psfstamp.o: psfstamp.c burntool.h burnparams.h \
+  pscoords/pscoords.h
+
+$(OBJ)/psfstats.o: psfstats.c burntool.h psf/psf.h
+
+$(OBJ)/sort.o: sort.c
+
+$(OBJ)/stardetect.o: stardetect.c burntool.h burnparams.h
+
+$(OBJ)/trailfit.o: trailfit.c burntool.h burnparams.h
Index: /trunk/extsrc/gpcsw/burntool.nopsf/psfstats.c
===================================================================
--- /trunk/extsrc/gpcsw/burntool.nopsf/psfstats.c	(revision 23924)
+++ /trunk/extsrc/gpcsw/burntool.nopsf/psfstats.c	(revision 23924)
@@ -0,0 +1,20 @@
+/* dummy version of psfstats.c - disable use of the psf */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "burntool.h"
+#include "math.h"
+#include "psf/psf.h"
+
+/****************************************************************/
+/* psf_stats(): Tell us about this PSF star */
+STATIC int psf_stats(int nx, int ny, IMTYPE *data, int bias, 
+		     double *fwhm, double *q)
+{
+   return(1);
+}
Index: /trunk/extsrc/gpcsw/gpcsrc/Make.Common
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/Make.Common	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/Make.Common	(revision 23924)
@@ -134,5 +134,5 @@
 TAR	 = gtar
 AR	 = ar
-FC       = gfortran
+FC       = g77
 CC	 = gcc
 LD       = gcc
@@ -673,5 +673,5 @@
 	@-rm -f $(DIR_BIN)/*.TEXT_FILE_BUSY 2> /dev/null || /bin/true
 	@ln $< $@
-	# @-setuidinst $@
+	@-setuidinst $@
 
 $(DIR_BIN)/%-$(VERSION): scripts/%.sh
Index: /trunk/extsrc/gpcsw/gpcsrc/analysis/libpscoords/pscoords.h
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/analysis/libpscoords/pscoords.h	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/analysis/libpscoords/pscoords.h	(revision 23924)
@@ -1,3 +1,3 @@
-/* Three-quarter-assed pscoords.h until jt writes me a real one */
+/* Header file for pscoords.c */
 #define NINT(x) (x<0?(int)((x)-0.5):(int)((x)+0.5))
 
@@ -128,4 +128,8 @@
 const PSC_OFFROT_T psc_otaoff[PSC_NX*PSC_NY];
 
+#define PS_scale 38.860	/* Default PS plate scale [um/arcsec] */
+#define PS_d3  1.49e-10	/* Default PS distortion [arcsec^-2] */
+#define PS_airdens 0.71	/* Default PS1 qir density (Haleakala) */
+
 /**********************/
 /* Normal Application */
@@ -147,5 +151,4 @@
  */
 
-
 /**************/
 /* Prototypes */
Index: /trunk/extsrc/gpcsw/gpcsrc/analysis/libpsf/psf.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/analysis/libpsf/psf.c	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/analysis/libpsf/psf.c	(revision 23924)
@@ -207,4 +207,7 @@
 /* Load up extras */
    if(psfextra != NULL) {
+/* Sanity check against no error but crazy fit! */
+      psfextra->xfw = xfwhm*binx;
+      psfextra->yfw = yfwhm*biny;
 /* Note that these major/minor things are messed up if binx!=biny */
       psfextra->majfw = wpar[9]*binx;
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/Makefile
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/Makefile	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/Makefile	(revision 23924)
@@ -5,5 +5,4 @@
 CCDEFS+=$(VERSIONDEFS)
 CCLINK += -lm
-
 # $(EXECNAME): $(OBJS) libfh.a libpsf.a libpscoords.a
 $(EXECNAME): $(OBJS) libfh.a libpscoords.a
@@ -13,15 +12,16 @@
 # Dependencies by Make.Common $Revision: 2.17 $
 
-$(OBJ)/basement.o: basement.c
-
 $(OBJ)/burnfix.o: burnfix.c burntool.h burnparams.h
 
 $(OBJ)/burntool.o: burntool.c fh/fh.h fhreg/general.h \
   fhreg/macros.h fhreg/gpc_detector.h \
-  burntool.h burnparams.h
+  burntool.h burnparams.h persist_fits.h
 
 $(OBJ)/burnutils.o: burnutils.c burntool.h burnparams.h
 
 $(OBJ)/cheapfits.o: cheapfits.c
+
+$(OBJ)/persist_fits.o: persist_fits.c fh/fh.h burntool.h \
+  burnparams.h persist_fits.h
 
 $(OBJ)/persistfix.o: persistfix.c burntool.h burnparams.h
@@ -34,4 +34,6 @@
 $(OBJ)/psfstats.o: psfstats.c burntool.h psf/psf.h
 
+$(OBJ)/sort.o: sort.c
+
 $(OBJ)/stardetect.o: stardetect.c burntool.h burnparams.h
 
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burnparams.h
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burnparams.h	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burnparams.h	(revision 23924)
@@ -1,3 +1,5 @@
 /* A global buffer into which routines can accumulate detections */
+#ifndef _INCLUDED_burnparams_
+#define _INCLUDED_burnparams_
 
 /* '#define EXTERN' in just one file, burntool.c, to declare variables */
@@ -28,4 +30,7 @@
 EXTERN int MAX_READ_NOISE;	/* Maximum believable read noise (ADU) */
 EXTERN double MIN_EADU;		/* Minimum believable e/ADU */
+EXTERN int SAT4SURE;		/* Ignore pixels above for noise estimate */
+EXTERN double MIN_BLAST_PASS;	/* Allow blasted cells if they have BIG satfrac */
+EXTERN double MAX_BLAST_PASS;	/* But not if it's all wiped out! */
 
 EXTERN int BURN_THRESH  ;	/* Threshold for onset of burning */
@@ -48,4 +53,5 @@
 
 EXTERN double NEGLIGIBLE_TRAIL;	/* Don't sweat less than this * sigma */
+EXTERN int EXPIRE_TRAIL_TIME;	/* Expire trails after this interval */
 
-
+#endif /* _INCLUDED_burnparams_ */
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burntool.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burntool.c	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burntool.c	(revision 23924)
@@ -20,4 +20,5 @@
 #define EXTERN  /* Define EXTERN to declare variables in params.h */
 #include "burnparams.h"
+#include "persist_fits.h"
 
 int
@@ -28,5 +29,4 @@
    HeaderUnit ehu;
    const char* ifilename = "-";
-   const char* ofilename = NULL;
    char extname[FH_MAX_STRLEN+1], otaposn[FH_MAX_STRLEN+1];
    int cellmask[MAXCELL];
@@ -34,26 +34,11 @@
    int nextend, cellxy, cell, cellcode;
    int ext, update, restore, psfsize, psfavg;
-   int outfd;
    IMTYPE *buf;
-   const char *burnfile=NULL,  *persistfile=NULL;
+   const char *burnfile=NULL,  *persistfile=NULL, *persistfitsfile=NULL;
+   const char *deltablefitsfile=NULL;
    const char *psffile=NULL, *psfstatfile=NULL;
    CELL OTA[MAXCELL];	/* Cell structure for entire OTA */
 
-   if (argc == 1)  {
-     syntax(argv[0]);
-     exit(EXIT_SUCCESS);
-   }
-
    if(argc > 1 && strncmp(argv[1], "help", 4) == 0) {
-      syntax(argv[0]);
-      exit(EXIT_SUCCESS);
-   }
-
-   if(argc > 1 && strncmp(argv[1], "--help", 6) == 0) {
-      syntax(argv[0]);
-      exit(EXIT_SUCCESS);
-   }
-
-   if(argc > 1 && strncmp(argv[1], "-h", 6) == 0) {
       syntax(argv[0]);
       exit(EXIT_SUCCESS);
@@ -71,5 +56,7 @@
       fprintf(stderr, "\rerror: `%s' is not a multi-extension FITS\n",
 	      ifilename);
+#ifndef JT2DHACK
       exit(EXIT_FAILURE);
+#endif
    }
 
@@ -91,4 +78,7 @@
    MAX_READ_NOISE = 20;		/* Maximum believable read noise (ADU) */
    MIN_EADU = 0.3;		/* Minimum believable e/ADU */
+   SAT4SURE  = 60000;		/* Ignore pixels above for noise estimate */
+   MIN_BLAST_PASS = 0.1;	/* Allow blasted cells if they have BIG satfrac */
+   MAX_BLAST_PASS = 0.9;	/* But not if it's all wiped out! */
 
    BURN_THRESH  = 30000;	/* Threshold for onset of burning */
@@ -108,5 +98,5 @@
 
    NEGLIGIBLE_TRAIL = 0.5;	/* Don't sweat less than this * sigma */
-
+   EXPIRE_TRAIL_TIME = 2000;	/* Expire a persist after this [sec] */
 
 /* Parse the args */
@@ -155,22 +145,23 @@
 	 burnfile = argv[i] + 9;
 
-/* Output file for burn streaks */
+/* Output text file for burn streaks */
       } else if(strncmp(argv[i], "out=", 4) == 0) {	/* out=fname */
 	 burnfile = argv[i] + 4;
 
-# if (0)
-/* XXX disable for now: is not yet working */
-/* Alternate Output file for deburned image */
-      } else if(strncmp(argv[i], "outimage=", 9) == 0) {	/* outimage=fname */
-	ofilename = argv[i] + 9;
-# endif
-
-/* Input file for previous burn persistence streaks */
-      } else if(strncmp(argv[i], "trailin=", 8) == 0) {	/* trailin=fname */
+/* Input text file for previous burn persistence streaks */
+      } else if(strncmp(argv[i], "infits=", 8) == 0) {	/* in=fname */
 	 persistfile = argv[i] + 8;
+
+/* Same thing, but information is stored in tables in a FITS file. */
+      } else if(strncmp(argv[i], "trailinfits=", 8) == 0) { /* infits=fname */
+	 persistfitsfile = argv[i] + 12;
 
 /* Input file for previous burn persistence streaks */
       } else if(strncmp(argv[i], "in=", 3) == 0) {	/* in=fname */
 	 persistfile = argv[i] + 3;
+
+/* Same thing, but information is stored in tables in a FITS file. */
+      } else if(strncmp(argv[i], "trailinfits=", 8) == 0) { /* trailin=fname */
+	 persistfitsfile = argv[i] + 12;
 
 /* Output file for PSF gallery */
@@ -244,4 +235,10 @@
       } else if(strncmp(argv[i], "thrpsf=", 7) == 0) {	/* thrpsf=thresh */
 	 if(sscanf(argv[i]+7, "%d", &PSF_THRESH) != 1) {
+	    fprintf(stderr, "\rerror: cannot get value from `%s'\n", argv[i]);
+	    exit(EXIT_FAILURE);
+	 }
+
+      } else if(strncmp(argv[i], "expire=", 7) == 0) {	/* expire=nsec */
+	 if(sscanf(argv[i]+7, "%d", &EXPIRE_TRAIL_TIME) != 1) {
 	    fprintf(stderr, "\rerror: cannot get value from `%s'\n", argv[i]);
 	    exit(EXIT_FAILURE);
@@ -295,4 +292,7 @@
 	 }
 
+      } else if(strncmp(argv[i], "deltables=", 8) == 0) { /* trailin=fname */
+	 deltablefitsfile = argv[i] + 10;        
+
       } else if(strncmp(argv[i], "help", 4) == 0) {	/* help output */
 	 syntax(argv[0]);
@@ -306,7 +306,22 @@
    }
 
+   /* If we're told to remove the table from a FITS file, then
+    * do nothing else. */
+   if(deltablefitsfile) {
+     fprintf(stderr, 
+             "\rRemoving burn tables from %s, all other options ignored.\n",
+             ifilename);
+     if(persist_fits_remove_tables(ihu, deltablefitsfile) != FH_SUCCESS) {
+       exit(EXIT_FAILURE);
+     }
+     else {
+       exit(EXIT_SUCCESS);
+     }
+   }
+   
+   /* If there is no other persistence info supplied, try getting
+    * it from the input FITS file. */
    if(restore && persistfile == NULL) {
-      fprintf(stderr, "\rerror: must specify an input file for restore\n");
-      exit(EXIT_FAILURE);
+     if(persistfitsfile == NULL) persistfitsfile = ifilename;
    }
 
@@ -322,4 +337,7 @@
       if(persist_read(OTA, persistfile)) exit(EXIT_FAILURE);
    }
+   else if(persistfitsfile != NULL) {
+     if(persist_fits_read(OTA, persistfitsfile) != FH_SUCCESS) exit(EXIT_FAILURE);
+   }
 
 /* Which OTA is this??? */
@@ -333,19 +351,34 @@
    }
 
-   if (ofilename) {
-     outfd = creat (ofilename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
-     if (outfd == -1) {
-	 fprintf(stderr, "\rerror: Cannot open %s for output\n", ofilename);
-         exit(EXIT_FAILURE);
-      }
-
-     if (fh_write(ihu, outfd)) {
-	 fprintf(stderr, "\rerror: Trouble writing PHU to %s\n", ofilename);
-         exit(EXIT_FAILURE);
-      }
+   /* Warn that what you're doing might not be a good idea. */
+   {
+     fh_bool burn_applied;
+     
+     if(fh_get_bool(ihu, PHU_NAME_BURN_APPLIED, 
+                    &burn_applied) != FH_SUCCESS) {
+	if(VERBOSE > 0) {
+	   fprintf(stderr, 
+		   "warning: Unable to determine whether burn correction "
+		   "already applied - unable to find %s in primary header\n",
+		   PHU_NAME_BURN_APPLIED);
+	}
+	burn_applied = FH_FALSE;
+     }
+     if(restore && (burn_applied == FH_FALSE)) {
+       fprintf(stderr, 
+               "warning: Restoring old burns, but header indicates no burns previously corrected.\n");             
+     }
+     else if (update && (burn_applied == FH_TRUE)) {
+       fprintf(stderr, 
+               "warning: Applying burn correction, but header indicates burns previously corrected.\n");             
+       }
    }
 
 /* Look at all the MEF's extensions */
+#ifndef JT2DHACK
    for (ext = 1; ext <= nextend; ext++) {
+#else
+   for (ext = 1; ext <= MAX(1,nextend); ext++) {
+#endif
       int naxis, naxis1, naxis2, naxis3;
       int prescan1, ovrscan1, ovrscan2, pontime;
@@ -353,4 +386,10 @@
       char xtension[FH_MAX_STRLEN + 1];
 
+#ifdef JT2DHACK
+      if(nextend == 0) {
+	 ehu = ihu;
+	 sprintf(extname, "%s", "xy00");
+      } else {
+#endif
       if (!(ehu = fh_ehu(ihu, ext)) || 
 	  fh_get_str(ehu, "EXTNAME", extname, sizeof(extname)) != FH_SUCCESS) {
@@ -377,4 +416,7 @@
          fprintf(stderr,
                  "warning: Skipping non-image extension %s\n", extname);
+#ifdef JT2DHACK
+      }
+#endif
 /*
  * %%% When reading from a pipe, does the data still need
@@ -448,4 +490,9 @@
       if (fh_get_BZERO(ehu, &bzero_d) == FH_SUCCESS) BZERO = bzero_d;
 
+      if(VERBOSE & VERB_NORM) {
+	 printf("nx=%d ny=%d prex=%d postx=%d posty=%d BZERO=%.1f\n", 
+		naxis1, naxis2, prescan1, ovrscan1, ovrscan2, BZERO);
+      }
+
       buf = (IMTYPE*)malloc(naxis1*naxis2*naxis3*sizeof(short));
       if (fh_read_padded_image(ehu, fh_file_desc(ehu), buf,
@@ -471,10 +518,24 @@
 /* Get bias and sky levels */
 	 err = cell_stats(nx, ny, naxis1, naxis2, imbuf, OTA+cell);
+	 if(err) {
+	    if(VERBOSE > 0) {
+	       fprintf(stderr, "logonly: error getting bias/sky/rms for cell %d, skipping...\n", cell);
+	    }
+	    continue;
+	 }
+
+/* Does this cell get a pass because it's heavily blasted? */
+/* Turn it in to a relaxation factor for the "read noise" */
+	 i = OTA[cell].satfrac > MIN_BLAST_PASS &&
+	     OTA[cell].satfrac < MAX_BLAST_PASS &&
+	     OTA[cell].sky < TRAIL_THRESH ? 30 : 1;
 
 /* Does this cell look kosher? */
 	 if(OTA[cell].rms*OTA[cell].rms > 
-	    OTA[cell].sky/MIN_EADU + MAX_READ_NOISE*MAX_READ_NOISE) {
+	    OTA[cell].sky/MIN_EADU + i*i*MAX_READ_NOISE*MAX_READ_NOISE) {
 	    if(VERBOSE > 0) {
 	       fprintf(stderr, "logonly: cell %d is unreasonably noisy, skipping...\n", cell);
+	       fprintf(stderr, "logonly: bias = %d sky = %d rms = %d satfrac = %.2f\n", 
+		       OTA[cell].bias, OTA[cell].sky, OTA[cell].rms, OTA[cell].satfrac);
 	    }
 	    continue;
@@ -506,5 +567,4 @@
 
 	 } else {
-
 /* Restore the old burns */
 	    burn_restore(naxis1-ovrscan1, naxis2-ovrscan2, naxis1, 
@@ -514,44 +574,19 @@
 /* Write the corrected data back to the FITS. */
 	 if(update) {
-	   if (ofilename) {
-	     fh_ehu(ehu, 0);	/* Seek back to the start of data */
-
-	     if (fh_write(ehu, outfd)) {
-	       fprintf(stderr, "\rerror: Trouble writing EXT %d to %s\n", ext, ofilename);
-	       exit(EXIT_FAILURE);
-	     }
-
-	     if (fh_write_padded_image(ehu, outfd, buf,
-				       naxis1*naxis2*naxis3*sizeof(short),
-				       FH_TYPESIZE_16) != FH_SUCCESS) {
+	    fh_ehu(ehu, 0);	/* Seek back to the start of data */
+	    if (fh_write_padded_image(ehu, fh_file_desc(ehu), buf,
+				      naxis1*naxis2*naxis3*sizeof(short),
+				      FH_TYPESIZE_16) != FH_SUCCESS) {
 	       fprintf(stderr, "\rerror: failed to re-write image data for extension `%s'.\n",
 		       extname);
 	       free(buf);
 	       exit(EXIT_FAILURE);
-	     }
-	   } else {
-	     fh_ehu(ehu, 0);	/* Seek back to the start of data */
-	     if (fh_write_padded_image(ehu, fh_file_desc(ehu), buf,
-				       naxis1*naxis2*naxis3*sizeof(short),
-				       FH_TYPESIZE_16) != FH_SUCCESS) {
-	       fprintf(stderr, "\rerror: failed to re-write image data for extension `%s'.\n",
-		       extname);
-	       free(buf);
-	       exit(EXIT_FAILURE);
-	     }
-	   }
-	 }
-      }
-      
+	    }
+	 }
+      }
+
       free(buf);
    }
 
-   if (ofilename) {
-     if (close(outfd)) {
-       fprintf(stderr, "\rerror: trouble writing data to output file %s\n", ofilename);
-       exit(EXIT_FAILURE);
-     }
-   }
-      
 /* Dump out the postage stamp file */
    if(psffile != NULL) {
@@ -564,8 +599,20 @@
    }
 
+/* Write burn info to FITS file. */
+   if(update) persist_fits_write(OTA, ihu);
+
+   
+   if(restore) {    
+     /* Indicate in the header that the burns are not applied. */
+     fh_set_bool(ihu, FH_AUTO, PHU_NAME_BURN_APPLIED, 
+                 FH_FALSE, PHU_COMMENT_BURN_APPLIED);
+     fh_rewrite(ihu);
+   }
+
    fh_destroy(ihu);
 
 /* Write the persistence data for the next image */
    if(burnfile != NULL) persist_write(OTA, burnfile);
+
 
    exit(EXIT_SUCCESS);
@@ -613,5 +660,5 @@
 		      CELL *cell)
 {
-   int i, j, k, n;
+   int i, j, k, n, nsat;
 
 /* Get bias stats */
@@ -624,9 +671,20 @@
 
 /* Get sky stats */
-   for(k=n=0; k<nx*ny; k+=((617*nx)/1000)) {
+   for(k=n=nsat=0; k<nx*ny; k+=((617*nx)/1000)) {
       i = k % nx;
       j = k / nx;
-      median_buf[n++] = data[i+NX*j];
-   }
+      if(data[i+NX*j] > SAT4SURE) {
+	 nsat++;
+      } else if(data[i+NX*j] != NODATA) {
+	 median_buf[n++] = data[i+NX*j];
+      }
+   }
+
+   if(n < 20) {		/* Better have hit at least 20! */
+      cell->sky = cell->rms = 0.0;
+      return(-1);
+   }
+   cell->satfrac = ((double)nsat) / n;
+
 /* First pass at sky and quartile */
    cell->sky = int_median(n, median_buf);
@@ -656,8 +714,15 @@
    printf(" restore={t|f}  Restore the input MEF by adding input fits?\n");
    printf(" in=fname       Input file for previous burn persistence streaks\n");
+   printf(" infits=fname   Input FITS file for previous burn persistence streaks (stored\n");
+   printf("                   in table extensions).  If both this and the 'in' input\n");
+   printf("                   file option are specified, then 'in' takes precedence.\n");
    printf(" out=fname      Output file for burn streaks\n");
-   printf(" outimage=imname Output file for MEF image\n");
+
 //   printf(" trailin=fname  Input file for previous burn persistence streaks\n");
+//   printf(" trailinfits=fname  Input FITS file for previous burn persistence streaks\n");
 //   printf(" trailout=fname Output file for burn streaks\n");
+   printf(" deltables=fname Copy mef_file to new FITS file 'fname' with burn streak\n");
+   printf("                   tables removed. NOTE: if specified, all other options\n");
+   printf("                   will be ignored!\n");
    printf(" psf=fname      Output file for PSF FITS stamp gallery\n");
    printf(" psfstat=fname  Output file for PSF statistics listing\n");
@@ -676,4 +741,5 @@
    printf(" rmask=X        Diameter growth factor of burned spots\n");
    printf(" bmask=X        Box size growth of burn/star boxes\n");
+   printf(" expire=N       Retire a blasted burn after N seconds\n");
    printf(" quiet={t|f}    Quiet?\n");
    printf(" verbose=N      Set verbosity bits:\n");
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burntool.h
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burntool.h	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/burntool.h	(revision 23924)
@@ -1,2 +1,5 @@
+#ifndef _INCLUDED_burntool_
+#define _INCLUDED_burntool_
+
 /* Burn correction routines:
    -------------------------  
@@ -63,5 +66,5 @@
 
 #define MAXCELL	64		/* Max cells in an OTA */
-#define MAXSIZE 700		/* Maximum vertical cell size */
+#define MAXSIZE 2048		/* Maximum vertical cell size */
 
 #define STAR_RADIUS  4		/* Radius over which a star ctr must be max */
@@ -87,5 +90,6 @@
 #define BURN_PWR  1		/* Power law */
 #define BURN_EXP  2		/* Exponential */
-#define PSF_STAR  3		/* Unfitted: good psf star */
+#define BURN_BLASTED 3		/* Blasted top to bottom: flag only for IPP */
+#define PSF_STAR  9		/* Unfitted: good psf star */
 
 /* Fit error codes */
@@ -93,4 +97,5 @@
 #define FIT_TOP_ERROR  2	/* Saturation extends to top: no points */
 #define FIT_SLOPE_ERROR  3	/* Unreasonable fit */
+#define FIT_EXPIRED 9		/* Don't carry any more as persistent */
 
 /* Fit parameters */
@@ -152,4 +157,5 @@
       int rms;			/* RMS in the sky */
       int time;			/* PON time of this cell */
+      double satfrac;		/* Fraction of SAT4SURE saturated pixels */
       int nburn;		/* Number of trails left by sat stars */
       OBJBOX *burn;		/* Stars which we think left a trail */
@@ -212,2 +218,4 @@
 int write_2ddata(int nx, int ny, int *ntot, IMTYPE *data, int fd);
 int write_3dend(int *ntot, int fd);
+
+#endif /* _INCLUDED_burntool_ */
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/man/burntool.1
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/man/burntool.1	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/man/burntool.1	(revision 23924)
@@ -53,4 +53,11 @@
 	power laws; downward burns as exponentials.
 	
+	Burntool also identifies really blasted areas which are saturated from
+	top to bottom.  These cannot be fitted, but are carried along for
+	EXPIRE_TRAIL_TIME seconds (default 2000) in the persistence list.  The
+	idea is to give IPP a clue that something bad happened, and of course
+	the persistence info tells when it happened.  This expiration time
+	for carrying the region can be set with "expire=N".
+
 	Burntool finishes by rewriting the modified MEF and all of the fits in
 	an output file ("out=file_name").  If desired, burntool can be run
@@ -112,9 +119,13 @@
 	   npsf=3          Number of contributing PSF stars
 	   fwhm=5.38       PSF FWHM [pix] (0.0 if fit fails)
-	   m2=11.08        Second moment of distribution [pix^2] 
-	   qp=5.154        Plus quadrupole qxx-qyy [pix^2]	 
-	   qc=0.560        Cross quadrupole 2*qxy [pix^2]	 
-	   qt=-5.201       Tangential quadrupole [pix^2] 	  
+	   fmed=5.76       FWHM averaged over psfavg (0.0 if fit fails)
+	   m2=11.08        Second moment of distribution [pix^2] (-99.99 if fails)
+	   qp=5.154        Plus quadrupole qxx-qyy [pix^2] (-99.99 if fails)
+	   qc=0.560        Cross quadrupole 2*qxy [pix^2] (-99.99 if fails)
+	   qt=-5.201       Tangential quadrupole [pix^2] (-99.99 if fails)
 			   	q+ * cos(2*phi) + qx * sin(2*phi)
+	   qpm=5.154       Plus quadrupole averaged over psfavg
+	   qcm=0.560       Cross quadrupole averaged over psfavg
+	   qtm=-5.201      Tangential averaged over psfavg
 
 	Helpful utilities include:
@@ -148,6 +159,17 @@
 		Input file for previous burn persistence streaks
 
+	infits=fname       
+		Input FITS file for previous burn persistence streaks
+                (burn streaks stored in table extensions). If both
+                this and the 'in' input file option are specified, then
+                'in' takes precedence.
+
 	out=fname      
 		Output file for burn streaks
+
+        deltables=fname
+                Copy mef_file to new FITS file 'fname' with burn streak 
+                tables removed. NOTE: if specified, all other options 
+                will be ignored!
 
 	psf=fname      
@@ -199,4 +221,7 @@
 	bmask=X        
 		Box size growth of burn/star (BMASK_GROW)
+
+	expire=N
+		Retire a blasted burn after N seconds
 
 	quiet={t|f}    
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persist_fits.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persist_fits.c	(revision 23924)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persist_fits.c	(revision 23924)
@@ -0,0 +1,1224 @@
+/* -*- c-file-style: "Ellemtel" -*-
+ *
+ * persist_fits.c - read and write persistence info to FITS tables.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "fh/fh.h"
+
+#include "burntool.h"
+#include "burnparams.h"
+#include "persist_fits.h"
+
+/* Default names for burn info table extensions. We'll use keyword information
+ * when reading to determine the names of the extensions, but we'll always 
+ * write them with these names. */
+#define DEFAULT_EXTNAME_AREA_TABLE "burntool_areas"
+#define DEFAULT_EXTNAME_FIT_TABLE  "burntool_fits"
+
+/* Index of all the columns of the area table. */
+typedef enum
+{
+   AREA_TABLE_COL_CELL,
+   AREA_TABLE_COL_TIME,
+   AREA_TABLE_COL_CX,
+   AREA_TABLE_COL_CY,
+   AREA_TABLE_COL_MAX,
+   AREA_TABLE_COL_Y0,
+   AREA_TABLE_COL_SX,
+   AREA_TABLE_COL_SY,
+   AREA_TABLE_COL_EX,
+   AREA_TABLE_COL_EY,
+   AREA_TABLE_COL_Y0M,
+   AREA_TABLE_COL_Y0P,
+   AREA_TABLE_COL_Y1M,
+   AREA_TABLE_COL_Y1P,
+   AREA_TABLE_COL_X0M,
+   AREA_TABLE_COL_X0P,
+   AREA_TABLE_COL_X1M,
+   AREA_TABLE_COL_X1P,
+   AREA_TABLE_COL_FUNC,
+   AREA_TABLE_COL_UP,
+   AREA_TABLE_COL_SLOPE,
+   AREA_TABLE_COL_NFIT,
+   AREA_TABLE_COL_SXFIT,
+   AREA_TABLE_COL_EXFIT,
+   
+   /* Add new columns above this line. */
+   AREA_TABLE_NUM_COLS
+} AREA_TABLE_COL_T;
+
+/* All of the columns of the area table. */
+static fhTableCol 
+area_table_cols[] = 
+{
+/*   Name     Description                              Units      Format                  Width  Dec places */
+   { "cell",  "Cell that correction area is in",       "",        FH_TABLE_FORMAT_INT,    2,     0 },
+   { "time",  "PON time when area created",            "secs",    FH_TABLE_FORMAT_INT,    10,    0 },
+   { "cx",    "Center x (position of max)",            "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "cy",    "Center y (position of max)",            "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "max",   "Max data value (above sky)",            "ADU",     FH_TABLE_FORMAT_INT,    5,     0 },
+   { "y0",    "y origin for fit (stamp sy for stars)", "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "sx",    "Left corner of area",                   "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "sy",    "Bottom corner of area",                 "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "ex",    "Right corner of area",                  "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "ey",    "Top corner of area",                    "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "y0m",   "Min y value at sx",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "y0p",   "Max y value at sx",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "y1m",   "Min y value at ex",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "y1p",   "Max y value at ex",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "x0m",   "Min x value at sy",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "x0p",   "Max x value at sy",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "x1m",   "Min x value at ey",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "x1p",   "Max x value at ey",                     "pixels",  FH_TABLE_FORMAT_INT,    5,     0 },
+   { "func",  "What are we going to do about it?",     "",        FH_TABLE_FORMAT_INT,    3,     0 },
+   { "up",    "Trails up or down",                     "1=up 0=down", FH_TABLE_FORMAT_INT,1,     0 },
+   { "slope", "Slope of fit",                          "",        FH_TABLE_FORMAT_DOUBLE, 9,     6 },
+   { "nfit",  "Number of columns corrected",           "",        FH_TABLE_FORMAT_INT,    3,     0 },
+   { "sxfit", "Starting column for fit",              "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "exfit", "Ending column for fit",                "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+};
+
+/* Index of all of the columns in the fit table. */
+typedef enum
+{
+   FIT_TABLE_COL_CELL,
+   FIT_TABLE_COL_CX,
+   FIT_TABLE_COL_CY,
+   FIT_TABLE_COL_XFIT,
+   FIT_TABLE_COL_YFIT,
+   FIT_TABLE_COL_ZERO,
+   
+   /* Add new columns above this line. */
+   FIT_TABLE_NUM_COLS
+} FIT_TABLE_COL_T;
+
+/* All of the columns in the fit table. */
+static fhTableCol
+fit_table_cols[] = 
+{
+/*   Name     Description                              Units      Format                  Width  Dec places */
+   { "cell",  "Cell that correction area is in",       "",        FH_TABLE_FORMAT_INT,    2,     0 },
+   { "cx",    "Center x (position of max)",            "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "cy",    "Center y (position of max)",            "pixels",  FH_TABLE_FORMAT_INT,    3,     0 },
+   { "xfit",  "x of each value of the start of correction", "pixels", FH_TABLE_FORMAT_INT,3,     0 },
+   { "yfit",  "x of each value of the start of correction", "pixels", FH_TABLE_FORMAT_INT,3,     0 },
+   { "zero",  "Zero of this fit",                      "",        FH_TABLE_FORMAT_DOUBLE, 8,     4 },
+};
+
+/* Structure describing area table. Stuff like the number of
+ * rows, the overall size, etc. are calculated at runtime. */
+static fhTable
+area_table = 
+{
+   .extname = DEFAULT_EXTNAME_AREA_TABLE,
+   .num_cols = AREA_TABLE_NUM_COLS,
+   .num_rows = 0, /* Don't know yet - need to rip through the data to figure this out. */
+   .cols = area_table_cols,
+};
+
+/* This is the fit table, in all its glory. */
+static fhTable
+fit_table = 
+{
+   .extname = DEFAULT_EXTNAME_FIT_TABLE,
+   .num_cols = FIT_TABLE_NUM_COLS,
+   .num_rows = 0, /* Don't know yet - need to rip through the data to figure this out. */
+   .cols = fit_table_cols,
+};
+
+/*
+ * Finds existing fit and area tables in a FITS file.
+ *
+ * Given the primary header of a FITS file, this function
+ * tries to find the area and fit table extensions. It does this
+ * by first looking at the header to see if the extension names
+ * of the tables are listed, and if so, looks for those by name.
+ * If that information is not available for some reason, the default
+ * names are used instead.
+ *
+ * Inputs:
+ *    phu - primary header of a FITS file.
+ *
+ * Outputs:
+ *      area_hu - header of area table stored here if found, NULL otherwise.
+ *    area_name - if non-NULL, this is a buffer where the name of the 
+ *                area table extension will be copied.
+ *       fit_hu - header of fit table stored here if found, NULL otherwise.
+ *     fit_name - if non-NULL, this is a buffer where the name of the 
+ *                fit table extension will be copied.
+ *
+ * This function returns nothing.
+ */
+static void
+find_existing_tables(HeaderUnit phu, 
+                     HeaderUnit * area_hu, char * area_name, 
+                     HeaderUnit * fit_hu, char * fit_name)
+{
+   char area_extname[FH_MAX_STRLEN+1];
+   char fit_extname[FH_MAX_STRLEN+1];
+
+   /* Does the FITS file provide the names of the burn tables?
+    * If so, use those, otherwise revert to the defaults. */
+   if(fh_get_str(phu, PHU_NAME_BURN_AREA, area_extname, sizeof(area_extname)) != FH_SUCCESS)
+   {
+      snprintf(area_extname, FH_MAX_STRLEN+1, DEFAULT_EXTNAME_AREA_TABLE);
+   }
+   if(fh_get_str(phu, PHU_NAME_BURN_FIT, fit_extname, sizeof(fit_extname)) != FH_SUCCESS)
+   {
+      snprintf(fit_extname, FH_MAX_STRLEN+1, DEFAULT_EXTNAME_FIT_TABLE);
+   }
+
+   *area_hu = fh_ehu_by_extname(phu, area_extname);
+   if(area_name)
+   {
+      if(*area_hu) strcpy(area_name, area_extname);
+      else area_extname[0] = '\0';
+   }
+   *fit_hu = fh_ehu_by_extname(phu, fit_extname);
+   if(fit_name)
+   {
+      if(*fit_hu) strcpy(fit_name, fit_extname);
+      else fit_name[0] = '\0';
+   }
+}
+
+/*
+ * Determines how many rows should be in the area and fit tables.
+ *
+ * Given all of the burn information for the whole OTA, this function
+ * figures out how many rows are needed in the area and fit tables to
+ * accomodate all of that info.
+ *
+ * Inputs:
+ *    cell - array of 64 CELL structures, one for each cell in the OTA.
+ *
+ * Outputs:
+ *    num_areas_found - the number of burn areas for the OTA stored here.
+ *     num_fits_found - the number of fits across all of the burn
+ *                      areas for the OTA stored here.
+ *
+ * This function returns FH_SUCCESS if the row was successfully 
+ * written.
+ */
+static void
+calculate_rows(CELL *cell, int * num_areas_found, int * num_fits_found)
+{
+   int j, k;
+   int num_areas = 0;
+   int num_fits = 0;
+
+   for(j=0; j<MAXCELL; j++) 
+   {
+      /* First: patched up persists */
+      for(k=0; k<cell[j].npersist; k++) 
+      {
+         if(cell[j].persist[k].fiterr) continue;
+         if(cell[j].persist[k].nfit <= 0) continue;
+         num_areas++;
+         num_fits += cell[j].persist[k].nfit;
+      }
+
+      /* Second: new burns */
+      for(k=0; k<cell[j].nburn; k++) 
+      {
+	 if(!cell[j].burn[k].burned) continue;
+	 if(cell[j].burn[k].fiterr && 
+	    cell[j].burn[k].fiterr != FIT_TOP_ERROR) continue;
+	 if(cell[j].burn[k].nfit <= 0) continue;
+         num_areas++;
+         num_fits += cell[j].burn[k].nfit;
+      }
+   }
+
+   *num_areas_found = num_areas;
+   *num_fits_found = num_fits;
+}
+
+/*
+ * Writes one row of the area table.
+ *
+ * Given all of the data for a row of the area table, this function writes
+ * the row in the table body.
+ *
+ * Inputs:
+ *       hu - header of fit table extension.
+ *     data - points to buffer for table body where data will be written.
+ *    table - describes structure of the table.
+ *      row - row of table to be written.
+ *     area - all properties of the area.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the row was successfully 
+ * written.
+ */
+static fh_result
+write_area_row(HeaderUnit hu, void * data, fhTable * table, int row, OBJBOX * area)
+{
+   if((fh_table_write_value(table, data, row, AREA_TABLE_COL_CELL,  &(area->cell))  != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_TIME,  &(area->time))  != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_CX,    &(area->cx))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_CY ,   &(area->cy))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_MAX,   &(area->max))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_Y0,    &(area->y0))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_SX,    &(area->sx))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_SY,    &(area->sy))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_EX,    &(area->ex))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_EY,    &(area->ey))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_Y0M,   &(area->y0m))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_Y0P,   &(area->y0p))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_Y1M,   &(area->y1m))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_Y1P,   &(area->y1p))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_X0M,   &(area->x0m))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_X0P,   &(area->x0p))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_X1M,   &(area->x1m))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_X1P,   &(area->x1p))   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_FUNC,  &(area->func))  != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_UP,    &(area->up))    != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_SLOPE, &(area->slope)) != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_NFIT,  &(area->nfit))  != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_SXFIT, &(area->sxfit)) != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, AREA_TABLE_COL_EXFIT, &(area->exfit)) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Error writing data to row %d of area table.\n", row);
+      return FH_BAD_VALUE;
+   }
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Writes area data to a FITS file.
+ *
+ * This function iterates through all of the burn areas in all cells 
+ * and populates the body of the area table in the provided buffer. 
+ *
+ * Inputs:
+ *       hu - header of fit table extension.
+ *     data - points to buffer for table body where data will be written.
+ *    table - describes structure of the table.
+ *     cell - array of 64 CELL structures for all cells of an OTA. Burn
+ *            data from these structures will be written to the table.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the table was successfully 
+ * written.
+ */
+static fh_result
+write_area_table(HeaderUnit hu, void * data, fhTable * table, CELL * cell)
+{
+   int j, k;
+   int row=0;
+   fh_result result;
+
+   for(j=0; j<MAXCELL; j++) 
+   {
+      /* First: patched up persists */
+      for(k=0; k<cell[j].npersist; k++) 
+      {
+         if(cell[j].persist[k].fiterr) continue;
+         if(cell[j].persist[k].nfit <= 0) continue;     
+
+         result = write_area_row(hu, data, table, row++, &(cell[j].persist[k]));
+         if(result != FH_SUCCESS) return result;
+      }
+
+      /* Second: new burns */
+      for(k=0; k<cell[j].nburn; k++) 
+      {
+	 if(!cell[j].burn[k].burned) continue;
+	 if(cell[j].burn[k].fiterr && 
+	    cell[j].burn[k].fiterr != FIT_TOP_ERROR) continue;
+	 if(cell[j].burn[k].nfit <= 0) continue;
+
+         result = write_area_row(hu, data, table, row++, &(cell[j].burn[k]));
+         if(result != FH_SUCCESS) return result;
+      }
+   }
+
+   return FH_SUCCESS;
+}
+
+
+/*
+ * Writes one row of the fit table.
+ *
+ * Given all of the data for a row of the fit table, this function writes
+ * the row in the table body.
+ *
+ * Inputs:
+ *       hu - header of fit table extension.
+ *     data - points to buffer for table body where data will be written.
+ *    table - describes structure of the table.
+ *      row - row of table to be written.
+ *     cell - cell number for fit.
+ *       cx - center x of fit.
+ *       cy - center y of fit.
+ *     xfit - x of each value of correction start.
+ *     yfit - y of each value of correction start.
+ *     zero - zero of this fit.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the row was successfully 
+ * written.
+ */
+static fh_result
+write_fit_row(HeaderUnit hu, void * data, fhTable * table, int row, 
+              int cell, int cx, int cy, int xfit, int yfit, double zero)
+{
+   if((fh_table_write_value(table, data, row, FIT_TABLE_COL_CELL, &cell) != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, FIT_TABLE_COL_CX,   &cx)   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, FIT_TABLE_COL_CY,   &cy)   != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, FIT_TABLE_COL_XFIT, &xfit) != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, FIT_TABLE_COL_YFIT, &yfit) != FH_SUCCESS) ||
+      (fh_table_write_value(table, data, row, FIT_TABLE_COL_ZERO, &zero) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Error writing data to row %d of fit table.\n", row);
+      return FH_BAD_VALUE;
+   }
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Writes fit data to a FITS file.
+ *
+ * This function iterates through all of the burn areas in all cells 
+ * and populates the body of the fit table in the provided buffer. 
+ *
+ * Inputs:
+ *       hu - header of fit table extension.
+ *     data - points to buffer for table body where data will be written.
+ *    table - describes structure of the table.
+ *     cell - array of 64 CELL structures for all cells of an OTA. Burn
+ *            data from these structures will be written to the table.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the table was successfully 
+ * written.
+ */
+static fh_result
+write_fit_table(HeaderUnit hu, void * data, fhTable * table, CELL * cell)
+{
+   int i, j, k;
+   int row=0;
+   fh_result result;
+
+   for(j=0; j<MAXCELL; j++) 
+   {
+      /* First: patched up persists */
+      for(k=0; k<cell[j].npersist; k++) 
+      {
+         if(cell[j].persist[k].fiterr) continue;
+	 for(i=0; i<cell[j].persist[k].nfit; i++) 
+         {
+            result = write_fit_row(hu, data, table, row++,
+                                   cell[j].cell, cell[j].persist[k].cx, cell[j].persist[k].cy,
+                                   cell[j].persist[k].xfit[i], cell[j].persist[k].yfit[i], 
+                                   cell[j].persist[k].zero[i]);
+            if(result != FH_SUCCESS) return result;
+	 }
+      }
+
+      /* Second: new burns */
+      for(k=0; k<cell[j].nburn; k++) 
+      {
+	 if(!cell[j].burn[k].burned) continue;
+	 if(cell[j].burn[k].fiterr && 
+	    cell[j].burn[k].fiterr != FIT_TOP_ERROR) continue;
+
+	 for(i=0; i<cell[j].burn[k].nfit; i++) 
+         {
+            result = write_fit_row(hu, data, table, row++,
+                                   cell[j].cell, cell[j].burn[k].cx, cell[j].burn[k].cy,
+                                   cell[j].burn[k].xfit[i], cell[j].burn[k].yfit[i], 
+                                   cell[j].burn[k].zero[i]);
+            if(result != FH_SUCCESS) return result;
+	 }
+      }
+   }
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Reads one row of the area table from a FITS file.
+ *
+ * Given information about an area table and a row, this function
+ * reads a single row of data from a buffer containing the table 
+ * body. The data is stored in boxbuf[row].
+ *
+ * Inputs:
+ *    table - describes structure of the table.
+ *     data - points to data from table.
+ *      row - the row to be read from the table.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the row was successfully 
+ * read.
+ */
+static fh_result
+read_area(fhTable * table, void * data, int row)
+{
+   int cell_num;
+
+   /* Get the cell number first for some sanity checking. */
+   if(fh_table_read_value(table, data, row, AREA_TABLE_COL_CELL, &cell_num) != FH_SUCCESS)
+   {
+      fprintf(stderr,
+              "error: Unable to get cell number from row %d of burn area table\n",
+              row);
+      return FH_BAD_VALUE;
+   }
+
+   /* Don't want to deal with gibberish. */
+   if((cell_num < 0) || (cell_num > MAXCELL))
+   {
+      fprintf(stderr, "error: illegal cell %d in area table row %d\n",
+              cell_num, row);
+      boxbuf[row].cell = -1;
+      boxbuf[row].nfit = 0;
+      return FH_BAD_VALUE;
+   }
+
+   boxbuf[row].cell = cell_num;
+
+   if((fh_table_read_value(table, data, row, AREA_TABLE_COL_TIME,  &(boxbuf[row].time)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_CX,    &(boxbuf[row].cx)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_CY,    &(boxbuf[row].cy)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_MAX,   &(boxbuf[row].max)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_Y0,    &(boxbuf[row].y0)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_SX,    &(boxbuf[row].sx)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_SY,    &(boxbuf[row].sy)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_EX,    &(boxbuf[row].ex)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_EY,    &(boxbuf[row].ey)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_Y0M,   &(boxbuf[row].y0m)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_Y0P,   &(boxbuf[row].y0p)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_Y1M,   &(boxbuf[row].y1m)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_Y1P,   &(boxbuf[row].y1p)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_X0M,   &(boxbuf[row].x0m)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_X0P,   &(boxbuf[row].x0p)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_X1M,   &(boxbuf[row].x1m)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_X1P,   &(boxbuf[row].x1p)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_FUNC,  &(boxbuf[row].func)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_UP,    &(boxbuf[row].up)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_SLOPE, &(boxbuf[row].slope)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_NFIT,  &(boxbuf[row].nfit)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_SXFIT, &(boxbuf[row].sxfit)) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, AREA_TABLE_COL_EXFIT, &(boxbuf[row].exfit)) != FH_SUCCESS))
+   {
+      fprintf(stderr,
+              "error: Error reading values from row %d of burn area table\n",
+              row);
+      boxbuf[row].nfit = 0;
+      return FH_BAD_VALUE;
+   }
+   
+   /* Make space for fits (to be read later). */
+   if(boxbuf[row].nfit > 0)
+   {
+      /* This shouldn't happen, but be nice and don't leak. */
+      if(boxbuf[row].zero) free(boxbuf[row].zero);
+      if(boxbuf[row].xfit) free(boxbuf[row].xfit);
+      if(boxbuf[row].yfit) free(boxbuf[row].yfit);
+
+      boxbuf[row].zero = (double *)calloc(boxbuf[row].nfit, sizeof(double));
+      boxbuf[row].xfit = (int *)calloc(boxbuf[row].nfit, sizeof(int));
+      boxbuf[row].yfit = (int *)calloc(boxbuf[row].nfit, sizeof(int));
+      boxbuf[row].fiterr = 0;
+   }
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Reads the area table from a FITS file.
+ *
+ * This function fills out boxbuf[] with all of the areas in the
+ * given table. 
+ *
+ * Inputs:
+ *       hu - header of the table extension for the area table.
+ *    table - describes structure of the table.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the tables were successfully 
+ * appended to the FITS file.
+ */
+static fh_result
+read_area_table(HeaderUnit hu, fhTable * table)
+{
+   fh_result result = FH_INVALID;
+   int i;
+   int row_chars, num_cols, num_rows;
+   void * data;
+
+   if((fh_get_int(hu, "NAXIS1",  &row_chars) != FH_SUCCESS) ||
+      (fh_get_int(hu, "NAXIS2",  &num_rows) != FH_SUCCESS) ||
+      (fh_get_int(hu, "TFIELDS", &num_cols) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Unable to find required keywords for area table dimensions\n");
+      return FH_NOT_FOUND;
+   }
+
+   /* Sanity check. */
+   if(num_cols != table->num_cols) 
+   {
+      fprintf(stderr, "error: %d-column area table found, expected %d cols.\n",
+              num_cols, table->num_cols);
+      return FH_BAD_VALUE;
+   }
+
+   if(num_rows > MAXBURN)
+   {
+      fprintf(stderr, 
+              "error: too many boxes in area table. Max is %d, got %d\n",
+              MAXBURN, num_rows);
+      return FH_BAD_VALUE;
+   }
+
+   table->num_rows = num_rows;
+   table->table_size = num_rows * row_chars;
+
+
+   if(!(data = malloc(table->table_size)))
+   {
+      fprintf(stderr, 
+              "error: Unable to allocate %d bytes for area table.\n",
+              table->table_size);
+      return FH_NO_MEMORY;
+   }
+      
+   if(fh_read_image(hu, fh_file_desc(hu), data, 
+                    table->table_size, FH_TYPESIZE_8) != FH_SUCCESS)
+   {
+      fprintf(stderr, "error: Unable to map area table body for reading.\n");
+      free(data);
+      return FH_INVALID;
+   }
+
+   for(i = 0; i < num_rows; i++)
+   {
+      if((result = read_area(table, data, i)) != FH_SUCCESS) break;
+   }
+
+   free(data);
+   return result;
+}
+
+/*
+ * Reads one row of the fit table from a FITS file.
+ *
+ * Given information about a fit table and a row, this function
+ * reads a single row of data from a buffer containing the table 
+ * body.
+ *
+ * Inputs:
+ *       hu - header of the table extension for the fit table.
+ *    table - describes structure of the table.
+ *     data - points to buffer of data from fit table.
+ *      row - the row to be read.
+ *
+ * Outputs:
+ *    cell - cell number stored here.
+ *      cx - center x from table row stored here.
+ *      cy - center y from table row stored here.
+ *    xfit - x of each value of correction start stored here.
+ *    yfit - y of each value of correction start stored here.
+ *    zero - zero of this fit stored here.
+ *
+ * This function returns FH_SUCCESS if the table row was read 
+ * successfully.
+ *
+ * Note: this function must not be called until after read_area_table()
+ *       has read out the burn areas across all cells and prepared
+ *       boxbuf[]. This function assumes that each area's fit
+ *       information has been filled out in boxbuf[], including 
+ *       allocating space for the fits. 
+ */
+static fh_result
+read_fit(HeaderUnit hu, fhTable * table, void * data, int row, 
+         int * cell, int * cx, int * cy, int * xfit, int * yfit, double * zero)
+{
+   if(fh_table_read_value(table, data, row, FIT_TABLE_COL_CELL, cell) != FH_SUCCESS)
+   {
+      fprintf(stderr,
+              "error: Unable to get cell number from row %d of fit table\n",
+              row);
+      return FH_BAD_VALUE;
+   }
+
+   if((*cell < 0) || (*cell > MAXCELL))
+   {
+      fprintf(stderr, "error: illegal cell %d in fit table row %d\n",
+              *cell, row);
+      
+      return FH_BAD_VALUE;
+   }
+
+   if((fh_table_read_value(table, data, row, FIT_TABLE_COL_CX,   cx) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, FIT_TABLE_COL_CY,   cy) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, FIT_TABLE_COL_XFIT, xfit) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, FIT_TABLE_COL_YFIT, yfit) != FH_SUCCESS) ||
+      (fh_table_read_value(table, data, row, FIT_TABLE_COL_ZERO, zero) != FH_SUCCESS))
+   {
+      fprintf(stderr,
+              "error: Error reading values from row %d of burn area table\n",
+              row);
+      return FH_BAD_VALUE;
+   }
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Reads the fit table from a FITS file.
+ *
+ * This function copies the data from a given fit table to boxbuf[].
+ * Once complete, the contents of boxbuf[] should be equivalent to
+ * if persist_read() had read from a text file.
+ *
+ * Inputs:
+ *       hu - header of the table extension for the fit table.
+ *    table - describes structure of the table.
+ *    num_areas - the number of areas that were previously read from
+ *                the area table and written to boxbuf.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the table was read 
+ * successfully.
+ *
+ * Note: this function must not be called until after read_area_table()
+ *       has read out the burn areas across all cells and prepared
+ *       boxbuf[]. This function assumes that each area's fit
+ *       information has been filled out in boxbuf[], including 
+ *       allocating space for the fits. 
+ */
+static fh_result
+read_fit_table(HeaderUnit hu, fhTable * table, int num_areas)
+{
+   fh_result result;
+   int area, i;
+   int row_chars, num_cols, num_rows;
+   void * data;
+   int fit_table_row;
+
+   int cell, cx, cy, xfit, yfit;
+   double zero;
+
+   if((fh_get_int(hu, "NAXIS1",  &row_chars) != FH_SUCCESS) ||
+      (fh_get_int(hu, "NAXIS2",  &num_rows) != FH_SUCCESS) ||
+      (fh_get_int(hu, "TFIELDS", &num_cols) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Unable to find required keywords for fit table dimensions\n");
+      return FH_NOT_FOUND;
+   }
+
+   /* Sanity check. */
+   if(num_cols != table->num_cols) 
+   {
+      fprintf(stderr, "error: %d-column fit table found, expected %d cols.\n",
+              num_cols, table->num_cols);
+      return FH_BAD_VALUE;
+   }
+
+   table->num_rows = num_rows;
+   table->table_size = num_rows * row_chars;
+   
+   /* Make space for the body of the table and copy it.
+    *
+    * %%% It would be preferable to just memory map the body of the
+    * table in the file itself, but I don't seem to be able to get 
+    * that to work for reads. */
+   if(!(data = malloc(table->table_size)))
+   {
+      fprintf(stderr, 
+              "error: Unable to allocate %d bytes for area table.\n",
+              table->table_size);
+      return FH_NO_MEMORY;
+   }
+   if(fh_read_image(hu, fh_file_desc(hu), data, table->table_size, FH_TYPESIZE_8) != FH_SUCCESS)
+   {
+      fprintf(stderr, "error: Unable to read fit table body.\n");
+      free(data);
+      return FH_INVALID;
+   }
+
+   /* For all areas, grab all of the fits for that area. This should 
+    * end up consuming the whole table. */
+   fit_table_row = 0;
+   for(area = 0; area < num_areas; area++)
+   {
+      for(i = 0; i < boxbuf[area].nfit; i++)
+      {
+         if((result = read_fit(hu, table, data, fit_table_row, 
+                               &cell, &cx, &cy, &xfit, &yfit, &zero)) != FH_SUCCESS)
+         {
+            free(data);
+            return result;
+         }
+         
+         /* This shouldn't happen, but just in case... */
+         if((cell != boxbuf[area].cell) || 
+            (cx != boxbuf[area].cx) ||
+            (cy != boxbuf[area].cy))
+         {
+            fprintf(stderr, 
+                    "error: Fit in table row %d does not match current area "
+                    "(table cell=%d cx=%d cy=%d vs. area cell=%d cx=%d cy=%d)\n",
+                    fit_table_row,
+                    cell, cx, cy, boxbuf[area].cell, boxbuf[area].cx, boxbuf[area].cy);
+            free(data);
+            return FH_BAD_VALUE;
+         }
+
+         boxbuf[area].xfit[i] = xfit;
+         boxbuf[area].yfit[i] = yfit;   
+         boxbuf[area].zero[i] = zero;      
+
+         /* Move on to the next row of the table. */
+         fit_table_row++;
+      }
+   }
+
+   free(data);
+   return FH_SUCCESS;
+}
+
+/*
+ * Retrieves burn information from a FITS file.
+ *
+ * This function reads the burn tables from an existing FITS file
+ * and builds up burntool's internal table of areas and fits from
+ * the information within. This function should be equivalent to
+ * persist_read(), except that it operates on a FITS file instead
+ * of text.
+ *
+ * Inputs:
+ *        cell - array of 64 CELL structures that describe all of the
+ *               burn info for the whole OTA. Burn info will be written
+ *               here.
+ *    filename - filename of FITS file to read from.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the tables were successfully 
+ * appended to the FITS file.
+ *
+ * Note: this function assumes that the passed file is a MEF and that
+ * any required sanity checks on the file have already been performed.
+ */
+fh_result
+persist_fits_read(CELL *cell, const char * filename)
+{
+   HeaderUnit phu;
+   HeaderUnit area_hu, fit_hu;
+   fh_result result;
+
+   if(!(phu = fh_create()))
+   {
+      fprintf(stderr, 
+              "error: unable to create header structure for FITS file \"%s\"\n",
+              filename);
+      return FH_NO_MEMORY;
+   }
+
+   /* Try to open the FITS file. */
+   if ((result = fh_file(phu, filename, FH_FILE_RDONLY)) != FH_SUCCESS)
+   {
+      fprintf(stderr, 
+              "error: unable to open FITS file \"%s\"\n",
+              filename);
+      fh_destroy(phu);
+      return result;
+   }
+
+   
+   /* Don't even try if this FITS file doesn't have any burn info in it. */
+   find_existing_tables(phu, &area_hu, NULL, &fit_hu, NULL);
+   if(!area_hu || !fit_hu) 
+   {
+      fprintf(stderr, 
+              "error: Unable to find persistence info in FITS file.\n");
+      fh_destroy(phu);
+      return FH_NOT_FOUND;
+   }
+
+   fh_table_init(&area_table);
+   fh_table_init(&fit_table);
+
+   fh_ehu_by_extname(phu, DEFAULT_EXTNAME_AREA_TABLE);
+   if((result = read_area_table(area_hu, &area_table)) != FH_SUCCESS)
+   {
+      free(area_table.strbuf);
+      free(fit_table.strbuf);
+      fh_destroy(phu);
+      return result;
+   }
+
+   fh_ehu_by_extname(phu, DEFAULT_EXTNAME_FIT_TABLE);
+   if((result = read_fit_table(fit_hu, &fit_table, area_table.num_rows)) != FH_SUCCESS)
+   {
+      free(area_table.strbuf);
+      free(fit_table.strbuf);
+      fh_destroy(phu);
+      return result;
+   }
+
+   /* Now copy over the boxes that were read to our array of cells. 
+      This section is ripped off from persist_read(). */
+   {
+      int i, k;
+      int nbox;
+
+/* Initialize the counts */
+      for(k=0; k<MAXCELL; k++) {
+         cell[k].npersist = 0;
+         cell[k].persist = NULL;
+      }
+
+/* Augment counts */
+      for(nbox = 0; nbox < area_table.num_rows; nbox++)
+      {
+         k = boxbuf[nbox].cell;
+         cell[k].npersist += 1;
+      }
+
+      
+/* Allocate some space for them */
+      for(k=0; k<MAXCELL; k++) {
+         if( (i=cell[k].npersist) > 0) {
+            cell[k].persist = (OBJBOX *)calloc(i, sizeof(OBJBOX));
+            cell[k].npersist = 0;
+         }
+      }
+/* Copy the results to the cells */
+      for(i=0; i<nbox; i++) {
+         k = boxbuf[i].cell;
+         memcpy(cell[k].persist+cell[k].npersist, boxbuf+i, sizeof(OBJBOX));
+         cell[k].npersist += 1;
+      }
+   }
+
+   fh_destroy(phu);
+   return FH_SUCCESS;
+}
+
+/*
+ * Writes burn information to a FITS file.
+ *
+ * This function writes all of the the burn information passed as 
+ * a pair of FITS table extensions - one table listing the various 
+ * areas across all of the cells in the OTA, and another indicating
+ * the various fits in those areas.
+ *
+ * Inputs:
+ *    cell - array of 64 CELL structures that describe all of the
+ *           burn info for the whole OTA.
+ *     phu - primary header for existing FITS file to be written to.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the tables were successfully 
+ * appended to the FITS file.
+ *
+ * Note: this function assumes that the passed file is a MEF and that
+ * any required sanity checks on the file have already been performed.
+ *
+ * Note: this function assumes that the burn information has been
+ * applied and sets the keyword defined by PHU_NAME_BURN_APPLIED
+ * to TRUE. 
+ */
+fh_result
+persist_fits_write(CELL *cell, HeaderUnit phu)
+{
+   int fd;
+   HeaderUnit area_hu, fit_hu;
+   void * data;
+   int nextend;
+
+   /* Don't even try if this FITS file already has burn info in it.
+    * It's a fair bit of work to try to strip an extension out of
+    * a FITS file. */
+   find_existing_tables(phu, &area_hu, NULL, &fit_hu, NULL);
+   if(area_hu || fit_hu) 
+   {
+      fprintf(stderr, 
+              "error: Unable to write correction info to FITS file. Correction FITS tables already exist.\n");
+      return FH_NO_SPACE;
+   }
+
+   /* Is there somehow no file associated with the primary header? */
+   if ((fd = fh_file_desc(phu)) == -1)
+   {
+      fprintf(stderr, "error: header passed has no associated file.\n");
+      return FH_INVALID;
+   }
+
+   /* We're going to append our tables to the end of the file, so go there now. */
+   if(lseek(fd, 0, SEEK_END) == (off_t)-1)
+   {
+      fprintf(stderr,
+              "error: Unable to seek to end of file.\n");
+      return FH_IN_ERRNO;
+   }
+
+   area_hu = fh_create();
+   fit_hu = fh_create();
+   if(!area_hu || !fit_hu) 
+   {
+      fprintf(stderr, 
+              "error: Unable to create headers for correction FITS tables.\n");
+      if(area_hu) fh_destroy(area_hu);
+      if(fit_hu) fh_destroy(fit_hu);
+      return FH_NO_MEMORY;
+   }
+
+   /* Find out how many rows are going to be in each table. */
+   calculate_rows(cell, &(area_table.num_rows), &(fit_table.num_rows));
+
+   /* Start with the area table:
+    * - Build up an extension header and write it to the file.
+    * - Make space in the FITS file for the table body.
+    * - Memory-map the area in the FITS file for writing.
+    * - Write the data to the table.
+    * - Unmap the file.
+    */
+   fh_link_ehu_to_phu(area_hu, phu);
+   if((fh_table_populate_header(area_hu, &area_table) != FH_SUCCESS) ||
+      (fh_write(area_hu, fd) != FH_SUCCESS) ||
+      (fh_reserve_padded_table(area_hu, fd) != FH_SUCCESS) ||
+      (fh_map_table(area_hu, &data, area_table.table_size) != FH_SUCCESS) ||
+      (write_area_table(area_hu, data, &area_table, cell) != FH_SUCCESS) ||
+      (fh_munmap_table(area_hu) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Error encountered writing area table to FITS file.\n");
+      fh_destroy(fit_hu);
+      free(area_table.strbuf);
+      free(fit_table.strbuf);
+      return FH_INVALID;
+   }
+
+   /* Now the fit table:
+    * - Build up an extension header and write it to the file.
+    * - Make space in the FITS file for the table body.
+    * - Memory-map the area in the FITS file for writing.
+    * - Write the data to the table.
+    */
+   fh_link_ehu_to_phu(fit_hu, phu);
+   if((fh_table_populate_header(fit_hu, &fit_table) != FH_SUCCESS) ||
+      (fh_write(fit_hu, fd) != FH_SUCCESS) ||
+      (fh_reserve_padded_table(fit_hu, fd) != FH_SUCCESS) ||
+      (fh_map_table(fit_hu, &data, fit_table.table_size) != FH_SUCCESS) ||
+      (write_fit_table(fit_hu, data, &fit_table, cell) != FH_SUCCESS) ||
+      (fh_munmap_table(fit_hu) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Error encountered writing area table to FITS file.\n");
+      free(area_table.strbuf);
+      free(fit_table.strbuf);
+      return FH_INVALID;
+   }
+
+   /* Finally, correct the primary header to account for the 
+    * new extensions. */
+   fh_get_int(phu, "NEXTEND", &nextend);
+   nextend += 2;
+   fh_set_int(phu, FH_AUTO, "NEXTEND", nextend, "Number of extensions");
+
+   /* Add info to the primary header indicating where to find the burn info. */
+   fh_set_str(phu, FH_AUTO, PHU_NAME_BURN_AREA, DEFAULT_EXTNAME_AREA_TABLE, PHU_COMMENT_BURN_AREA);
+   fh_set_str(phu, FH_AUTO, PHU_NAME_BURN_FIT, DEFAULT_EXTNAME_FIT_TABLE, PHU_COMMENT_BURN_FIT);
+   fh_set_bool(phu, FH_AUTO, PHU_NAME_BURN_APPLIED, FH_TRUE, PHU_COMMENT_BURN_APPLIED);  
+
+   fh_rewrite(phu);
+
+   /* Get shot of the temporary space associated with the table structures. */
+   free(area_table.strbuf);
+   free(fit_table.strbuf);
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Removes the burn tables from a FITS file.
+ *
+ * This function strips out the burn tables (if present) from a FITS
+ * file, writing a new file with a couple less extensions in it.
+ *
+ * Inputs:
+ *     phu_in - primary header for existing FITS file to be copied.
+ *    fileout - filename of new FITS file.
+ *
+ * Outputs:
+ *    None.
+ *
+ * This function returns FH_SUCCESS if the new file was successfully
+ * written.
+ */
+fh_result
+persist_fits_remove_tables(HeaderUnit phu_in, const char * fileout)
+{
+   HeaderUnit phu_out;
+
+   HeaderUnit ehu_area;
+   HeaderUnit ehu_fit;
+
+   int num_extensions;
+   int num_extensions_copied = 0;
+
+   int fd_out;
+   int i;
+   fh_bool burn_applied = FH_FALSE;
+
+   char area_extname[FH_MAX_STRLEN+1];
+   char fit_extname[FH_MAX_STRLEN+1];
+
+   if(fh_get_int(phu_in, "NEXTEND", &num_extensions) != FH_SUCCESS)
+   {
+      fprintf(stderr, "error: Unable to get NEXTEND from input primary FITS header.\n");
+      return FH_INVALID;
+   }
+
+   find_existing_tables(phu_in, &ehu_area, area_extname, &ehu_fit, fit_extname);
+   if(!ehu_area || !ehu_fit) 
+   {
+      fprintf(stderr, 
+              "error: Unable to find persistence info in FITS file.\n");
+      return FH_INVALID;
+   }
+
+   /* Print a warning if the input FITS file appears to have burn 
+    * areas applied and that the output fiel is about to have that
+    * info removed. */
+   fh_get_bool(phu_in, PHU_NAME_BURN_APPLIED, &burn_applied);
+   if(burn_applied == FH_TRUE)
+   {
+      fprintf(stderr, 
+              "warning: input FITS file has burns applied to it. "
+              "%s still has those burns applied and has no built-in "
+              "knowledge of how to undo them.\n", fileout);
+   }
+
+   if((fd_out = open(fileout, O_CREAT | O_RDWR, 0644)) < 0)
+   {
+      fprintf(stderr, "error: Failed to open \"%s\" for output.\n",
+              fileout);
+      exit(EXIT_FAILURE);
+   }
+
+   /* Copy the primary header. Note that fh_merge() doesn't
+    * preserve any reserved space in the header (in our case, a
+    * big block of identical COMMENT cards). For now, make sure
+    * there's still some free space in the header by reserving 
+    * an entire block. The downside to this is obviously that the
+    * header will look a little different than the one we're copying,
+    * but I can't figure any way around this right now. */
+   if(!(phu_out = fh_create()) ||
+      (fh_merge(phu_out, phu_in) != FH_SUCCESS) ||
+      (fh_reserve(phu_out, (2880/80)) != FH_SUCCESS) ||
+      (fh_write(phu_out, fd_out) != FH_SUCCESS))
+   {
+      fprintf(stderr, "error: Unable to copy primary header to \"%s\"\n",
+              fileout);
+      fh_destroy(phu_out);
+      close(fd_out);
+      return FH_INVALID;
+   }
+
+   /* Walk through all of the extensions in the input FITS. Copy everything
+    * but the tables of burn info. */
+   for (i = 1; i <= num_extensions; i++)
+   {
+      HeaderUnit ehu_in;
+      HeaderUnit ehu_out;
+      char extname[FH_MAX_STRLEN+1];
+
+      if(!(ehu_in = fh_ehu(phu_in, i)))
+      {
+         fprintf(stderr, "error: Unable to read extension %d from input FITS file\n", i);
+         fh_destroy(phu_out);
+         close(fd_out);
+         return FH_INVALID;
+      }
+      
+      if (fh_get_str(ehu_in, "EXTNAME", extname, sizeof(extname)) != FH_SUCCESS)
+      {
+         fprintf(stderr, "error: Unable to get EXTNAME from extension %d in input FITS file.\n", i); 
+         fh_destroy(phu_out);
+         close(fd_out);
+         return FH_INVALID;
+      }
+
+      /* No match? Copy this extension in its entirety. */
+      if(strcasecmp(extname, area_extname) && strcasecmp(extname, fit_extname))
+      {
+         ehu_out = fh_create();
+
+         /* Copy the extension's header. The same thing about
+          * reserved space with fh_merge() applies here. */
+         if((fh_merge(ehu_out, ehu_in) != FH_SUCCESS) ||
+            (fh_reserve(ehu_out, (2880/80)) != FH_SUCCESS))
+         {
+            fprintf(stderr, "error: Unable to copy extension %d to %s.\n", i, fileout); 
+            fh_destroy(phu_out);
+            close(fd_out);
+            return FH_INVALID;           
+         }
+         
+         /* Apparently this can never fail. It would be handy if it just 
+          * returned FH_SUCCESS so it could be included in a big block of
+          * checks. */
+         fh_link_ehu_to_phu(ehu_out, phu_out);
+
+         /* WRite the copy of our header and all of the data that follows. */
+         if((fh_write(ehu_out, fd_out) != FH_SUCCESS) ||
+            (fh_copy_padded_image(ehu_out, fd_out, fh_file_desc(ehu_in)) != FH_SUCCESS))
+         {
+            fprintf(stderr, "error: Unable to copy extension %d to %s.\n", i, fileout); 
+            fh_destroy(phu_out);
+            close(fd_out);
+            return FH_INVALID;           
+         }
+
+         num_extensions_copied++;
+      }
+   }
+
+   /* Tweak the primary header on the new file to reflect the fact 
+    * that we just removed some extensions. */
+   fh_set_int(phu_out, FH_AUTO, "NEXTEND", 
+                 num_extensions_copied, "Number of extensions");
+   fh_remove(phu_out, PHU_NAME_BURN_AREA);
+   fh_remove(phu_out, PHU_NAME_BURN_FIT);
+   fh_rewrite(phu_out);
+
+   fh_destroy(phu_in);
+   fh_destroy(phu_out);
+   close(fd_out);
+
+   return FH_SUCCESS;
+}
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persist_fits.h
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persist_fits.h	(revision 23924)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persist_fits.h	(revision 23924)
@@ -0,0 +1,34 @@
+/* -*- c-file-style: "Ellemtel" -*-
+ *
+ * persist_fits.h - definitions for reading and writing persistence info to FITS tables.
+ *
+ */
+
+#ifndef _INCLUDED_persist_fits_
+#define _INCLUDED_persist_fits_
+
+#include "fh/fh.h"
+#include "burntool.h"
+
+/* Keywords in primary header that give extension names of burn tables. */
+#define PHU_NAME_BURN_AREA       "BTOOLAR"
+#define PHU_NAME_BURN_FIT        "BTOOLFIT"
+
+/* Flag indicating whether burn correction has already been applied. */
+#define PHU_NAME_BURN_APPLIED    "BTOOLAPP"
+
+/* Comments on keywords - use these if you rewrite them for any reason. */
+#define PHU_COMMENT_BURN_AREA    "Name of extension containing burntool streak areas"
+#define PHU_COMMENT_BURN_FIT     "Name of extension containing burntool streak fits"
+#define PHU_COMMENT_BURN_APPLIED "[T=applied] Burn streaks applied to image data"
+
+fh_result
+persist_fits_read(CELL *cell, const char * filename);
+
+fh_result
+persist_fits_write(CELL *cell, HeaderUnit phu);
+
+fh_result
+persist_fits_remove_tables(HeaderUnit phu_in, const char * fileout);
+
+#endif /* _INCLUDED_persist_fits_ */
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persistfix.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persistfix.c	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persistfix.c	(revision 23924)
@@ -27,4 +27,12 @@
 /* Fix up all the persistence streaks */
    for(k=0; k<cell->npersist; k++) {
+
+/* Is this just a blasted area being carried for IPP? */
+      if((cell->persist)[k].func == BURN_BLASTED) {
+	 if(cell->time - (cell->persist)[k].time > EXPIRE_TRAIL_TIME) {
+	    (cell->persist)[k].fiterr = FIT_EXPIRED;
+	 }
+	 continue;
+      }
 
 /* Fit the trail */
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persistio.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persistio.c	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/persistio.c	(revision 23924)
@@ -233,5 +233,5 @@
    fprintf(fp,  "# Cell: %d  sky= %d   rms= %d   bias= %d\n", 
 	   cell[0].cell, cell[0].sky, cell[0].rms, cell[0].bias);
-   fprintf(fp, "#Cell time    cx  cy  max   sx  sy   ex  ey  y0m y0p y1m y1p x0m x0p x1m x1p     slope    zero\n");
+   fprintf(fp, "#Cell time    cx  cy  max   y0   sx  sy   ex  ey  y0m y0p y1m y1p x0m x0p x1m x1p F up    slope nfit sxf exf\n");
 
    for(j=0; j<MAXCELL; j++) {
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/psfstats.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/psfstats.c	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/psfstats.c	(revision 23924)
@@ -1,3 +1,3 @@
-/* psfstats.c - calculate some statistics about a PSF */
+/* dummy version of psfstats.c - disable use of the psf */
 #include <stdio.h>
 #include <stdlib.h>
@@ -12,11 +12,4 @@
 #include "psf/psf.h"
 
-//#define PSFSTATS		/* ignore psf stats so we can skip fortan */
-//#define TEST		/* Dump all the PSF info? */
-
-static unsigned short int *psfbuf=NULL;
-static int npsfbuf=0;
-#define PHONYSKY 1000
-
 /****************************************************************/
 /* psf_stats(): Tell us about this PSF star */
@@ -24,101 +17,4 @@
 		     double *fwhm, double *q)
 {
-
-#ifdef PSFSTATS
-
-   int i, j, r, r2, err, i0, j0;
-   double d, qxx, qxy, qyy, mx, my, sum;
-   PSF_PARAM psfout;      /* Results of fit */
-   PSF_EXTRA psfextra;    /* Extra results from fit */
-   double pi=4*atan(1.0);
-
-   if(nx*ny > npsfbuf) {
-      if(psfbuf != NULL) free(psfbuf);
-      psfbuf = (ushort *)calloc(nx*ny, sizeof(ushort));
-      npsfbuf = nx*ny;
-   }
-   for(i=0; i<nx*ny; i++) {
-      j = ((int)data[i]) - bias + PHONYSKY;
-      if(j >= 0 && j < 65535) psfbuf[i] = j;
-      else psfbuf[i] = 0;
-   }
-
-/* 2D PSF fit */
-   err = psf(nx, ny, psfbuf, PSF_2DIM, NULL, &psfout, &psfextra);
-
-#ifdef TEST
-   printf("err = %d\n", err);
-#endif
-
-   if(!err) {
-#ifdef TEST
-      printf("err = %d\n", err);
-      printf("bias = %d\n", bias);
-      printf("ix, iy = %d %d\n", psfout.ix, psfout.iy);
-      printf("x0, y0 = %.2f %.2f\n", psfout.x0, psfout.y0);
-      printf("fwhm = %.2f\n", psfout.fwhm);
-      printf("peak, bckgnd = %.1f %.1f\n", psfout.peak, psfout.bkgnd);
-      printf("flux, S/N = %.1f %.2f\n", psfout.flux, psfout.sn);
-      printf("weight = %.2f\n", psfout.weight);
-      printf("FWmaj, FWmin = %.2f %.2f\n", psfextra.majfw, psfextra.minfw);
-      printf("theta = %.1f\n", psfextra.thfw*180/pi);
-      printf("wpeak, wbkgnd = %.1f %.1f\n", psfextra.wpeak, psfextra.wbkgnd);
-      printf("dflux, dbckgnd = %.1f %.1f\n", psfextra.dflux, psfextra.dbkgnd);
-      printf("rmsbkgnd = %.1f\n", psfextra.rmsbkgnd);
-#endif
-      fwhm[0] = psfextra.majfw;
-      fwhm[1] = psfextra.minfw;
-      fwhm[2] = psfextra.thfw;
-
-/* Moments */
-      i0 = MIN(psfout.ix, nx-psfout.ix);
-      j0 = MIN(psfout.iy, ny-psfout.iy);
-   } else {
-      fwhm[0] = fwhm[1] = fwhm[2] = 0.0;
-      i0 = nx/2;
-      j0 = ny/2;
-   }
-
-   r = MIN(i0, j0) - 1;
-   sum = mx = my = qxx = qxy = qyy = 0.0;
-   for(j=j0-r; j<j0+r; j++) {
-      for(i=i0-r; i<i0+r; i++) {
-	 r2 = (i-i0)*(i-i0) + (j-j0)*(j-j0);
-	 if(r2 > r*r) continue;
-	 d = (double)data[i+j*nx] - bias;
-	 sum += d;
-	 mx += (i+0.5) * d;
-	 my += (j+0.5) * d;
-	 qxx += (i+0.5) * (i+0.5) * d;
-	 qxy += (i+0.5) * (j+0.5) * d;
-	 qyy += (j+0.5) * (j+0.5) * d;
-      }
-   }
-   if(sum > 0) {
-      mx /= sum;
-      my /= sum;
-      qxx = qxx / sum - mx*mx;
-      qxy = qxy / sum - mx*my;
-      qyy = qyy / sum - my*my;
-   } else {
-      q[0] = q[1] = q[2] = 0.0;
-   }
-
-   q[0] = (qxx + qyy) / 2;
-   q[1] = qxx - qyy;
-   q[2] = 2 * qxy;
-
-/* phi is the angular position of the OTA around the center [rad] */
-//   qtang = (qxx - qyy) * cos(2*phi) + 2 * qxy * sin(2*phi);
-
-#ifdef TEST
-   printf("Sum= %.1f mx= %.2f my= %.2f qxx= %.4f qxy= %.4f qyy= %.4f\n", 
-	  sum, mx, my, qxx, qxy, qyy);
-#endif
-
-# else
-   printf("psfstats ignored in this build\n");
-   return (1);
-#endif /* PSFSTATS */
-   return(0);
+   return(1);
 }
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/trailfit.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/trailfit.c	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/burntool/trailfit.c	(revision 23924)
@@ -148,8 +148,16 @@
    }
 
+/* Burn extends all the way to the top */
    if(up && y1 >= ny-1) {
       box->slope = -1.0;
-      box->nfit = xe - xs + 1;
       box->fiterr = FIT_TOP_ERROR;
+/* No fit here because the burn is to top, but the persist may be fitable */
+      if(box->sy > FIT_EDGE) {
+	 box->nfit = xe - xs + 1;
+/* However, if burn extends to bottom, persist will not be able to fit */
+      } else {
+	 box->nfit = 1;		/* Just enough to keep it alive */
+	 box->func = BURN_BLASTED;
+      }
       return(0);
    }
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/Makefile
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/Makefile	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/Makefile	(revision 23924)
@@ -10,8 +10,8 @@
 include ../Make.Common
 
-VERSION = 2.01
+VERSION = 2.03
 CCWARN += $(WERROR)
 CCDEFS += -DHAVE_FH_VALIDATE
-SRCS = fh.c
+SRCS = fh.c fh_table.c
 HDRS = fh.h fh_registry.h fh_registry.asm
 
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/fh.h
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/fh.h	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/fh.h	(revision 23924)
@@ -36,4 +36,7 @@
 #define FH_COUNT_CARDS "@FH_COUNT_CARDS@"  /* See also: fh_count_cards() */
 
+/* Size of buffer in fhTableCol structure for format string. */
+#define FH_TABLE_FORMAT_STR_LEN 20
+
 typedef enum /* fh_result -- Result Code for most functions in this library: */
 {  FH_SUCCESS = 0,
@@ -64,4 +67,42 @@
 } fh_bool;			/* Value for "logical" FITS cards */
 
+/* The possible field formats in an ASCII table extension. */
+typedef enum
+{
+   FH_TABLE_FORMAT_CHAR = 'A',   /* Designates a string field. */
+   FH_TABLE_FORMAT_INT = 'I',    /* Integer field. */
+   FH_TABLE_FORMAT_FLOAT = 'F',  /* Float field. */
+   FH_TABLE_FORMAT_DOUBLE = 'D'  /* Double field. */
+} fhTableFormat;
+
+typedef struct
+{
+  char * name;    /* Short string identifying this column's data. */ 
+  char * comment; /* Longer blurb about this column's data. */
+  char * units;   /* Units for column's data. */
+  fhTableFormat format; /* Type of data held in this column. */
+  int width;      /* Width of this column's data, in characters. */
+  int dec_places; /* For doubles, etc, the number of decimal places to write. */
+  /* Everything below this line is generated by fh_table_init(). These 
+   * do not need to be filled out by hand. */
+  int first_char_pos; /* Position of first character in this column. Calculated on the fly. */
+  char format_string[FH_TABLE_FORMAT_STR_LEN]; /* Format string for printf. */
+} fhTableCol;
+
+/* Describes the layout of an ASCII table. */
+typedef struct
+{
+  char * extname;    /* Name of table extension. */
+  int num_cols;      /* Number of rows in table. */
+  int num_rows;      /* Number of columns in table. */
+  fhTableCol * cols; /* Array of cols columns. */
+  /* Everything below this line is generated by fh_table_init(). These 
+   * do not need to be filled out by hand. */
+  int widest_col;    /* Width of widest column. */
+  char * strbuf;     /* Will point to buf of widest_col + 1 chars. */
+  int row_width;     /* Number of characters in a table tow. */
+  int table_size;    /* Total size of table data, in bytes. */
+} fhTable;
+
 typedef void* HeaderUnit; /* Handle to a list of FITS cards allocated by fh_create. */
 
@@ -345,3 +386,77 @@
 double fh_idx(HeaderUnit hu); /* idx of the last card returned by fh_next */
 fh_result fh_merge(HeaderUnit hu, const HeaderUnit source); /* source unchanged */
+
+/* ---------------------------------------------------------
+ * Reading and writing tables
+ * ---------------------------------------------------------
+ */
+
+fh_result
+fh_table_init(fhTable * table);
+/* Call once to initialise information about a given table. This will
+ * walk through all of the columns and fill out starting character
+ * positions and so forth, and will calculate the size of each row
+ * and the size of the table as a whole, and so forth. */
+
+fh_result
+fh_table_populate_header(HeaderUnit hu, fhTable * table);
+/* Sets up keywords for a table extension header, including all 
+ * of the column information, etc. This will initialise the table
+ * structure if necessary. This does not include any fh_write... 
+ * calls to write the header to the file. */
+
+fh_result
+fh_reserve_padded_table(HeaderUnit hu, int fd);
+/* Analogous to fh_reserve_padded_image() for tables. This reserves 
+ * space for the table based on the information in the passed extension
+ * header at the current location in the file. The header must therefore
+ * be populated prior to calling this function (use 
+ * fh_table_populate_header() to build a header from a table structure.
+ */
+
+fh_result
+fh_map_table(HeaderUnit hu, void** data, int size);
+fh_result
+fh_munmap_table(HeaderUnit hu);
+/* Analogous to fh_map_raw_image() and fh_munmap_image() for tables. 
+ * These calls give the calling program access to a memory mapped pointer
+ * to the data in the FITS file.  The data format is equivalent to what
+ * would be produced by fh_read_image() with typesize=FH_TYPESIZE_RAW.
+ * Whether or not the file can be modified through the memory-mapped
+ * pointer depends on whether fh_mode=FH_FILE_RDONLY or FH_FILE_RDWR
+ * in the call to fh_file() that opened the file.  Be sure to use
+ * fh_munmap_table() when done.
+ *
+ * `size' must match the exact number bytes of data which should be in
+ * the file, according to its NAXIS and BITPIX values, excluding padding.
+ *
+ * %%% TODO: FH_FILE_RDWR is completely unimplemented!
+ *
+ * %%% TODO: File locking is not handled properly yet.  For now, use
+ * FH_FILE_RDONLY_NOLOCK and FH_FILE_RDWR_NOLOCK, especially on MEF
+ * files where the locks are left in place until the file is closed
+ * otherwise!
+ */
+
+fh_result
+fh_table_read_value(fhTable * table, void * data,
+                    int row, int col, void * value);
+/* Reads a single value from an existing table at (row, column). 
+ * The table field is interpreted based on the format of that
+ * column in the table structure, and is passed back via the
+ * 'value' pointer. It is the caller's responsibility to ensure
+ * that 'value' points to a location with sufficient space for
+ * the data in the field, and that any casting to & from a void
+ * pointer is done correctly.
+ */
+
+fh_result
+fh_table_write_value(fhTable * table, void * data, 
+                     int row, int col, void * value);
+/* Similar to read_value() above, this function overwrites a field
+ * in a table at the given location.  The caller is repsonsible 
+ * for casting the location of the stored data to a void pointer. 
+ */
+
+
 #endif /* _INCLUDED_fh */
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/fh_table.c
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/fh_table.c	(revision 23924)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/libfh/fh_table.c	(revision 23924)
@@ -0,0 +1,476 @@
+/* -*- c-file-style: "Ellemtel" -*-
+ *
+ * fh_table - manage ASCII FITS tables as part of libfh.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fh.h"
+
+/* The FITS standard explicitly states the maximum number of 
+ * columns in a table. */
+#define MAX_COLS 999
+
+#define FH_RESERVE \
+"COMMENT  Reserved space.  This line can be used to add a new FITS card.         "
+/*
+ * Always place the END on the second line of a new 2880 block, so that
+ * there are enough blank lines after the END for expansion, but we
+ * don't require or put any blank lines before because some FITS
+ * implementations (libfh is one of them, and cfitsio too) will
+ * remove them.  Use a COMMENT instead, repeated until one occupies
+ * the first line of a new block.  Then the END will go after that.
+ * Anything which compresses and decompresses does not have to worry
+ * about keeping track of blank lines, since they are after the end.
+ * It will be forced to put back just as many as there were to begin
+ * with in order to pad the header again.
+ */
+static void
+pad_header(HeaderUnit hu)
+{
+   int blocks_orig = fh_header_blocks(hu);
+   double idx = 900000000;
+
+   do
+   {
+      fh_set_card(hu, idx++, FH_RESERVE);
+   } while (fh_header_blocks(hu) == blocks_orig);
+   fh_set_card(hu, idx++, FH_RESERVE);
+}
+
+/*
+ * Sets up table info.
+ *
+ * Inputs:
+ *    table - FITS table.
+ */
+fh_result
+fh_table_init(fhTable * table)
+{
+   int i;
+   int row_width = 0;
+
+   /* Already set up? */
+   if(table->strbuf) return FH_SUCCESS;
+
+   if((table->num_cols <= 0) || (table->num_cols > MAX_COLS))
+   {
+      fprintf(stderr, 
+              "error: %d-column table is invalid. Tables may only have 1..%d columns.\n",
+              table->num_cols, MAX_COLS);
+      return FH_INVALID;
+   }
+
+   table->widest_col = 0;
+   for(i = 0 ; i < table->num_cols; i++)
+   {
+      /* Widest column in table? */
+      if(table->cols[i].width > table->widest_col)
+      {
+         table->widest_col = table->cols[i].width;
+      }
+
+      /* Character positioning in a row in FITS starts from 1, not 0. */
+      table->cols[i].first_char_pos = row_width + 1;
+      row_width += table->cols[i].width;
+
+      /* Generate a format string that will be used later to 
+       * sprintf() values to the table. */
+      switch(table->cols[i].format)
+      {
+         case FH_TABLE_FORMAT_CHAR:
+            snprintf(table->cols[i].format_string, FH_TABLE_FORMAT_STR_LEN, 
+                     "%%%ds", table->cols[i].width);
+            break;
+            
+         case FH_TABLE_FORMAT_INT:
+            snprintf(table->cols[i].format_string, FH_TABLE_FORMAT_STR_LEN, 
+                     "%%%dd", table->cols[i].width);
+            break;
+            
+         case FH_TABLE_FORMAT_FLOAT:
+         case FH_TABLE_FORMAT_DOUBLE: /* Intentional fall-through. */
+            snprintf(table->cols[i].format_string, FH_TABLE_FORMAT_STR_LEN, 
+                     "%%%d.%df", table->cols[i].width, table->cols[i].dec_places);
+            break;
+            
+         default:
+            free(table);
+            fprintf(stderr, "error: column %d has unknown format specifier '%c'\n",
+                    i, table->cols[i].format);
+            return FH_BAD_VALUE;
+            break;
+      }
+   }
+
+   table->row_width = row_width;
+   table->table_size = row_width * table->num_rows;
+
+   /* Make a space guaranteed to be large enough to hold any
+    * of the fields, plus a null character on the end. This is 
+    * done so that we can use the string library to manipulate
+    * the contents of each field. */
+   if(!(table->strbuf = malloc(table->widest_col + 1))) 
+   {
+      fprintf(stderr, 
+              "error: Unable to malloc %d bytes for table field buffer.\n",
+              table->widest_col + 1);
+      return FH_NO_MEMORY;
+   }
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Writes essential FITS header information.
+ *
+ * Given a table and a FITS header, this funciton will write the necessary 
+ * keywords to make the FITS header describe an ASCII table.
+ *
+ * Inputs:
+ *       table - FITS table.
+ *          hu - the FITS header to be written.
+ *
+ * Outputs:
+ *    none.
+ *
+ * This function returns a PASS/FAIL indication that the header was 
+ * successfully populated.
+ *
+ * NOTE: this function currently assumes that the FITS table is being
+ * written to an extension header, not a primary. 
+ *
+ * NOTE: this function does not set keywords for the extension name
+ * or other keywords that are not explicitly required for an ASCII 
+ * table extension. It is the caller's responsibility to fill out
+ * these keywords as required.
+ */
+fh_result fh_table_populate_header(HeaderUnit hu, fhTable * table)
+{
+   char keyword[FH_NAME_SIZE + 1];
+   char value[FH_MAX_STRLEN + 1];
+   int i;
+   double idx;
+
+   /* Do some sanity checks first. */
+   if(!table || !table->cols || !table->num_cols || !table->num_rows)
+   {
+      fprintf(stderr, 
+              "error: Incomplete table info passed to fh_table_populate_header()\n");
+      return FH_INVALID;
+   }
+
+   if(fh_table_init(table) != FH_SUCCESS) return FH_BAD_VALUE;
+
+   fh_set_str(hu, 0.0, "XTENSION", "TABLE", "");
+   fh_set_int(hu, 1.0, "BITPIX", 8, "8-bit ASCII");
+   fh_set_int(hu, 2.0, "NAXIS", 2, "2D table of values");
+   fh_set_int(hu, 2.1, "NAXIS1", table->row_width, 
+              "Characters in each table row");
+   fh_set_int(hu, 2.2, "NAXIS2", table->num_rows, 
+              "Number of rows in table");
+   fh_set_int(hu, 3.0, "PCOUNT", 0, 
+              "Random parameters before each array in a group");
+   fh_set_int(hu, 3.2, "GCOUNT", 1, 
+              "Number of random groups");
+   fh_set_int(hu, 4.0, "TFIELDS", table->num_cols, 
+              "Number of columns in table");
+
+   /* Write the format specifiers for each column. */
+   for(i=0; i < table->num_cols; i++)
+   {
+      idx = 5.0 + i/10.;
+      int n = i + 1; /* FITS numbering scheme starts from 1. */
+      snprintf(keyword, FH_NAME_SIZE + 1, "TFORM%d", n);
+      if((table->cols[i].format == FH_TABLE_FORMAT_FLOAT) ||
+         (table->cols[i].format == FH_TABLE_FORMAT_DOUBLE))
+      {
+         snprintf(value, FH_MAX_STRLEN + 1, "%c%d.%d", 
+                  table->cols[i].format, table->cols[i].width,
+                  table->cols[i].dec_places);
+      }
+      else
+      {
+         snprintf(value, FH_MAX_STRLEN + 1, "%c%d", 
+                  table->cols[i].format, table->cols[i].width);
+      }
+      fh_set_str(hu, idx, keyword, value, 
+                 "FITS table field format");
+      idx += 0.01;
+
+      snprintf(keyword, FH_NAME_SIZE + 1, "TBCOL%d", n);
+      fh_set_int(hu, idx, keyword, table->cols[i].first_char_pos, 
+                 "First character of column");
+   }
+
+   /* Set the name of this extension. */
+   fh_set_str(hu, 6.0, "EXTNAME", table->extname, "Extension name");
+
+   /* The FITS standard suggests that other descriptive information 
+    * that describes each column must follow after all of the format
+    * information. Presumably this is because this information, while
+    * nice to have from a self-documenting perspective, isn't strictly
+    * necessary to interpret the actual table values. */
+   for(i=0; i < table->num_cols; i++)
+   {
+      idx = 7.0 + i/10.;
+      int n = i + 1; /* FITS numbering scheme starts from 1. */
+      snprintf(keyword, FH_NAME_SIZE + 1, "TTYPE%d", n);
+      fh_set_str(hu, idx, keyword, table->cols[i].name, 
+                 "Column name");
+      
+      snprintf(keyword, FH_NAME_SIZE + 1, "TUNIT%d", n);
+      idx += 0.01;
+      fh_set_str(hu, idx, keyword, table->cols[i].units, 
+                 "Column data units");
+      
+      snprintf(keyword, FH_NAME_SIZE + 1, "TBCOM%d", n);
+      idx += 0.01;
+      fh_set_str(hu, idx, keyword, table->cols[i].comment, 
+                 "Column description");
+
+   }
+   pad_header(hu);
+   return FH_SUCCESS;
+}
+
+/*
+ * Writes a field in a table.
+ *
+ * This function is used to write a single field to a table. Given
+ * a table, information about its columns, the row and column within 
+ * the table to be written and a value, the value will be written to 
+ * the table according to the formatting instructions for that column.
+ *
+ * The value is passed as a void* so that this one function can be used
+ * for any type of data that can be put into the table. The function
+ * uses the column information to decide how to handle the pointer. It is
+ * the caller's responsibility to keep their casts and pointers straight.
+ *
+ * Inputs:
+ *    table - FITS table.
+ *     data - buffer where FITS table is to be written.
+ *      row - the row in the table where the datum should be written.
+ *      col - the column in the table where the datum should be written.
+ *    value - points to the value to be written.
+ *
+ * Outputs:
+ *    none.
+ *
+ * This function returns a FH_SUCCESS if the value was successfully written.
+ */
+fh_result
+fh_table_write_value(fhTable * table, void * data, 
+                     int row, int col, void * value)
+{
+   char * ptr;
+
+   /* Do some sanity checks first. */
+   if(!table)
+   {
+      fprintf(stderr, 
+              "error: no FITS table passed to fh_table_write_value().\n");
+      return FH_INVALID;
+   }
+   if(!table->strbuf)
+   {
+      fprintf(stderr, 
+              "error: no field buffer in this table. Was fh_table_populate_header() called already?\n");
+      return FH_INVALID;
+   }
+   if((row >= table->num_rows) || (col >= table->num_cols))
+   {
+      fprintf(stderr, 
+              "error: attempted to write value beyond edge of FITS table (row %d col %d, table is %dx%d).\n",
+              row, col, table->num_rows, table->num_cols);
+      return FH_INVALID;
+   }
+   if(!value)
+   {
+      fprintf(stderr, "error: no value given for row %d, col %d of FITS table.\n", row, col);
+      return FH_INVALID;            
+   }
+   
+   /* Figure out where this data is going to go. */
+   ptr = (char *)data +
+      (row * table->row_width) +
+      table->cols[col].first_char_pos - 1;
+
+   switch(table->cols[col].format)
+   {
+      case FH_TABLE_FORMAT_CHAR:
+         snprintf(table->strbuf, table->cols[col].width + 1, 
+                  table->cols[col].format_string, (char *)value);
+         break;
+         
+      case FH_TABLE_FORMAT_INT:
+         snprintf(table->strbuf, table->cols[col].width + 1,
+                  table->cols[col].format_string, *((int *)value));
+         break;
+         
+      case FH_TABLE_FORMAT_FLOAT:
+         snprintf(table->strbuf, table->cols[col].width + 1, 
+                  table->cols[col].format_string, *((float *)value));
+         break;
+      case FH_TABLE_FORMAT_DOUBLE:
+         snprintf(table->strbuf, table->cols[col].width + 1, 
+                  table->cols[col].format_string, *((double *)value));
+         break;
+         
+      default:
+         fprintf(stderr, 
+                 "error: column %d has unknown format specifier '%c'\n",
+                 col, table->cols[col].format);
+         return FH_BAD_VALUE;
+         break;
+   }
+
+   /* Copy the field over to the table, minus the null character. */
+   memcpy(ptr, table->strbuf, table->cols[col].width);
+
+   return FH_SUCCESS;
+}
+
+/*
+ * Reads a field in a table.
+ *
+ * This function is used to retrieve a single field in a table. Given
+ * a table, information about its columns, the row and column within 
+ * the table to be written and a value, the value will be written to 
+ * the table according to the formatting instructions for that column.
+ *
+ * The value is passed as a void* so that this one function can be used
+ * for any type of data that can be put into the table. The function
+ * uses the column information to decide how to handle the pointer. It is
+ * the caller's responsibility to ensure that there is enough space on
+ * the end of the value pointer to store the value, and that the value,
+ * once written, will be interpreted properly (e.g. don't go passing
+ * a pointer to a double for an int field).
+ *
+ * Inputs:
+ *    table - FITS table.
+ *     data - buffer where FITS table is to be written.
+ *      row - the row in the table where the datum should be written.
+ *      col - the column in the table where the datum should be written.
+ *
+ * Outputs:
+ *    value - value in field will be written here.
+ *
+ * This function returns a FH_SUCCESS if the value was successfull read.
+ */
+fh_result
+fh_table_read_value(fhTable * table, void * data, 
+                    int row, int col, void * value)
+{
+   char * ptr;
+   char * endchar;
+
+   /* Do some sanity checks first. */
+   if(!table)
+   {
+      fprintf(stderr, 
+              "error: no FITS table passed to fh_table_read_value().\n");
+      return FH_INVALID;
+   }
+   if(!table->strbuf)
+   {
+      fprintf(stderr, 
+              "error: no field buffer in this table. Was fh_table_populate_header() called already?\n");
+      return FH_INVALID;
+   }
+   if((row >= table->num_rows) || (col >= table->num_cols))
+   {
+      fprintf(stderr, 
+              "error: attempted to read value beyond edge of FITS table (row %d col %d, table is %dx%d).\n",
+              row, col, table->num_rows, table->num_cols);
+      return FH_INVALID;
+   }
+   if(!value)
+   {
+      fprintf(stderr, "error: no value given for row %d, col %d of FITS table.\n", row, col);
+      return FH_INVALID;            
+   }
+   
+   /* Figure out where this value is going to come from. */
+   ptr = (char *)data +
+      (row * table->row_width) +
+      table->cols[col].first_char_pos - 1;
+   
+   /* Grab the value, and shove a null on the end so the string
+    * library can have its way with it. */
+   memcpy(table->strbuf, ptr, table->cols[col].width);
+   table->strbuf[table->cols[col].width] = '\0';
+
+   /* Interpret the data based on the column's reported type. */
+   switch(table->cols[col].format)
+   {
+      case FH_TABLE_FORMAT_CHAR:
+         strcpy((char *)value, table->strbuf);
+         break;
+         
+      case FH_TABLE_FORMAT_INT:
+         *((int *)value) = (int)strtol(table->strbuf, &endchar, 10);
+         if(*endchar != '\0')
+         {
+            fprintf(stderr,
+                    "error: Invalid value in table at row %d, col %d. "
+                    "Expected an int, got '%s'\n",
+                    row, col, table->strbuf);
+         }
+         break;
+         
+      case FH_TABLE_FORMAT_FLOAT:
+         *((float *)value) = strtod(table->strbuf, &endchar);
+         if(*endchar != '\0')
+         {
+            fprintf(stderr,
+                    "error: Invalid value in table at row %d, col %d. "
+                    "Expected a float, got '%s'\n",
+                    row, col, table->strbuf);
+         }
+         break;
+      case FH_TABLE_FORMAT_DOUBLE:
+         *((double *)value) = strtod(table->strbuf, &endchar);
+         if(*endchar != '\0')
+         {
+            fprintf(stderr,
+                    "error: Invalid value in table at row %d, col %d. "
+                    "Expected a double, got '%s'\n",
+                    row, col, table->strbuf);
+         }
+         break;
+         
+      default:
+         fprintf(stderr, 
+                 "error: column %d has unknown format specifier '%c'\n",
+                 col, table->cols[col].format);
+         return FH_BAD_VALUE;
+         break;
+   }
+
+   return FH_SUCCESS;
+}
+
+
+/* Not sure if we need to do anything more with these right now. At 
+ * the moment, these are purely cosmetic wrappers around their 
+ * respective image functions. */
+fh_result
+fh_reserve_padded_table(HeaderUnit hu, int fd)
+{
+   return fh_reserve_padded_image(hu, fd);
+}
+
+fh_result
+fh_map_table(HeaderUnit hu, void** data, int size)
+{
+   return fh_map_raw_image(hu, data, size);
+}
+
+fh_result
+fh_munmap_table(HeaderUnit hu)
+{
+   return fh_munmap_raw_image(hu);
+}
Index: /trunk/extsrc/gpcsw/gpcsrc/fits/libfhreg/gpc_telescope.h
===================================================================
--- /trunk/extsrc/gpcsw/gpcsrc/fits/libfhreg/gpc_telescope.h	(revision 23923)
+++ /trunk/extsrc/gpcsw/gpcsrc/fits/libfhreg/gpc_telescope.h	(revision 23924)
@@ -84,5 +84,4 @@
 MK_PFL(  522.5, ROT     ,6, "Telescope rotator angle (degrees)"               )
 MK_PFL(  522.6, POSANGLE,6, "Telescope position angle (degrees)"              )
-MK_PFL(  522.7, PA_DEBUG,6, "Telescope position angle (alternate value)"      )
 MK_PFL(  523.0, COMRA   ,6, "Commanded telescope Right Ascension (degrees)"   )
 MK_PFL(  523.1, COMDEC  ,6, "Commanded telescope Declination (degrees)"       )
Index: /trunk/extsrc/gpcsw/setuidinst
===================================================================
--- /trunk/extsrc/gpcsw/setuidinst	(revision 23924)
+++ /trunk/extsrc/gpcsw/setuidinst	(revision 23924)
@@ -0,0 +1,4 @@
+#!/bin/csh -f
+# the camera build tools call setuidinst, but we don't need it
+
+exit 0
