Index: /trunk/Ohana/Makefile.in
===================================================================
--- /trunk/Ohana/Makefile.in	(revision 37806)
+++ /trunk/Ohana/Makefile.in	(revision 37807)
@@ -34,4 +34,5 @@
 dvopsps     \
 elixir      \
+fakeastro   \
 gastro      \
 gastro2     \
Index: /trunk/Ohana/configure.tcsh
===================================================================
--- /trunk/Ohana/configure.tcsh	(revision 37806)
+++ /trunk/Ohana/configure.tcsh	(revision 37807)
@@ -240,4 +240,6 @@
 set needlibs   = "$needlibs X11"
 set needlibs   = "$needlibs pthread"
+set needlibs   = "$needlibs gsl"
+set needlibs   = "$needlibs gslcblas"
 set needlibs   = "$needlibs m"
 
@@ -269,4 +271,5 @@
 set needincs = "$needincs png.h"
 set needincs = "$needincs pthread.h"
+set needincs = "$needincs gsl/gsl_sf_legendre.h"
 set needincs = "$needincs readline/history.h"
 set needincs = "$needincs readline/readline.h"
Index: /trunk/Ohana/src/addstar/Makefile
===================================================================
--- /trunk/Ohana/src/addstar/Makefile	(revision 37806)
+++ /trunk/Ohana/src/addstar/Makefile	(revision 37807)
@@ -24,4 +24,8 @@
 sedstar      : $(BIN)/sedstar.$(ARCH)
 load2mass    : $(BIN)/load2mass.$(ARCH)
+loadstarpar  : $(BIN)/loadstarpar.$(ARCH)
+loadstarpar_client : $(BIN)/loadstarpar_client.$(ARCH)
+loadICRF  : $(BIN)/loadICRF.$(ARCH)
+loadICRF_client : $(BIN)/loadICRF_client.$(ARCH)
 loadwise     : $(BIN)/loadwise.$(ARCH)
 dumpskycells : $(BIN)/dumpskycells.$(ARCH)
@@ -31,7 +35,7 @@
 mkcmf        : $(BIN)/mkcmf.$(ARCH)
 
-all: addstar addstar_client sedstar load2mass skycells mkcmf loadwise loadsupercos dumpskycells findskycell
-
-INSTALL = addstar addstar_client sedstar load2mass skycells mkcmf loadwise loadsupercos dumpskycells findskycell
+all: addstar addstar_client sedstar load2mass loadstarpar loadstarpar_client loadICRF loadICRF_client skycells mkcmf loadwise loadsupercos dumpskycells findskycell
+
+INSTALL = addstar addstar_client sedstar load2mass loadstarpar loadstarpar_client loadICRF loadICRF_client skycells mkcmf loadwise loadsupercos dumpskycells findskycell
 
 # I need to fix the client/server version of addstar now that I have dropped Stars
@@ -241,4 +245,72 @@
 $(SRC)/psps_ids.$(ARCH).o
 
+LOAD-STARPAR = \
+$(SRC)/loadstarpar.$(ARCH).o \
+$(SRC)/ConfigInit.$(ARCH).o \
+$(SRC)/SetSignals.$(ARCH).o \
+$(SRC)/Shutdown.$(ARCH).o \
+$(SRC)/SkyRegionUtils.$(ARCH).o \
+$(SRC)/args_loadstarpar.$(ARCH).o \
+$(SRC)/find_matches_starpar.$(ARCH).o \
+$(SRC)/loadstarpar_catalog.$(ARCH).o \
+$(SRC)/loadstarpar_io.$(ARCH).o \
+$(SRC)/loadstarpar_make_subset.$(ARCH).o \
+$(SRC)/loadstarpar_readstars.$(ARCH).o \
+$(SRC)/loadstarpar_remote_hosts.$(ARCH).o \
+$(SRC)/loadstarpar_save_remote.$(ARCH).o \
+$(SRC)/loadstarpar_table.$(ARCH).o \
+$(SRC)/psps_ids.$(ARCH).o
+
+LOAD-STARPAR-CLIENT = \
+$(SRC)/loadstarpar_client.$(ARCH).o \
+$(SRC)/ConfigInit.$(ARCH).o \
+$(SRC)/SetSignals.$(ARCH).o \
+$(SRC)/Shutdown.$(ARCH).o \
+$(SRC)/SkyRegionUtils.$(ARCH).o \
+$(SRC)/args_loadstarpar.$(ARCH).o \
+$(SRC)/find_matches_starpar.$(ARCH).o \
+$(SRC)/loadstarpar_catalog.$(ARCH).o \
+$(SRC)/loadstarpar_io.$(ARCH).o \
+$(SRC)/loadstarpar_make_subset.$(ARCH).o \
+$(SRC)/loadstarpar_readstars.$(ARCH).o \
+$(SRC)/loadstarpar_remote_hosts.$(ARCH).o \
+$(SRC)/loadstarpar_save_remote.$(ARCH).o \
+$(SRC)/loadstarpar_table.$(ARCH).o \
+$(SRC)/psps_ids.$(ARCH).o
+
+LOAD-ICRF = \
+$(SRC)/loadICRF.$(ARCH).o \
+$(SRC)/ConfigInit.$(ARCH).o \
+$(SRC)/SetSignals.$(ARCH).o \
+$(SRC)/Shutdown.$(ARCH).o \
+$(SRC)/SkyRegionUtils.$(ARCH).o \
+$(SRC)/args_loadICRF.$(ARCH).o \
+$(SRC)/find_matches_ICRF.$(ARCH).o \
+$(SRC)/loadICRF_catalog.$(ARCH).o \
+$(SRC)/loadICRF_io.$(ARCH).o \
+$(SRC)/loadICRF_make_subset.$(ARCH).o \
+$(SRC)/loadICRF_readstars.$(ARCH).o \
+$(SRC)/loadICRF_remote_hosts.$(ARCH).o \
+$(SRC)/loadICRF_save_remote.$(ARCH).o \
+$(SRC)/loadICRF_table.$(ARCH).o \
+$(SRC)/psps_ids.$(ARCH).o
+
+LOAD-ICRF-CLIENT = \
+$(SRC)/loadICRF_client.$(ARCH).o \
+$(SRC)/ConfigInit.$(ARCH).o \
+$(SRC)/SetSignals.$(ARCH).o \
+$(SRC)/Shutdown.$(ARCH).o \
+$(SRC)/SkyRegionUtils.$(ARCH).o \
+$(SRC)/args_loadICRF.$(ARCH).o \
+$(SRC)/find_matches_ICRF.$(ARCH).o \
+$(SRC)/loadICRF_catalog.$(ARCH).o \
+$(SRC)/loadICRF_io.$(ARCH).o \
+$(SRC)/loadICRF_make_subset.$(ARCH).o \
+$(SRC)/loadICRF_readstars.$(ARCH).o \
+$(SRC)/loadICRF_remote_hosts.$(ARCH).o \
+$(SRC)/loadICRF_save_remote.$(ARCH).o \
+$(SRC)/loadICRF_table.$(ARCH).o \
+$(SRC)/psps_ids.$(ARCH).o
+
 LOAD-WISE = \
 $(SRC)/loadwise.$(ARCH).o \
@@ -309,15 +381,19 @@
 $(SRC)/Shutdown.$(ARCH).o 
 
-$(ADDSTARC)   	  : $(INC)/addstar.h
-$(ADDSTARD)   	  : $(INC)/addstar.h
-$(ADDSTART)   	  : $(INC)/addstar.h
-$(ADDSTAR)    	  : $(INC)/addstar.h
-$(ADDSTAR_CLIENT) : $(INC)/addstar.h
-$(SKYCELLS)    	  : $(INC)/addstar.h
-$(DUMPSKYCELLS)	  : $(INC)/addstar.h
-$(FINDSKYCELL)	  : $(INC)/addstar.h
-$(LOAD-2MASS)  	  : $(INC)/addstar.h $(INC)/2mass.h
-$(LOAD-WISE)   	  : $(INC)/addstar.h $(INC)/WISE.h
-$(MKCMF)       	  : $(INC)/mkcmf.h
+$(ADDSTARC)   	       : $(INC)/addstar.h
+$(ADDSTARD)   	       : $(INC)/addstar.h
+$(ADDSTART)   	       : $(INC)/addstar.h
+$(ADDSTAR)    	       : $(INC)/addstar.h
+$(ADDSTAR_CLIENT)      : $(INC)/addstar.h
+$(SKYCELLS)    	       : $(INC)/addstar.h
+$(DUMPSKYCELLS)	       : $(INC)/addstar.h
+$(FINDSKYCELL)	       : $(INC)/addstar.h
+$(LOAD-2MASS)  	       : $(INC)/addstar.h $(INC)/2mass.h
+$(LOAD-STARPAR)	       : $(INC)/addstar.h $(INC)/loadstarpar.h
+$(LOAD-STARPAR-CLIENT) : $(INC)/addstar.h $(INC)/loadstarpar.h
+$(LOAD-ICRF)	       : $(INC)/addstar.h $(INC)/loadICRF.h
+$(LOAD-ICRF-CLIENT)    : $(INC)/addstar.h $(INC)/loadICRF.h
+$(LOAD-WISE)   	       : $(INC)/addstar.h $(INC)/WISE.h
+$(MKCMF)       	       : $(INC)/mkcmf.h
 
 $(BIN)/addstar.$(ARCH) 	      : $(ADDSTAR)
@@ -330,4 +406,8 @@
 $(BIN)/load2mass.$(ARCH)      : $(LOAD-2MASS)
 $(BIN)/loadwise.$(ARCH)       : $(LOAD-WISE)
+$(BIN)/loadstarpar.$(ARCH)    : $(LOAD-STARPAR)
+$(BIN)/loadstarpar_client.$(ARCH) : $(LOAD-STARPAR-CLIENT)
+$(BIN)/loadICRF.$(ARCH)       : $(LOAD-ICRF)
+$(BIN)/loadICRF_client.$(ARCH): $(LOAD-ICRF-CLIENT)
 $(BIN)/loadsupercos.$(ARCH)   : $(LOAD-SUPERCOS)
 $(BIN)/skycells.$(ARCH)       : $(SKYCELLS)
Index: /trunk/Ohana/src/addstar/include/WISE.h
===================================================================
--- /trunk/Ohana/src/addstar/include/WISE.h	(revision 37806)
+++ /trunk/Ohana/src/addstar/include/WISE.h	(revision 37807)
@@ -21,9 +21,10 @@
 short WISE_W1, WISE_W2, WISE_W3, WISE_W4;
 
-int ALLSKY;
+enum {MODE_NONE, MODE_ALLWISE, MODE_ALLSKY, MODE_PRELIM};
+int MODE;
 
 AddstarClientOptions args_loadwise (int argc, char **argv, AddstarClientOptions options);
 
-int loadwise_rawdata (SkyList *skytable, char *filename, AddstarClientOptions options, int ALLSKY);
+int loadwise_rawdata (SkyList *skytable, char *filename, AddstarClientOptions options);
 
 int getWISE_setup ();
Index: /trunk/Ohana/src/addstar/include/addstar.h
===================================================================
--- /trunk/Ohana/src/addstar/include/addstar.h	(revision 37806)
+++ /trunk/Ohana/src/addstar/include/addstar.h	(revision 37807)
@@ -20,5 +20,5 @@
 # define IN_REGION(R,D) ( \
 ((D) >= region[0].Dmin) && ((D) < region[0].Dmax) && \
-((R) >= region[0].Rmin)  && ((R) < region[0].Rmax))
+((R) >= region[0].Rmin) && ((R) < region[0].Rmax))
 
 /* grab named photcode */
@@ -31,4 +31,6 @@
 # define dCOS(A)   ((double) cos ((double)RAD_DEG*A))
 # define dSIN(A)   ((double) sin ((double)RAD_DEG*A))
+
+# define myAbortF(FORMAT,...) { fprintf (stderr, FORMAT, __VA_ARGS__); abort(); }
 
 typedef struct {
@@ -326,4 +328,8 @@
 int strextend (char *input, char *format,...);
 
+void initMosaicCoords ();
+void saveMosaicCoords (Coords *input);
+
+
 /** 
     there is an inconsistency to be resolved: fixed structures (like Image)
Index: /trunk/Ohana/src/addstar/include/loadICRF.h
===================================================================
--- /trunk/Ohana/src/addstar/include/loadICRF.h	(revision 37807)
+++ /trunk/Ohana/src/addstar/include/loadICRF.h	(revision 37807)
@@ -0,0 +1,42 @@
+
+typedef struct {
+  double R, D;
+  Measure measure;
+  int found; // TRUE == matched to an input star
+  int flag; // TRUE == included in a subset
+} ICRF_Stars;
+
+int   HOST_ID;
+char *HOSTDIR;
+char *CPT_FILE;
+char *INPUT;
+
+AddstarClientOptions args_loadICRF (int *argc, char **argv, AddstarClientOptions options);
+AddstarClientOptions args_loadICRF_client (int *argc, char **argv, AddstarClientOptions options);
+
+int loadICRF_table (SkyList *skylistInput, HostTable *hosts, char *filename, AddstarClientOptions *options);
+
+ICRF_Stars *loadICRF_make_subset (ICRF_Stars *stars, int Nstars, int start, SkyRegion *region, int *nsubset);
+
+int loadICRF_save_remote (ICRF_Stars *stars, int Nstars, HostTable *hosts, SkyRegion *region, char *fullname, AddstarClientOptions *options);
+
+int save_remote_host (HostInfo *host);
+
+int init_remote_hosts ();
+int find_empty_slot ();
+int harvest_all ();
+int harvest_host ();
+
+int loadICRF_catalog (ICRF_Stars *stars, int Nstars, SkyRegion *region, char *filename, AddstarClientOptions *options);
+
+int galactic_to_celestial (double *R, double *D, double l, double b);
+
+int find_matches_ICRF (SkyRegion *region, ICRF_Stars *stars, int Nstars, Catalog *catalog, AddstarClientOptions *options);
+
+int loadICRF_save_stars (char *filename, ICRF_Stars *stars, int Nstars);
+ICRF_Stars *loadICRF_load_stars (char *filename, int *nstars);
+
+ICRF_Stars *loadICRF_readstars (char *filename, int *nstars);
+
+int loadICRF_sortStars (ICRF_Stars *stars, int Nstars);
+int InitICRF_Star (ICRF_Stars *star);
Index: /trunk/Ohana/src/addstar/include/loadstarpar.h
===================================================================
--- /trunk/Ohana/src/addstar/include/loadstarpar.h	(revision 37807)
+++ /trunk/Ohana/src/addstar/include/loadstarpar.h	(revision 37807)
@@ -0,0 +1,41 @@
+
+typedef struct {
+  double R, D;
+  StarPar starpar;
+  int flag; // in a subset?
+  int found; // assigned to an object?
+} StarPar_Stars;
+
+int   HOST_ID;
+char *HOSTDIR;
+char *CPT_FILE;
+char *INPUT;
+
+AddstarClientOptions args_loadstarpar (int argc, char **argv, AddstarClientOptions options);
+AddstarClientOptions args_loadstarpar_client (int argc, char **argv, AddstarClientOptions options);
+
+int loadstarpar_table (SkyList *skylistInput, HostTable *hosts, char *filename, AddstarClientOptions *options);
+
+StarPar_Stars *loadstarpar_make_subset (StarPar_Stars *stars, int Nstars, int start, SkyRegion *region, int *nsubset);
+
+int loadstarpar_save_remote (StarPar_Stars *stars, int Nstars, HostTable *hosts, SkyRegion *region, char *fullname, AddstarClientOptions *options);
+
+int save_remote_host (HostInfo *host);
+
+int init_remote_hosts ();
+int find_empty_slot ();
+int harvest_all ();
+int harvest_host ();
+
+int loadstarpar_catalog (StarPar_Stars *stars, int Nstars, SkyRegion *region, char *filename, AddstarClientOptions *options);
+
+int galactic_to_celestial (double *R, double *D, double l, double b);
+
+int find_matches_starpar (SkyRegion *region, StarPar_Stars *stars, int Nstars, Catalog *catalog, AddstarClientOptions *options);
+
+int loadstarpar_save_stars (char *filename, StarPar_Stars *stars, int Nstars);
+StarPar_Stars *loadstarpar_load_stars (char *filename, int *nstars);
+
+StarPar_Stars *loadstarpar_readstars (char *filename, int *nstars);
+
+int loadstarpar_sortStars (StarPar_Stars *stars, int Nstars);
Index: /trunk/Ohana/src/addstar/src/LoadData.c
===================================================================
--- /trunk/Ohana/src/addstar/src/LoadData.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/LoadData.c	(revision 37807)
@@ -33,4 +33,10 @@
   // we need to perform that analysis here
   GetZeroPointExposure (headers, headerSets, Nimages);
+
+  if (!options[0].mosaic) {
+    // we are requiring a single set of N x WRPs + 1 DIS per file set.
+    // NOTE: if the -mosaic was supplied, do not reset the mosaic coords here 
+    initMosaicCoords ();
+  }
 
   // now run through the images, interpret the headers and read the stars
Index: /trunk/Ohana/src/addstar/src/LoadDataPMM.c
===================================================================
--- /trunk/Ohana/src/addstar/src/LoadDataPMM.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/LoadDataPMM.c	(revision 37807)
@@ -193,5 +193,5 @@
 
     // XXX for now, we define a totally fake coordinate system centered on the plate center
-    strcpy (image[0].coords.ctype, "DEC--TAN");
+    InitCoords (&image[0].coords, "DEC--TAN");
     
     image[0].coords.crval1  = pmm_get_ra (RA);
@@ -203,12 +203,4 @@
     image[0].coords.crpix2 = 0.5*PLATE_NY;
     image[0].coords.cdelt1 = image[0].coords.cdelt2 = 0.9 / 3600.0;
-
-    image[0].coords.pc1_1 = 0.0;
-    image[0].coords.pc1_2 = 1.0;
-    image[0].coords.pc2_1 = 1.0;
-    image[0].coords.pc2_2 = 0.0;
-
-    image[0].coords.Npolyterms = 0;
-    memset (image[0].coords.polyterms, 0, 2*7*sizeof(float));
 
     image[0].NX = PLATE_NX;
Index: /trunk/Ohana/src/addstar/src/LoadStars.c
===================================================================
--- /trunk/Ohana/src/addstar/src/LoadStars.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/LoadStars.c	(revision 37807)
@@ -88,4 +88,9 @@
       exit (1);
     }
+    /* supplied photcode is incompatible with multi-chip images */
+    if ((NheaderSets > 1) && options[0].mosaic) {
+      fprintf (stderr, "ERROR: -mosaic cannot be supplied to multiple images\n");
+      exit (1);
+    }
 
     // if these are SDSS data, load with SDSS-specific wrapper
Index: /trunk/Ohana/src/addstar/src/NewImage.c
===================================================================
--- /trunk/Ohana/src/addstar/src/NewImage.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/NewImage.c	(revision 37807)
@@ -33,4 +33,5 @@
       goto reject;
     }
+    saveMosaicCoords (mosaic);
   }    
 
Index: /trunk/Ohana/src/addstar/src/ReadImageHeader.c
===================================================================
--- /trunk/Ohana/src/addstar/src/ReadImageHeader.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/ReadImageHeader.c	(revision 37807)
@@ -1,3 +1,15 @@
 # include "addstar.h"
+
+// for DIS/WRP sets, we need to save the DIS set and supply it to the WRP entries
+static Coords *mosaic = NULL;
+
+void initMosaicCoords () {
+  mosaic = NULL;
+}
+
+void saveMosaicCoords (Coords *input) {
+  ALLOCATE (mosaic, Coords, 1);
+  memcpy (mosaic, input, sizeof(Coords));
+}
 
 /* read an image header corresponding to a CMF / CMP data block */
@@ -54,5 +66,5 @@
   /* only load astrometry, NAXIS1,2, and time if this is a MOSAIC_PHU (ctype is ....-DIS) */
   if (!strcmp (&image[0].coords.ctype[4], "-DIS")) {
-    RegisterMosaic (&image[0].coords);
+    saveMosaicCoords (&image[0].coords);
     return (TRUE);
   }
@@ -79,8 +91,9 @@
   }
   if (!strcmp (&image[0].coords.ctype[4], "-WRP")) {
-    if (!isRegisteredMosaic()) {
+    if (!mosaic) {
       fprintf (stderr, "no mosaic for WRP image (use -mosaic)\n");
       return (FALSE);
     }
+    image[0].coords.mosaic = mosaic;
   } else {
     /* force image to lie in 0-360 range */
Index: /trunk/Ohana/src/addstar/src/ReadStarsSDSS.c
===================================================================
--- /trunk/Ohana/src/addstar/src/ReadStarsSDSS.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/ReadStarsSDSS.c	(revision 37807)
@@ -201,20 +201,10 @@
     
     // XXX for now, we define a totally fake coordinate system centered on the first listed star
-    strcpy (images[N].coords.ctype, "DEC--TAN");
-    
+    InitCoords (&images[N].coords, "DEC--TAN");
     images[N].coords.crval1 = stars[0].average.R;
     images[N].coords.crval2 = stars[0].average.D;
-    
     images[N].coords.crpix1 = stars[0].measure.Xccd;
     images[N].coords.crpix2 = stars[0].measure.Yccd;
     images[N].coords.cdelt1 = images[N].coords.cdelt2 = 0.4 / 3600.0;
-
-    images[N].coords.pc1_1 = 0.0;
-    images[N].coords.pc1_2 = 1.0;
-    images[N].coords.pc2_1 = 1.0;
-    images[N].coords.pc2_2 = 0.0;
-
-    images[N].coords.Npolyterms = 0;
-    memset (images[N].coords.polyterms, 0, 2*7*sizeof(float));
 
     images[N].NX = 2048;
Index: /trunk/Ohana/src/addstar/src/UpdateDatabase_Image.c
===================================================================
--- /trunk/Ohana/src/addstar/src/UpdateDatabase_Image.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/UpdateDatabase_Image.c	(revision 37807)
@@ -21,5 +21,4 @@
 
   /* find correpsonding regions for image */
-  RegisterMosaic (mosaic);
   skylist = NULL;
   newlist = NULL;
Index: /trunk/Ohana/src/addstar/src/addstar.c
===================================================================
--- /trunk/Ohana/src/addstar/src/addstar.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/addstar.c	(revision 37807)
@@ -132,5 +132,5 @@
       }
     }
-    MARKTIME ("load cpt: %f sec\n", dtime); RESETTIME; 
+    if (VERBOSE) MARKTIME ("load cpt: %f sec\n", dtime); RESETTIME; 
 
     // Naves_disk == 0 implies an empty catalog file
@@ -163,5 +163,5 @@
 	break;
     }
-    MARKTIME ("match stars: %f sec\n", dtime); RESETTIME; 
+    if (VERBOSE) MARKTIME ("match stars: %f sec\n", dtime); RESETTIME; 
 
     /* report total updated values */
@@ -183,5 +183,5 @@
     dvo_catalog_unlock (&catalog);
     dvo_catalog_free (&catalog);
-    MARKTIME ("save cpt: %f sec\n", dtime); RESETTIME; 
+    if (VERBOSE) MARKTIME ("save cpt: %f sec\n", dtime); RESETTIME; 
 
     if (options.mode == ADDSTAR_MODE_REFCAT) free (stars);
Index: /trunk/Ohana/src/addstar/src/args.c
===================================================================
--- /trunk/Ohana/src/addstar/src/args.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/args.c	(revision 37807)
@@ -175,5 +175,5 @@
       exit (1);
     }
-    RegisterMosaic (&MOSAIC);
+    saveMosaicCoords (&MOSAIC);
     remove_argument (N, &argc, argv);
     gfits_free_header (&header);
Index: /trunk/Ohana/src/addstar/src/args_client.c
===================================================================
--- /trunk/Ohana/src/addstar/src/args_client.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/args_client.c	(revision 37807)
@@ -83,5 +83,5 @@
       exit (1);
     }
-    RegisterMosaic (&MOSAIC);
+    saveMosaicCoords (&MOSAIC);
     remove_argument (N, &argc, argv);
     gfits_free_header (&header);
Index: /trunk/Ohana/src/addstar/src/args_loadICRF.c
===================================================================
--- /trunk/Ohana/src/addstar/src/args_loadICRF.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/args_loadICRF.c	(revision 37807)
@@ -0,0 +1,219 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+static void help (void);
+static void help_client (void);
+
+AddstarClientOptions args_loadICRF (int *argc, char **argv, AddstarClientOptions options) {
+  
+  int N;
+
+  /* check for help request */
+  if (get_argument (*argc, argv, "-help") ||
+      get_argument (*argc, argv, "-h")) {
+    help ();
+  }
+
+  /* basic mode: image, list, refcat */
+  options.mode = ADDSTAR_MODE_REFCAT;
+
+  /* we do not allow a subset to be extracted -- all or nothing, babe */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax= 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+
+  // XXX for the moment, make this selection manual.  it needs to be automatic 
+  // based on the state of the SkyTable
+  HOST_ID = 0;
+  PARALLEL = FALSE;
+  if ((N = get_argument (*argc, argv, "-parallel"))) {
+    PARALLEL = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  // this is a test mode : rather than launching the remote jobs and waiting for completion,
+  // relphot will simply list the remote command and wait for the user to signal completion
+  PARALLEL_MANUAL = FALSE;
+  if ((N = get_argument (*argc, argv, "-parallel-manual"))) {
+    PARALLEL = TRUE; // -parallel-manual implies -parallel
+    PARALLEL_MANUAL = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  // this is a test mode : rather than launching the relphot_client jobs remotely, they are 
+  // run in serial via 'system'
+  PARALLEL_SERIAL = FALSE;
+  if ((N = get_argument (*argc, argv, "-parallel-serial"))) {
+    if (PARALLEL_MANUAL) {
+      fprintf (stderr, "ERROR: cannot mix -parallel-manual and -parallel-serial\n");
+      exit (1);
+    }
+    PARALLEL = TRUE; // -parallel-serial implies -parallel
+    PARALLEL_SERIAL = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  /* only add to existing regions */
+  options.existing_regions = FALSE;
+  if ((N = get_argument (*argc, argv, "-existing-regions"))) {
+    options.existing_regions = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  /* replace measurement, don't duplicate (ref/cat only) */
+  options.replace = FALSE;
+  if ((N = get_argument (*argc, argv, "-replace"))) {
+    options.replace = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  /* extra error messages */
+  VERBOSE = FALSE;
+  if ((N = get_argument (*argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  /* other addstar options which cannot be used in loadICRF */
+  options.photcode = 0;
+  options.timeref = 0; 
+  options.mosaic = FALSE;
+  options.skip_missed = FALSE;
+  options.closest = FALSE;
+  options.nosort = FALSE;
+  options.update = FALSE;
+  options.only_images = FALSE;
+  options.calibrate = FALSE;
+  options.quality_airmass = FALSE;
+  ACCEPT_ASTROM = FALSE;
+  FORCE_READ = FALSE;
+  TEXTMODE = FALSE;
+  SUBPIX = FALSE;
+  DUMP = NULL;
+
+  if (*argc < 2) {
+    fprintf (stderr, "USAGE: loadICRF [options] (file) [..more files]\n");
+    exit (2);
+  }
+  return (options);
+}
+
+AddstarClientOptions args_loadICRF_client (int *argc, char **argv, AddstarClientOptions options) {
+  
+  int N;
+
+  /* check for help request */
+  if (get_argument (*argc, argv, "-help") ||
+      get_argument (*argc, argv, "-h")) {
+    help ();
+  }
+
+  PARALLEL = FALSE;
+  PARALLEL_MANUAL = FALSE;
+  PARALLEL_SERIAL = FALSE;
+
+  HOST_ID = 0;
+  if ((N = get_argument (*argc, argv, "-hostID"))) {
+    remove_argument (N, argc, argv);
+    HOST_ID = atoi (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!HOST_ID) help_client();
+
+  HOSTDIR = NULL;
+  if ((N = get_argument (*argc, argv, "-hostdir"))) {
+    remove_argument (N, argc, argv);
+    HOSTDIR = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!HOSTDIR) help_client();
+
+  CPT_FILE = NULL;
+  if ((N = get_argument (*argc, argv, "-cpt"))) {
+    remove_argument (N, argc, argv);
+    CPT_FILE = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!CPT_FILE) help_client();
+  
+  INPUT = NULL;
+  if ((N = get_argument (*argc, argv, "-input"))) {
+    remove_argument (N, argc, argv);
+    INPUT = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!INPUT) help_client();
+
+  /* we do not allow a subset to be extracted -- all or nothing, babe */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax= 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+
+  /* only add to existing objects */
+  options.only_match = FALSE;
+  if ((N = get_argument (*argc, argv, "-only-match"))) {
+    options.only_match = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  /* replace measurement, don't duplicate (ref/cat only) */
+  options.replace = FALSE;
+  if ((N = get_argument (*argc, argv, "-replace"))) {
+    options.replace = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  /* extra error messages */
+  VERBOSE = FALSE;
+  if ((N = get_argument (*argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  /* other addstar options which cannot be used in loadICRF */
+  options.photcode = 0;
+  options.timeref = 0; 
+  options.mosaic = FALSE;
+  options.skip_missed = FALSE;
+  options.closest = FALSE;
+  options.nosort = FALSE;
+  options.update = FALSE;
+  options.only_images = FALSE;
+  options.calibrate = FALSE;
+  options.quality_airmass = FALSE;
+  ACCEPT_ASTROM = FALSE;
+  FORCE_READ = FALSE;
+  TEXTMODE = FALSE;
+  SUBPIX = FALSE;
+  DUMP = NULL;
+
+  if (*argc != 1) {
+    fprintf (stderr, "USAGE: loadICRF_client -cpt (file) -input (file)\n");
+    exit (2);
+  }
+  return (options);
+}
+
+static void help () {
+
+  fprintf (stderr, "USAGE: loadICRF [options] (file) [..more files]\n");
+  fprintf (stderr, "  add data from ICRF list to DVO\n\n");
+
+  fprintf (stderr, "  optional flags:\n");
+  fprintf (stderr, "  -replace              	  : replace time/photcode measurements (no duplication)\n");
+  fprintf (stderr, "  -v                    	  : verbose mode\n");
+  fprintf (stderr, "  -help                 	  : this list\n");
+  fprintf (stderr, "  -h                    	  : this list\n\n");
+  exit (2);
+}
+
+static void help_client () {
+
+  fprintf (stderr, "USAGE: loadICRF_client -D CATDIR catdir -hostID ID -hostdir (dir) -cpt (filename) -input (input) [options]\n");
+  fprintf (stderr, "  add data from stellar parameter file to DVO\n\n");
+
+  fprintf (stderr, "  optional flags:\n");
+  fprintf (stderr, "  -replace              	  : replace time/photcode measurements (no duplication)\n");
+  fprintf (stderr, "  -v                    	  : verbose mode\n");
+  fprintf (stderr, "  -help                 	  : this list\n");
+  fprintf (stderr, "  -h                    	  : this list\n\n");
+  exit (2);
+}
Index: /trunk/Ohana/src/addstar/src/args_loadstarpar.c
===================================================================
--- /trunk/Ohana/src/addstar/src/args_loadstarpar.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/args_loadstarpar.c	(revision 37807)
@@ -0,0 +1,227 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+static void help (void);
+static void help_client (void);
+
+AddstarClientOptions args_loadstarpar (int argc, char **argv, AddstarClientOptions options) {
+  
+  int N;
+
+  /* check for help request */
+  if (get_argument (argc, argv, "-help") ||
+      get_argument (argc, argv, "-h")) {
+    help ();
+  }
+
+  /* basic mode: image, list, refcat */
+  options.mode = ADDSTAR_MODE_REFCAT;
+
+  /* we do not allow a subset to be extracted -- all or nothing, babe */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax= 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+
+  // XXX for the moment, make this selection manual.  it needs to be automatic 
+  // based on the state of the SkyTable
+  HOST_ID = 0;
+  PARALLEL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel"))) {
+    PARALLEL = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  // this is a test mode : rather than launching the remote jobs and waiting for completion,
+  // relphot will simply list the remote command and wait for the user to signal completion
+  PARALLEL_MANUAL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel-manual"))) {
+    PARALLEL = TRUE; // -parallel-manual implies -parallel
+    PARALLEL_MANUAL = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  // this is a test mode : rather than launching the relphot_client jobs remotely, they are 
+  // run in serial via 'system'
+  PARALLEL_SERIAL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel-serial"))) {
+    if (PARALLEL_MANUAL) {
+      fprintf (stderr, "ERROR: cannot mix -parallel-manual and -parallel-serial\n");
+      exit (1);
+    }
+    PARALLEL = TRUE; // -parallel-serial implies -parallel
+    PARALLEL_SERIAL = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  /* only add to existing regions */
+  options.existing_regions = FALSE;
+  if ((N = get_argument (argc, argv, "-existing-regions"))) {
+    options.existing_regions = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  /* only add to existing objects */
+  options.only_match = FALSE;
+  if ((N = get_argument (argc, argv, "-only-match"))) {
+    options.only_match = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  /* replace measurement, don't duplicate (ref/cat only) */
+  options.replace = FALSE;
+  if ((N = get_argument (argc, argv, "-replace"))) {
+    options.replace = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  /* extra error messages */
+  VERBOSE = FALSE;
+  if ((N = get_argument (argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  /* other addstar options which cannot be used in loadstarpar */
+  options.photcode = 0;
+  options.timeref = 0; 
+  options.mosaic = FALSE;
+  options.skip_missed = FALSE;
+  options.closest = FALSE;
+  options.nosort = FALSE;
+  options.update = FALSE;
+  options.only_images = FALSE;
+  options.calibrate = FALSE;
+  options.quality_airmass = FALSE;
+  ACCEPT_ASTROM = FALSE;
+  FORCE_READ = FALSE;
+  TEXTMODE = FALSE;
+  SUBPIX = FALSE;
+  DUMP = NULL;
+
+  if (argc < 2) {
+    fprintf (stderr, "USAGE: loadstarpar [options] (fitsfile) [..more files]\n");
+    exit (2);
+  }
+  return (options);
+}
+
+AddstarClientOptions args_loadstarpar_client (int argc, char **argv, AddstarClientOptions options) {
+  
+  int N;
+
+  /* check for help request */
+  if (get_argument (argc, argv, "-help") ||
+      get_argument (argc, argv, "-h")) {
+    help ();
+  }
+
+  PARALLEL = FALSE;
+  PARALLEL_MANUAL = FALSE;
+  PARALLEL_SERIAL = FALSE;
+
+  HOST_ID = 0;
+  if ((N = get_argument (argc, argv, "-hostID"))) {
+    remove_argument (N, &argc, argv);
+    HOST_ID = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!HOST_ID) help_client();
+
+  HOSTDIR = NULL;
+  if ((N = get_argument (argc, argv, "-hostdir"))) {
+    remove_argument (N, &argc, argv);
+    HOSTDIR = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!HOSTDIR) help_client();
+
+  CPT_FILE = NULL;
+  if ((N = get_argument (argc, argv, "-cpt"))) {
+    remove_argument (N, &argc, argv);
+    CPT_FILE = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!CPT_FILE) help_client();
+  
+  INPUT = NULL;
+  if ((N = get_argument (argc, argv, "-input"))) {
+    remove_argument (N, &argc, argv);
+    INPUT = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!INPUT) help_client();
+
+  /* we do not allow a subset to be extracted -- all or nothing, babe */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax= 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+
+  /* only add to existing objects */
+  options.only_match = FALSE;
+  if ((N = get_argument (argc, argv, "-only-match"))) {
+    options.only_match = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  /* replace measurement, don't duplicate (ref/cat only) */
+  options.replace = FALSE;
+  if ((N = get_argument (argc, argv, "-replace"))) {
+    options.replace = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  /* extra error messages */
+  VERBOSE = FALSE;
+  if ((N = get_argument (argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  /* other addstar options which cannot be used in loadstarpar */
+  options.photcode = 0;
+  options.timeref = 0; 
+  options.mosaic = FALSE;
+  options.skip_missed = FALSE;
+  options.closest = FALSE;
+  options.nosort = FALSE;
+  options.update = FALSE;
+  options.only_images = FALSE;
+  options.calibrate = FALSE;
+  options.quality_airmass = FALSE;
+  ACCEPT_ASTROM = FALSE;
+  FORCE_READ = FALSE;
+  TEXTMODE = FALSE;
+  SUBPIX = FALSE;
+  DUMP = NULL;
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: loadstarpar_client -cpt (file) -input (file)\n");
+    exit (2);
+  }
+  return (options);
+}
+
+static void help () {
+
+  fprintf (stderr, "USAGE: loadstarpar [options] (file) [..more files]\n");
+  fprintf (stderr, "  add data from stellar parameter file to DVO\n\n");
+
+  fprintf (stderr, "  optional flags:\n");
+  fprintf (stderr, "  -only-match           	  : only add measurements to existing objects\n");
+  fprintf (stderr, "  -replace              	  : replace time/photcode measurements (no duplication)\n");
+  fprintf (stderr, "  -v                    	  : verbose mode\n");
+  fprintf (stderr, "  -help                 	  : this list\n");
+  fprintf (stderr, "  -h                    	  : this list\n\n");
+  exit (2);
+}
+
+static void help_client () {
+
+  fprintf (stderr, "USAGE: loadstarpar_client -D CATDIR catdir -hostID ID -hostdir (dir) -cpt (filename) -input (input) [options]\n");
+  fprintf (stderr, "  add data from stellar parameter file to DVO\n\n");
+
+  fprintf (stderr, "  optional flags:\n");
+  fprintf (stderr, "  -only-match           	  : only add measurements to existing objects\n");
+  fprintf (stderr, "  -replace              	  : replace time/photcode measurements (no duplication)\n");
+  fprintf (stderr, "  -v                    	  : verbose mode\n");
+  fprintf (stderr, "  -help                 	  : this list\n");
+  fprintf (stderr, "  -h                    	  : this list\n\n");
+  exit (2);
+}
Index: /trunk/Ohana/src/addstar/src/args_loadwise.c
===================================================================
--- /trunk/Ohana/src/addstar/src/args_loadwise.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/args_loadwise.c	(revision 37807)
@@ -53,8 +53,19 @@
   }
 
+enum {MODE_NONE, MODE_ALLWISE, MODE_ALLSKY, MODE_PRELIM};
+int MODE;
+
   /* load the prelim data dump */
-  ALLSKY = TRUE;
-  if ((N = get_argument (argc, argv, "-prelim"))) {
-    ALLSKY = FALSE;
+  MODE = MODE_ALLWISE;
+  if ((N = get_argument (argc, argv, "-mode"))) {
+    MODE = MODE_NONE;
+    remove_argument (N, &argc, argv);
+    if (!strcasecmp(argv[N], "allwise")) MODE = MODE_ALLWISE;
+    if (!strcasecmp(argv[N], "allsky"))  MODE = MODE_ALLSKY;
+    if (!strcasecmp(argv[N], "prelim"))  MODE = MODE_PRELIM;
+    if (MODE == MODE_NONE) {
+      fprintf (stderr, "invalid mode: %s\n", argv[N]);
+      exit (2);
+    }
     remove_argument (N, &argc, argv);
   }
Index: /trunk/Ohana/src/addstar/src/build_links.c
===================================================================
--- /trunk/Ohana/src/addstar/src/build_links.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/build_links.c	(revision 37807)
@@ -1,5 +1,3 @@
 # include "addstar.h"
-
-# define myAbortF(FORMAT,...) { fprintf (stderr, FORMAT, __VA_ARGS__); abort(); }
 
 /* 
Index: /trunk/Ohana/src/addstar/src/coord_transforms.c
===================================================================
--- /trunk/Ohana/src/addstar/src/coord_transforms.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/coord_transforms.c	(revision 37807)
@@ -0,0 +1,76 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+// values used by libdvo/coord_systems (from Liu et al 2011, A&A 526, A16):
+// Liu etal and Reid etal disagree at the 0.3 arcsec level
+// transform->phi =  62.8717488056*RAD_DEG;
+// transform->Xo  =  32.9319185700*RAD_DEG;
+// transform->xo  = 282.8594812080;
+
+int galactic_to_celestial (double *R, double *D, double l, double b) {
+
+  fprintf (stderr, "define me...");
+  abort();
+}
+
+int celestial_to_galactic (double *l, double *b, double R, double D) {
+
+_angp = np.radians(192.859508333) #  12h 51m 26.282s (J2000)
+_dngp = np.radians(27.128336111)  # +27d 07' 42.01" (J2000) 
+_l0   = np.radians(32.932)
+_ce   = np.cos(_dngp)
+_se   = np.sin(_dngp)
+
+
+
+}
+
+
+
+
+/*** python version from LSD (Mario Juric)
+
+# Appendix of Reid et al. (http://adsabs.harvard.edu/cgi-bin/bib_query?2004ApJ...616..872R)
+# This convention is also used by LAMBDA/WMAP (http://lambda.gsfc.nasa.gov/toolbox/tb_coordconv.cfm)
+_angp = np.radians(192.859508333) #  12h 51m 26.282s (J2000)
+_dngp = np.radians(27.128336111)  # +27d 07' 42.01" (J2000) 
+_l0   = np.radians(32.932)
+_ce   = np.cos(_dngp)
+_se   = np.sin(_dngp)
+
+def equgal(ra, dec):
+	ra = np.radians(ra)
+	dec = np.radians(dec)
+
+	cd, sd = np.cos(dec), np.sin(dec)
+	ca, sa = np.cos(ra - _angp), np.sin(ra - _angp)
+
+	sb = cd*_ce*ca + sd*_se
+	l = np.arctan2(sd - sb*_se, cd*sa*_ce) + _l0
+	b = np.arcsin(sb)
+
+	l = np.where(l < 0, l + 2.*np.pi, l)
+
+	l = np.degrees(l)
+	b = np.degrees(b)
+
+	return NamedList(('l', l), ('b', b))
+
+def galequ(l, b):
+	l = np.radians(l)
+	b = np.radians(b)
+
+	cb, sb = np.cos(b), np.sin(b)
+	cl, sl = np.cos(l-_l0), np.sin(l-_l0)
+
+	ra  = np.arctan2(cb*cl, sb*_ce-cb*_se*sl) + _angp
+	dec = np.arcsin(cb*_ce*sl + sb*_se)
+
+	ra = np.where(ra < 0, ra + 2.*np.pi, ra)
+
+	ra = np.degrees(ra)
+	dec = np.degrees(dec)
+
+	return NamedList(('ra', ra), ('dec', dec))
+
+ ***/
Index: /trunk/Ohana/src/addstar/src/fakeimage.c
===================================================================
--- /trunk/Ohana/src/addstar/src/fakeimage.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/fakeimage.c	(revision 37807)
@@ -3,5 +3,5 @@
 Image *fakeimage (char *rootname, off_t *Nimage, int photcode) {
 
-  int i, j, Nx, Ny, Nchips;
+  int i, Nx, Ny, Nchips;
   double pltscale, pixscale;
   double Rmin, Rmax, Dmin, Dmax;
@@ -33,9 +33,7 @@
 
   /* create a mosaic distortion structure */
-  strcpy (MOSAIC.ctype, "DEC--DIS");
+  InitCoords (&MOSAIC, "DEC--DIS");
   MOSAIC.crval1 = FAKE_RA;
   MOSAIC.crval2 = FAKE_DEC;
-  
-  MOSAIC.crpix1 = MOSAIC.crpix2 = 0.0;
   
   /* mosaic 'pixels' are millimeters */
@@ -49,8 +47,4 @@
 
   MOSAIC.Npolyterms = 3;
-  for (i = 0; i < 7; i++) {
-    MOSAIC.polyterms[i][0] = 0;
-    MOSAIC.polyterms[i][1] = 0;
-  }
   ScanConfig (config, "DPLATE_X",   "%lf", 0, &pltscale);
   MOSAIC.polyterms[3][0] = pltscale;  // L : X^3 Y^0
@@ -59,6 +53,4 @@
   MOSAIC.polyterms[4][1] = pltscale;  // M : X^0 Y^0
   MOSAIC.polyterms[6][1] = pltscale;  // M : X^2 Y^0
-
-  RegisterMosaic (&MOSAIC);
 
   /* some basic data about the chisp */
@@ -85,22 +77,12 @@
     strcpy (image[i+1].name, name);
 
-    strcpy (image[i+1].coords.ctype, "DEC--WRP");
+    InitCoords (&image[i+1].coords, "DEC--WRP");
     
     image[i+1].coords.crval1 = dX*pixscale;
     image[i+1].coords.crval2 = dY*pixscale;
-    
-    image[i+1].coords.crpix1 = image[i+1].coords.crpix2 = 0.0;
+
     image[i+1].coords.cdelt1 = image[i+1].coords.cdelt2 = pixscale;
 
-    image[i+1].coords.pc1_1 = 1.0;
-    image[i+1].coords.pc1_2 = 0.0;
-    image[i+1].coords.pc2_1 = 0.0;
-    image[i+1].coords.pc2_2 = 1.0;
-
-    image[i+1].coords.Npolyterms = 0;
-    for (j = 0; j < 7; j++) {
-      image[i+1].coords.polyterms[j][0] = 0;
-      image[i+1].coords.polyterms[j][1] = 0;
-    }
+    image[i+1].coords.mosaic = &MOSAIC;
 
     image[i+1].sidtime  = 0.0;
Index: /trunk/Ohana/src/addstar/src/find_matches.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_matches.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/find_matches.c	(revision 37807)
@@ -53,4 +53,5 @@
    * coordops.update.c).  We use the center of the region (catalog) for crval1,2. 
    */
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -59,11 +60,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
-  strcpy (tcoords.ctype, "DEC--ARC");
 
   /* build spatial index (RA sort) referencing input array sequence */
Index: /trunk/Ohana/src/addstar/src/find_matches_ICRF.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_matches_ICRF.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/find_matches_ICRF.c	(revision 37807)
@@ -0,0 +1,177 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+// we find matching stars by (R,D).  we add the ICRF to the array of ICRFs with appropriate links.  
+int find_matches_ICRF (SkyRegion *region, ICRF_Stars *stars, int Nstars, Catalog *catalog, AddstarClientOptions *options) {
+  
+  off_t i, j, n, N, J, Jmin;
+  double RADIUS, RADIUS2, Rmin;
+  double *X1, *Y1, *X2, *Y2;
+  double dX, dY, dR;
+  off_t *N1, *N2;
+  off_t Nave, NAVE, Nmeas, NMEAS, Nmatch;
+
+  /** allocate local arrays (stars) **/
+  ALLOCATE (X1, double, Nstars);
+  ALLOCATE (Y1, double, Nstars);
+  ALLOCATE (N1, off_t,  Nstars);
+
+  /** allocate local arrays (catalog) **/
+  NAVE = Nave = catalog[0].Naverage;
+  ALLOCATE (X2, double, NAVE);
+  ALLOCATE (Y2, double, NAVE);
+  ALLOCATE (N2, off_t,  NAVE);
+
+  /* internal counters */
+  Nmatch = 0;
+
+  // current max obj ID for this catalog
+  unsigned int catID = catalog[0].catID;
+
+  int Nsecfilt = catalog[0].Nsecfilt;
+
+  NMEAS = Nmeas = catalog[0].Nmeasure;
+
+  /* project onto rectilinear grid with 1 arcsec pixels. the choice of ARC projection has
+   * the advantage that every point in R,D has a mapping to a unique X,Y.  However, note
+   * that not all possible X,Y points map back to R,D and the local plate scale changes
+   * far from the projection pole. We use the center of the region (catalog) for crval1,2.
+   */
+  Coords tcoords;
+  InitCoords (&tcoords, "DEC--ARC");
+  tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
+  tcoords.crval2 = (region[0].Dmax < 90) ? 0.5*(region[0].Dmin + region[0].Dmax) : 90.0;
+  tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
+
+  /* build spatial index (RA sort) referencing input array sequence */
+  for (i = 0; i < Nstars; i++) {
+    RD_to_XY (&X1[i], &Y1[i], stars[i].R, stars[i].D, &tcoords);
+    N1[i] = i;
+  }
+  sort_coords_index (X1, Y1, N1, Nstars);
+
+  /* build spatial index (RA sort) */
+  for (i = 0; i < Nave; i++) {
+    RD_to_XY (&X2[i], &Y2[i], catalog[0].average[i].R, catalog[0].average[i].D, &tcoords);
+    N2[i] = i;
+  }
+  sort_coords_index (X2, Y2, N2, Nave);
+
+  /* choose a radius for matches */
+  RADIUS = options->radius; /* provided by config */
+  RADIUS2 = RADIUS*RADIUS;
+
+  /** find matched stars **/
+  for (i = j = 0; (i < Nstars) && (j < Nave); ) {
+    if (!finite(X1[i]) || !finite(Y1[i])) { 
+      i++; 
+      continue;
+    }
+    if (!finite(X2[j]) || !finite(Y2[j])) { 
+      j++; 
+      continue;
+    }
+    
+    /* negative dX: j is too large */
+    dX = X1[i] - X2[j];
+    if (dX <= -1.02*RADIUS) {
+      i++;
+      continue;
+    }
+    /* positive dX, i is too large */
+    if (dX >= 1.02*RADIUS) {
+      j++;
+      continue;
+    }
+
+    if (stars[N1[i]].found) {
+      /* this star has already been assigned to an object in this or another catalog */
+      i++;
+      continue;
+    }
+
+    /* this block will match a given detection to the closest object within range of that
+       detection.  Note that this matches ALL detections within range of the single object
+       to that same object.  In the case of ICRF, this is what I want because I am
+       attaching each radio band as a separate measurement, with a single concatenated
+       list.  This is not super efficient, but ICRF is a small list
+     */
+    
+    /* within match range; look for matches */
+    Jmin = -1;
+    Rmin = RADIUS2;
+    for (J = j; (dX > -1.02*RADIUS) && (J < Nave); J++) {
+      /* find closest match for this detection */
+      dX = X1[i] - X2[J];
+      dY = Y1[i] - Y2[J];
+      dR = dX*dX + dY*dY;
+      if (dR > RADIUS2) continue;
+      if (dR < Rmin) {
+	Rmin = dR;
+	Jmin  = J;
+      }
+    }
+
+    /* no match, try next detection */ 
+    if (Jmin == -1) {
+      i++;
+      continue;
+    }
+
+    /*** a match is found, add to measure with links to average ***/
+    Nmatch ++;
+    n = N2[Jmin];
+    N = N1[i];
+
+    /* make sure there is space for next entry */
+    if (Nmeas >= NMEAS) {
+      NMEAS = Nmeas + 1000;
+      REALLOCATE (catalog[0].measure, Measure, NMEAS);
+    }
+
+    /** add new measurement for this star **/
+
+    // set the new measurements
+    catalog[0].measure[Nmeas]          = stars[N].measure;
+
+    // measure now carries R,D (not dR,dD) 
+    catalog[0].measure[Nmeas].averef   = n;
+    catalog[0].measure[Nmeas].objID    = catalog[0].average[n].objID;
+    catalog[0].measure[Nmeas].catID    = catID;
+
+    catalog[0].average[n].flags |= ID_ICRF_QSO;
+
+    stars[N].found = TRUE;
+    catalog[0].average[n].Nmeasure ++;
+    Nmeas ++;
+    i++;
+  }
+
+  // list missed QSOs, do not insert
+  for (i = 0; i < Nstars; i++) {
+    if (stars[i].found) continue;
+    if (!IN_REGION (stars[i].R, stars[i].D)) continue;
+    fprintf (stderr, "missed ICRF QSO @ %10.6f %10.6f\n", stars[i].R, stars[i].D);
+  }
+
+  REALLOCATE (catalog[0].average, Average, Nave);
+  REALLOCATE (catalog[0].measure, Measure, Nmeas);
+ 
+  catalog[0].sorted = FALSE;
+
+  /* check if the catalog has changed?  if no change, no need to write */
+  // catalog[0].objID     = objID; -- I am not adding new entries, so objID does not change
+  catalog[0].Naverage  = Nave;
+  catalog[0].Nmeasure  = Nmeas;
+  catalog[0].Nsecf_mem = Nave*Nsecfilt;
+  if (VERBOSE) fprintf (stderr, "Nstars, Nave, Nmeas: %d "OFF_T_FMT" "OFF_T_FMT" ("OFF_T_FMT" matches)\n",  Nstars, Nave, Nmeas, Nmatch);
+
+  free (X1);
+  free (Y1);
+  free (N1);
+  free (N2);
+  free (X2);
+  free (Y2);
+
+  return (Nmatch);
+}
Index: /trunk/Ohana/src/addstar/src/find_matches_closest.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_matches_closest.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/find_matches_closest.c	(revision 37807)
@@ -53,4 +53,5 @@
    * far from the projection pole. We use the center of the region (catalog) for crval1,2.
    */
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -59,11 +60,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
-  strcpy (tcoords.ctype, "DEC--ARC");
 
   /* build spatial index (RA sort) referencing input array sequence */
Index: /trunk/Ohana/src/addstar/src/find_matches_closest_refstars.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_matches_closest_refstars.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/find_matches_closest_refstars.c	(revision 37807)
@@ -47,4 +47,5 @@
    * far from the projection pole. We use the center of the region (catalog) for crval1,2.
    */
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -53,11 +54,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0.0;
-  tcoords.crpix2 = 0.0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 0;
-  strcpy (tcoords.ctype, "DEC--ARC");
   
   /* build spatial index (RA sort) referencing input array sequence */
Index: /trunk/Ohana/src/addstar/src/find_matches_refstars.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_matches_refstars.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/find_matches_refstars.c	(revision 37807)
@@ -51,12 +51,8 @@
   /* project onto rectilinear grid with 1 arcsec pixels, sort by X */
   /* reference for coords is catalog center */
+  InitCoords (&tcoords, "DEC--ZEA");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   tcoords.crval2 = 0.5*(region[0].Dmin + region[0].Dmax);
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  strcpy (tcoords.ctype, "DEC--ZEA");
-  tcoords.Npolyterms = 0;
   
   for (i = 0; i < Nstars; i++) {
Index: /trunk/Ohana/src/addstar/src/find_matches_starpar.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_matches_starpar.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/find_matches_starpar.c	(revision 37807)
@@ -0,0 +1,218 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+// we find matching stars by (R,D).  we add the starpar to the array of starpars with appropriate links.  
+int find_matches_starpar (SkyRegion *region, StarPar_Stars *stars, int Nstars, Catalog *catalog, AddstarClientOptions *options) {
+  
+  off_t i, j, n, N, J, Jmin;
+  double RADIUS, RADIUS2, Rmin;
+  double *X1, *Y1, *X2, *Y2;
+  double dX, dY, dR;
+  off_t *N1, *N2;
+  off_t Nave, NAVE, Nstarpar, NSTARPAR, Nmatch;
+
+  /** allocate local arrays (stars) **/
+  ALLOCATE (X1, double, Nstars);
+  ALLOCATE (Y1, double, Nstars);
+  ALLOCATE (N1, off_t,  Nstars);
+
+  /** allocate local arrays (catalog) **/
+  NAVE = Nave = catalog[0].Naverage;
+  ALLOCATE (X2, double, NAVE);
+  ALLOCATE (Y2, double, NAVE);
+  ALLOCATE (N2, off_t,  NAVE);
+
+  /* internal counters */
+  Nmatch = 0;
+  NSTARPAR = Nstarpar = catalog[0].Nstarpar;
+
+  // current max obj ID for this catalog
+  unsigned int objID = catalog[0].objID;
+  unsigned int catID = catalog[0].catID;
+
+  int Nsecfilt = catalog[0].Nsecfilt;
+
+  /* project onto rectilinear grid with 1 arcsec pixels. the choice of ARC projection has
+   * the advantage that every point in R,D has a mapping to a unique X,Y.  However, note
+   * that not all possible X,Y points map back to R,D and the local plate scale changes
+   * far from the projection pole. We use the center of the region (catalog) for crval1,2.
+   */
+  Coords tcoords;
+  InitCoords (&tcoords, "DEC--ARC");
+  tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
+  tcoords.crval2 = (region[0].Dmax < 90) ? 0.5*(region[0].Dmin + region[0].Dmax) : 90.0;
+  tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
+
+  /* build spatial index (RA sort) referencing input array sequence */
+  for (i = 0; i < Nstars; i++) {
+    RD_to_XY (&X1[i], &Y1[i], stars[i].R, stars[i].D, &tcoords);
+    N1[i] = i;
+  }
+  sort_coords_index (X1, Y1, N1, Nstars);
+
+  /* build spatial index (RA sort) */
+  for (i = 0; i < Nave; i++) {
+    RD_to_XY (&X2[i], &Y2[i], catalog[0].average[i].R, catalog[0].average[i].D, &tcoords);
+    N2[i] = i;
+  }
+  sort_coords_index (X2, Y2, N2, Nave);
+
+  /* choose a radius for matches */
+  RADIUS = options->radius; /* provided by config */
+  RADIUS2 = RADIUS*RADIUS;
+
+  /** find matched stars **/
+  for (i = j = 0; (i < Nstars) && (j < Nave); ) {
+    if (!finite(X1[i]) || !finite(Y1[i])) { 
+      i++; 
+      continue;
+    }
+    if (!finite(X2[j]) || !finite(Y2[j])) { 
+      j++; 
+      continue;
+    }
+    
+    /* negative dX: j is too large */
+    dX = X1[i] - X2[j];
+    if (dX <= -1.02*RADIUS) {
+      i++;
+      continue;
+    }
+    /* positive dX, i is too large */
+    if (dX >= 1.02*RADIUS) {
+      j++;
+      continue;
+    }
+
+    if (stars[N1[i]].found) {
+      /* this star has already been assigned to an object in this or another catalog */
+      i++;
+      continue;
+    }
+
+    /* this block will match a given detection to the closest object within range of that detection.
+       XXX note that this matches ALL detections within range of the single object to that same object 
+       this is bad, but I cannot just go in linear order (ie, mark off each object as they are
+       used).  I should make a list of all Nobj * Ndet pairs in range and choose the matches
+       based on their separations.  UGH
+     */
+    
+    /* within match range; look for matches */
+    Jmin = -1;
+    Rmin = RADIUS2;
+    for (J = j; (dX > -1.02*RADIUS) && (J < Nave); J++) {
+      /* find closest match for this detection */
+      dX = X1[i] - X2[J];
+      dY = Y1[i] - Y2[J];
+      dR = dX*dX + dY*dY;
+      if (dR > RADIUS2) continue;
+      if (dR < Rmin) {
+	Rmin = dR;
+	Jmin  = J;
+      }
+    }
+
+    /* no match, try next detection */ 
+    if (Jmin == -1) {
+      i++;
+      continue;
+    }
+
+    /*** a match is found, add to starpar with links to average ***/
+    Nmatch ++;
+    n = N2[Jmin];
+    N = N1[i];
+
+    /* make sure there is space for next entry */
+    if (Nstarpar >= NSTARPAR) {
+      NSTARPAR = Nstarpar + 1000;
+      REALLOCATE (catalog[0].starpar, StarPar, NSTARPAR);
+    }
+
+    /** add starpar for this star **/
+
+    // set the new starparments
+    catalog[0].starpar[Nstarpar]          = stars[N].starpar;
+    catalog[0].starpar[Nstarpar].averef   = n;
+    catalog[0].starpar[Nstarpar].objID    = catalog[0].average[n].objID;
+    catalog[0].starpar[Nstarpar].catID    = catID;
+
+    // NOTE: include R,D in starpar?
+    // XXX if I add R,D to starpar, I should rationalize R,D to the same boundary?
+
+    stars[N].found = TRUE;
+    catalog[0].average[n].Nstarpar ++;
+    Nstarpar ++;
+    i++;
+  }
+
+# if (1)
+  /** incorporate unmatched image stars? **/
+  for (i = 0; (i < Nstars) && !options->only_match; i++) {
+
+    // skip already matched stars
+    if (stars[i].found) continue;
+    if (!IN_REGION (stars[i].R, stars[i].D)) continue;
+
+    /* make sure there is space for next entry */
+    if (Nstarpar >= NSTARPAR) {
+      NSTARPAR = Nstarpar + 1000;
+      REALLOCATE (catalog[0].starpar, StarPar, NSTARPAR);
+    }
+    if (Nave >= NAVE) {
+      NAVE = Nave + 1000;
+      REALLOCATE (catalog[0].average, Average, NAVE);
+      REALLOCATE (catalog[0].secfilt, SecFilt, NAVE*Nsecfilt);
+    }
+
+    dvo_average_init (&catalog[0].average[Nave]);
+    catalog[0].average[Nave].R         	   = stars[i].R;
+    catalog[0].average[Nave].D         	   = stars[i].D;
+
+    catalog[0].average[Nave].Nstarpar  	   = 1;
+    catalog[0].average[Nave].starparOffset = Nstarpar;
+    catalog[0].average[Nave].objID     	   = objID;
+    catalog[0].average[Nave].catID     	   = catID;
+
+    if (PSPS_ID) {
+      catalog[0].average[Nave].extID = CreatePSPSObjectID(catalog[0].average[Nave].R, catalog[0].average[Nave].D);
+    }
+
+    objID ++;
+
+    for (j = 0; j < Nsecfilt; j++) {
+      dvo_secfilt_init (&catalog[0].secfilt[Nave*Nsecfilt+j]);
+    }
+
+    catalog[0].starpar[Nstarpar]        = stars[i].starpar;
+    catalog[0].starpar[Nstarpar].averef = Nave;
+    catalog[0].starpar[Nstarpar].objID  = catalog[0].average[Nave].objID;
+    catalog[0].starpar[Nstarpar].catID  = catID;
+
+    stars[i].found = TRUE;
+    Nstarpar ++;
+    Nave ++;
+  }
+# endif      
+
+  REALLOCATE (catalog[0].average, Average, Nave);
+  REALLOCATE (catalog[0].starpar, StarPar, Nstarpar);
+ 
+  catalog[0].sorted = FALSE;
+
+  /* check if the catalog has changed?  if no change, no need to write */
+  catalog[0].objID     = objID; // new max value, save on catalog close
+  catalog[0].Naverage  = Nave;
+  catalog[0].Nstarpar  = Nstarpar;
+  catalog[0].Nsecf_mem = Nave*Nsecfilt;
+  if (VERBOSE) fprintf (stderr, "Nstars, Nave, Nstarpar: %d "OFF_T_FMT" "OFF_T_FMT" ("OFF_T_FMT" matches)\n",  Nstars, Nave, Nstarpar, Nmatch);
+
+  free (X1);
+  free (Y1);
+  free (N1);
+  free (N2);
+  free (X2);
+  free (Y2);
+
+  return (Nmatch);
+}
Index: /trunk/Ohana/src/addstar/src/find_missing.c
===================================================================
--- /trunk/Ohana/src/addstar/src/find_missing.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/find_missing.c	(revision 37807)
@@ -14,5 +14,4 @@
     REALLOCATE (catalog[0].missing, Missing, NMISS);
   }
-  if (!FindMosaicForImage (overlap, Noverlap, j)) continue;
   if (!in_image (catalog[0].average[Nave].R, catalog[0].average[Nave].D, &overlap[j])) continue;
   add_miss_link (&catalog[0].average[Nave], next_miss, Nmiss);
@@ -27,5 +26,5 @@
 /* add reference for undetected catalog stars */
 /* XXX allow this option only for single images? */
-if (!strcmp (&image[0].coords.ctype[4], "-WRP")) RegisterMosaic (mosaic);
+if (!strcmp (&image[0].coords.ctype[4], "-WRP")) image[0].coords = mosaic;
 for (j = 0; (j < Nave) && !options.skip_missed; j++) {
   n = N2[j];
Index: /trunk/Ohana/src/addstar/src/loadICRF.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF.c	(revision 37807)
@@ -0,0 +1,54 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+/* This is the DVO program to upload ICRF QSO data (eg, from Leonid Petrov) into a DVO
+   database.  It is modeled on the loadstarpar program and is expected to be run only
+   rarely (once?).  The ICRF QSO parameter data are delivered as text file.  It does not
+   allow a subset of the sky to be uploaded; entire QSO parameter files are loaded if
+   supplied on the command line.
+
+   USAGE: loadICRF -D CATDIR (catdir) (file.fits) [...more files]
+*/
+
+int main (int argc, char **argv) {
+
+  int i;
+  AddstarClientOptions options;
+
+  options = ConfigInit (&argc, argv);
+  options = args_loadICRF (&argc, argv, options); // XXX fix args after rest is done
+
+  // load the full sky description table (dvodb must exist)
+  SkyTable *sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, FALSE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  
+  // load the list of hosts
+  HostTable *hosts = NULL;
+  if (PARALLEL) {
+    hosts = HostTableLoad (CATDIR, sky->hosts);
+    if (!hosts) {
+      fprintf (stderr, "ERROR: failure reading Host Table %s for database %s\n", sky->hosts, CATDIR);
+      exit (1);
+    }    
+
+    // ensure that the paths are absolute path names
+    for (i = 0; i < hosts->Nhosts; i++) {
+      char *tmppath = abspath (hosts->hosts[i].pathname, DVO_MAX_PATH);
+      free (hosts->hosts[i].pathname);
+      hosts->hosts[i].pathname = tmppath;
+    }
+
+    // set up the array of active hosts
+    init_remote_hosts ();
+  }
+
+  // generate the subset matching the user-selected region
+  SkyList *skylist = SkyListByPatch (sky, -1, &UserPatch);
+
+  // argv[i] are a list of input tables; load one at a time
+  for (i = 1; i < argc; i++) {
+    fprintf (stderr, "loading %s\n", argv[i]);
+    loadICRF_table (skylist, hosts, argv[i], &options);
+  }
+  exit (0);
+}  
Index: /trunk/Ohana/src/addstar/src/loadICRF_catalog.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_catalog.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_catalog.c	(revision 37807)
@@ -0,0 +1,28 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+int loadICRF_catalog (ICRF_Stars *stars, int Nstars, SkyRegion *region, char *filename, AddstarClientOptions *options) {
+
+  Catalog catalog;
+
+  // now we have all of the loaded stars in this catalog
+  catalog.filename = filename;
+  catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+  catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+  catalog.catflags = LOAD_AVES | LOAD_SECF | LOAD_MEAS;
+  catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+    
+  // an error exit status here is a significant error
+  if (!dvo_catalog_open (&catalog, region, VERBOSE, "w")) {
+    fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+    exit (2);
+  }
+
+  find_matches_ICRF (region, stars, Nstars, &catalog, options);
+    
+  dvo_catalog_save (&catalog, VERBOSE);
+  dvo_catalog_unlock (&catalog);
+  dvo_catalog_free (&catalog);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/addstar/src/loadICRF_client.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_client.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_client.c	(revision 37807)
@@ -0,0 +1,30 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+int main (int argc, char **argv) {
+
+  AddstarClientOptions options;
+
+  // need to construct these options with args_loadICRF...
+  options = ConfigInit (&argc, argv);
+  options = args_loadICRF_client (&argc, argv, options);
+
+  // client is called with a pointer to the file to be loaded
+
+  SkyTable *sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, FALSE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  
+  SkyList *skylist = SkyRegionByCPT (sky, CPT_FILE);
+
+  char cptfile[1024];
+  snprintf (cptfile, 1024, "%s/%s.cpt", HOSTDIR, skylist[0].regions[0]->name);
+
+  int Nstars;
+  ICRF_Stars *stars = loadICRF_load_stars (INPUT, &Nstars);
+
+  loadICRF_catalog (stars, Nstars, skylist->regions[0], cptfile, &options);
+
+  free (stars);
+
+  exit (0);
+}
Index: /trunk/Ohana/src/addstar/src/loadICRF_io.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_io.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_io.c	(revision 37807)
@@ -0,0 +1,189 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+int loadICRF_save_stars (char *filename, ICRF_Stars *stars, int Nstars) {
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    myAbortF ("ERROR: cannot open file for output %s\n", filename);
+  }
+
+  gfits_fwrite_header  (f, &header);
+  gfits_fwrite_matrix  (f, &matrix);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  gfits_create_table_header (&theader, "BINTABLE", "ICRF");
+
+  gfits_define_bintable_column (&theader, "D", "RA",       "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "DEC",      "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "RA_ERR",   "", "arcsec", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "DEC_ERR",  "", "arcsec", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "F_PSF",    "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dF_PSF",   "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "F_AP",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dF_AP",    "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "M_PSF",    "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dM_PSF",   "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "M_AP",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dM_AP",    "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "PHOTCODE", "", "", 1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "FLAGS",    "", "", 1.0, FT_BZERO_INT32);
+
+  int i;
+  double       *ra       = NULL; ALLOCATE (ra      , double	 , Nstars); for (i = 0; i < Nstars; i++) ra      [i] = stars[i].R ;
+  double       *dec      = NULL; ALLOCATE (dec     , double	 , Nstars); for (i = 0; i < Nstars; i++) dec     [i] = stars[i].D ;
+  float        *dR       = NULL; ALLOCATE (dR      , float 	 , Nstars); for (i = 0; i < Nstars; i++) dR      [i] = stars[i].measure.dXccd   ;
+  float        *dD       = NULL; ALLOCATE (dD      , float 	 , Nstars); for (i = 0; i < Nstars; i++) dD      [i] = stars[i].measure.dYccd   ;
+  float        *Fpsf     = NULL; ALLOCATE (Fpsf    , float	 , Nstars); for (i = 0; i < Nstars; i++) Fpsf    [i] = stars[i].measure.FluxPSF ;
+  float        *dFpsf    = NULL; ALLOCATE (dFpsf   , float	 , Nstars); for (i = 0; i < Nstars; i++) dFpsf   [i] = stars[i].measure.dFluxPSF;
+  float        *Fap      = NULL; ALLOCATE (Fap     , float	 , Nstars); for (i = 0; i < Nstars; i++) Fap     [i] = stars[i].measure.FluxAp  ;
+  float        *dFap     = NULL; ALLOCATE (dFap    , float	 , Nstars); for (i = 0; i < Nstars; i++) dFap    [i] = stars[i].measure.dFluxAp ;
+  float        *Mpsf     = NULL; ALLOCATE (Mpsf    , float	 , Nstars); for (i = 0; i < Nstars; i++) Mpsf    [i] = stars[i].measure.M       ;
+  float        *dMpsf    = NULL; ALLOCATE (dMpsf   , float	 , Nstars); for (i = 0; i < Nstars; i++) dMpsf   [i] = stars[i].measure.dM      ;
+  float        *Map      = NULL; ALLOCATE (Map     , float	 , Nstars); for (i = 0; i < Nstars; i++) Map     [i] = stars[i].measure.Map     ;
+  float        *dMap     = NULL; ALLOCATE (dMap    , float	 , Nstars); for (i = 0; i < Nstars; i++) dMap    [i] = stars[i].measure.dMap    ;
+  unsigned int *photcode = NULL; ALLOCATE (photcode, unsigned int, Nstars); for (i = 0; i < Nstars; i++) photcode[i] = stars[i].measure.photcode;
+  unsigned int *flags    = NULL; ALLOCATE (flags   , unsigned int, Nstars); for (i = 0; i < Nstars; i++) flags   [i] = stars[i].measure.dbFlags ;
+
+  // generate the output array that carries the data
+  gfits_create_table (&theader, &ftable);
+
+  // add the columns to the output array
+  gfits_set_bintable_column (&theader, &ftable, "RA",       ra      , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "DEC",      dec     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "RA_ERR",   dR      , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "DEC_ERR",  dD      , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "F_PSF",    Fpsf    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dF_PSF",   dFpsf   , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "F_AP",     Fap     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dF_AP",    dFap    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "M_PSF",    Mpsf    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dM_PSF",   dMpsf   , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "M_AP",     Map     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dM_AP",    dMap    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "PHOTCODE", photcode, Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "FLAGS",    flags   , Nstars);
+    
+  gfits_fwrite_Theader (f, &theader);
+  gfits_fwrite_table (f, &ftable);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  fclose (f);
+
+  return TRUE;
+}
+
+
+# define GET_COLUMN(OUT,NAME,TYPE)					\
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+ICRF_Stars *loadICRF_load_stars (char *filename, int *nstars) {
+
+  int i, Ncol;
+  off_t Nrow;
+  char type[16];
+
+  ICRF_Stars *stars = NULL;
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  header.buffer = NULL;
+  matrix.buffer = NULL;
+  ftable.buffer = NULL;
+  theader.buffer = NULL;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read header\n");
+    goto escape;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read matrix\n");
+    goto escape;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) goto escape;
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) goto escape;
+ 
+  GET_COLUMN(ra      , "RA",          double);
+  GET_COLUMN(dec     , "DEC",         double);
+  GET_COLUMN(dR      , "RA_ERR",      float);
+  GET_COLUMN(dD      , "DEC_ERR",     float);
+  GET_COLUMN(Fpsf    , "F_PSF",       float);
+  GET_COLUMN(dFpsf   , "dF_PSF",      float);
+  GET_COLUMN(Fap     , "F_AP",        float);
+  GET_COLUMN(dFap    , "dF_AP",       float);
+  GET_COLUMN(Mpsf    , "M_PSF",       float);
+  GET_COLUMN(dMpsf   , "dM_PSF",      float);
+  GET_COLUMN(Map     , "M_AP",        float);
+  GET_COLUMN(dMap    , "dM_AP",       float);
+  GET_COLUMN(photcode, "PHOTCODE",    int);
+  GET_COLUMN(flags   , "FLAGS",       int);
+
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  ALLOCATE (stars, ICRF_Stars, Nrow);
+
+  for (i = 0; i < Nrow; i++) {
+    InitICRF_Star (&stars[i]);
+    stars[i].R               = ra      [i];
+    stars[i].D	             = dec     [i];
+    stars[i].measure.R       = ra      [i];
+    stars[i].measure.D       = dec     [i];
+    stars[i].measure.dXccd   = dR      [i];
+    stars[i].measure.dYccd   = dD      [i];
+    stars[i].measure.FluxPSF = Fpsf    [i];
+    stars[i].measure.dFluxPSF= dFpsf   [i];
+    stars[i].measure.FluxAp  = Fap     [i];
+    stars[i].measure.dFluxAp = dFap    [i];
+    stars[i].measure.M       = Mpsf    [i];
+    stars[i].measure.dM      = dMpsf   [i];
+    stars[i].measure.Map     = Map     [i];
+    stars[i].measure.dMap    = dMap    [i];
+    stars[i].measure.photcode= photcode[i];
+    stars[i].measure.dbFlags = flags   [i];
+  }
+
+  fclose (f);
+  *nstars = Nrow;
+  return stars;
+
+escape:
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  fclose (f);
+  return NULL;
+}
Index: /trunk/Ohana/src/addstar/src/loadICRF_make_subset.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_make_subset.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_make_subset.c	(revision 37807)
@@ -0,0 +1,44 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+// assign stars in the given region to the subset (NOTE: stars are sorted by RA, start is
+// first entry in stars array in this region)
+
+ICRF_Stars *loadICRF_make_subset (ICRF_Stars *stars, int Nstars, int start, SkyRegion *region, int *nsubset) {
+
+  int i;
+
+  ICRF_Stars *subset = NULL;
+
+  // collect array of (Stars *) stars in a new output catalog
+  int Nsubset = 0;
+  int NSUBSET = 3000;
+  ALLOCATE (subset, ICRF_Stars, NSUBSET);
+
+  // find the rest of the stars in this output region
+  for (i = start; i < Nstars; i++) {
+    if (stars[i].flag) continue;
+
+    // check if in skyregion
+    if (stars[i].R < region[0].Rmin) continue;
+    if (stars[i].R > region[0].Rmax) break;
+    if (stars[i].D < region[0].Dmin) continue;
+    if (stars[i].D > region[0].Dmax) continue;
+	  
+    // check if in UserPatch (a GLOBAL)
+    if (stars[i].R < UserPatch.Rmin) continue;
+    if (stars[i].R > UserPatch.Rmax) break;
+    if (stars[i].D < UserPatch.Dmin) continue;
+    if (stars[i].D > UserPatch.Dmax) continue;
+	  
+    subset[Nsubset] = stars[i];
+    Nsubset ++;
+
+    stars[i].flag = TRUE;  // set flag for stars we have already assigned to a subset
+
+    CHECK_REALLOCATE (subset, ICRF_Stars, NSUBSET, Nsubset, 10000);
+  }
+
+  *nsubset = Nsubset;
+  return subset;
+}
Index: /trunk/Ohana/src/addstar/src/loadICRF_readstars.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_readstars.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_readstars.c	(revision 37807)
@@ -0,0 +1,180 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+// ICRF table is ASCII text, with the following interesting columns (fixed bytes)
+//   0       : type (C - calibrator, N - non-calibrator, U - unreliable)
+//   3 -  21 : names (make into an extID?)
+//  24 -  39 : RA (ICRF)
+//  40 -  55 : DEC (ICRF)
+//  57 -  62 : dR (mas) [use dX,dY in milliarcsec 'pixels']
+//  64 -  69 : dD (mas)
+//  79 -  84 : Number of observations used (put in dt?)
+//  87 -  92 : S-band total flux density integrated over entire map,  Jy  (note 87 may be <) : 
+//  94 -  99 : S-band unresolved flux density at long VLBA baselines, Jy
+// 102 - 107 : C-band total flux density integrated over entire map,  Jy
+// 109 - 114 : C-band unresolved flux density at long VLBA baselines, Jy
+// 117 - 122 : X-band total flux density integrated over entire map,  Jy
+// 124 - 129 : X-band unresolved flux density at long VLBA baselines, Jy
+// 132 - 137 : U-band total flux density integrated over entire map,  Jy
+// 139 - 144 : U-band unresolved flux density at long VLBA baselines, Jy
+// 147 - 152 : K-band total flux density integrated over entire map,  Jy
+// 154 - 159 : K-band unresolved flux density at long VLBA baselines, Jy
+
+// I'm going to save these are FluxPSF (total), FluxAp (unresolved), and mags for 
+// the signficant ones (AB_m from Jy).  Use new photcodes to represent C,X,U,K
+
+/* 
+#  IVS name J2000 name  Right ascension Declination        D_alp  D_Del   Corr    #Obs  S-band flux    C-band Flux    X-band Flux   U-band flux    K-band Flux    Type Cat
+#                                                                                       Total  Unres   Total  Unres   Total  Unres  Total  Unres   Total  Unres
+#                       hr mn seconds   deg mn seconds      mas    mas                   Jy     Jy      Jy     Jy      Jy     Jy     Jy     Jy      Jy     Jy
+C  2357+080 J0000+0816  00 00 07.031141 +08 16 45.05175    0.46   0.85   0.758     41  -1.00  -1.00   -1.00  -1.00    0.020 <0.014  -1.00  -1.00   -1.00  -1.00   X    rfc_2014c
+01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0         1         2         3         4         5         6         7         8         9         0         1         2         3         4         5         6         
+*/
+
+
+int loadICRF_addstar(ICRF_Stars *star, float fluxPSF, float fluxAp, double R, double D, float dX, float dY, int photcode);
+
+ICRF_Stars *loadICRF_readstars (char *filename, int *nstars) {
+
+  // read in the full FITS files ('cause I don't have a partial read option)
+  FILE *f = fopen (filename, "r");
+  if (f == NULL) Shutdown ("can't read stellar parameter file: %s", filename);
+
+  int Nstars = 0;
+  int NSTARS = 1000;
+  ICRF_Stars *stars = NULL;
+  ALLOCATE (stars, ICRF_Stars, NSTARS);
+
+  double R, D;
+
+  // ICRF QSO photcodes (we store flux in Jy and AB mags) 
+  PhotCode *codeS = GetPhotcodebyName ("ICRF_S"); if (!codeS) { fprintf (stderr, "missing ICRF_S, exiting\n"); exit (1); }
+  PhotCode *codeC = GetPhotcodebyName ("ICRF_C"); if (!codeC) { fprintf (stderr, "missing ICRF_C, exiting\n"); exit (1); }
+  PhotCode *codeX = GetPhotcodebyName ("ICRF_X"); if (!codeX) { fprintf (stderr, "missing ICRF_X, exiting\n"); exit (1); }
+  PhotCode *codeU = GetPhotcodebyName ("ICRF_U"); if (!codeU) { fprintf (stderr, "missing ICRF_U, exiting\n"); exit (1); }
+  PhotCode *codeK = GetPhotcodebyName ("ICRF_K"); if (!codeK) { fprintf (stderr, "missing ICRF_K, exiting\n"); exit (1); }
+
+  /* read in stars line-by-line */
+  char line[1024];
+  while (scan_line (f, line) != EOF) {
+    stripwhite (line);
+    if (line[0] == 0) continue;
+    if (line[0] == '#') continue;
+
+    // objects with values other than "C" in the first column are not good for
+    // calibration, so just skip.
+    if (line[0] != 'C') continue;
+
+    // terminate the partial strings containing RA and DEC:
+    char cr = line[39]; line[39] = 0;
+    char cd = line[55]; line[55] = 0;
+
+    if (!ohana_str_to_radec (&R, &D, &line[24], &line[40])) {
+      line[39] = cr;
+      line[55] = cd;
+      fprintf (stderr, "problem with coords: %s\n", line);
+      exit (1);
+    }
+    R = ohana_normalize_angle (R);
+    
+    line[63] = 0;
+    float dR = atof(&line[57]);
+    line[70] = 0;
+    float dD = atof(&line[64]);
+
+    int dX = 1000.0*100*dR;
+    int dY = 1000.0*100*dD;
+
+    // XXX set this somewhere?
+    // line[85] = 0;
+    // int Nmeas = atoi(&line[79]);
+
+    line[ 93] = 0; float fluxSt = (line[ 87] == '<') ? NAN : atof(&line[ 87]); if (fluxSt == -1.0) fluxSt = NAN;
+    line[100] = 0; float fluxSu = (line[ 94] == '<') ? NAN : atof(&line[ 94]); if (fluxSu == -1.0) fluxSu = NAN;
+    line[108] = 0; float fluxCt = (line[102] == '<') ? NAN : atof(&line[102]); if (fluxCt == -1.0) fluxCt = NAN;
+    line[115] = 0; float fluxCu = (line[109] == '<') ? NAN : atof(&line[109]); if (fluxCu == -1.0) fluxCu = NAN;
+    line[123] = 0; float fluxXt = (line[117] == '<') ? NAN : atof(&line[117]); if (fluxXt == -1.0) fluxXt = NAN;
+    line[130] = 0; float fluxXu = (line[124] == '<') ? NAN : atof(&line[124]); if (fluxXu == -1.0) fluxXu = NAN;
+    line[138] = 0; float fluxUt = (line[132] == '<') ? NAN : atof(&line[132]); if (fluxUt == -1.0) fluxUt = NAN;
+    line[145] = 0; float fluxUu = (line[139] == '<') ? NAN : atof(&line[139]); if (fluxUu == -1.0) fluxUu = NAN;
+    line[153] = 0; float fluxKt = (line[147] == '<') ? NAN : atof(&line[147]); if (fluxKt == -1.0) fluxKt = NAN;
+    line[160] = 0; float fluxKu = (line[154] == '<') ? NAN : atof(&line[154]); if (fluxKu == -1.0) fluxKu = NAN;
+
+    // we treat each valid flux as a new star
+    if (isfinite(fluxSt)) {
+      loadICRF_addstar (&stars[Nstars], fluxSt, fluxSu, R, D, dX, dY, codeS->code);
+      Nstars++;
+      CHECK_REALLOCATE (stars, ICRF_Stars, NSTARS, Nstars, 1000);
+    }
+    if (isfinite(fluxCt)) {
+      loadICRF_addstar (&stars[Nstars], fluxCt, fluxCu, R, D, dX, dY, codeC->code);
+      Nstars++;
+      CHECK_REALLOCATE (stars, ICRF_Stars, NSTARS, Nstars, 1000);
+    }
+    if (isfinite(fluxXt)) {
+      loadICRF_addstar (&stars[Nstars], fluxXt, fluxXu, R, D, dX, dY, codeX->code);
+      Nstars++;
+      CHECK_REALLOCATE (stars, ICRF_Stars, NSTARS, Nstars, 1000);
+    }
+    if (isfinite(fluxUt)) {
+      loadICRF_addstar (&stars[Nstars], fluxUt, fluxUu, R, D, dX, dY, codeU->code);
+      Nstars++;
+      CHECK_REALLOCATE (stars, ICRF_Stars, NSTARS, Nstars, 1000);
+    }
+    if (isfinite(fluxKt)) {
+      loadICRF_addstar (&stars[Nstars], fluxKt, fluxKu, R, D, dX, dY, codeK->code);
+      Nstars++;
+      CHECK_REALLOCATE (stars, ICRF_Stars, NSTARS, Nstars, 1000);
+    }
+
+  }
+  *nstars = Nstars;
+  return (stars);
+}
+
+int loadICRF_addstar(ICRF_Stars *star, float fluxPSF, float fluxAp, double R, double D, float dX, float dY, int photcode) {
+
+  InitICRF_Star (star);
+
+  star->R = R;
+  star->D = D;
+  star->measure.R = R;
+  star->measure.D = D;
+  star->measure.dXccd = dX;   // in centi-pixels, where 1 pixel = 1 mas
+  star->measure.dYccd = dY;   // in centi-pixels, where 1 pixel = 1 mas
+
+  star->measure.FluxPSF = fluxPSF; // flux in Jy
+  star->measure.dFluxPSF = 0.01;  // this is just a guess
+  star->measure.FluxAp = fluxAp;  // flux in Jy
+  star->measure.dFluxAp = 0.01;   // this is just a guess
+
+  star->measure.M = (fluxPSF > 0.0) ? 8.9 - 2.5*log10(fluxPSF) : NAN;
+  star->measure.dM = (fluxPSF > 0.0) ? 0.01 / fluxPSF : NAN;
+  star->measure.Map = (fluxAp > 0.0) ? 8.9 - 2.5*log10(fluxAp) : NAN;
+  star->measure.dMap = (fluxAp > 0.0) ? 0.01 / fluxAp: NAN;
+  star->measure.photcode = photcode;
+  star->measure.dbFlags |= ID_MEAS_ICRF_QSO;
+  return TRUE;
+}
+
+int loadICRF_sortStars (ICRF_Stars *stars, int Nstars) {
+
+# define SWAPFUNC(A,B){ ICRF_Stars temp = stars[A]; stars[A] = stars[B]; stars[B] = temp; }
+# define COMPARE(A,B)(stars[A].R < stars[B].R)
+
+  OHANA_SORT (Nstars, COMPARE, SWAPFUNC);
+
+# undef SWAPFUNC
+# undef COMPARE
+  
+  return TRUE;
+}
+
+int InitICRF_Star (ICRF_Stars *star) {
+
+    dvo_measure_init (&star[0].measure);
+    star[0].found = FALSE; // FALSE = not yet assigned to an object
+    star[0].flag = FALSE; // FALSE = not yet assigned to a subset
+    return TRUE;
+}
Index: /trunk/Ohana/src/addstar/src/loadICRF_remote_hosts.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_remote_hosts.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_remote_hosts.c	(revision 37807)
@@ -0,0 +1,144 @@
+# include "addstar.h"
+# include "loadICRF.h"
+# include <sys/types.h>
+# include <sys/wait.h>
+
+# define DEBUG 1
+
+// we are running N parallel remote jobs on the remote hosts.  In the usual remote
+// processing (eg, relphot or relastro), we launch one job per remote host.  In this case,
+// we are launching one job foreach table (catalog.cpt) being touched.  individual hosts
+// may have more than one job active at a time.  
+
+int       Nhosts = 0;
+HostInfo **hosts = NULL;
+
+int init_remote_hosts () {
+
+  Nhosts = 10;
+  ALLOCATE (hosts, HostInfo *, Nhosts);
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    hosts[i] = NULL;
+  }
+  return TRUE;
+}
+
+int find_empty_slot () {
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    if (hosts[i] == NULL) return i;
+  }
+  return -1;
+}
+
+int harvest_all () {
+
+  int slot = -1;
+
+  while (slot != -2) {
+    slot = harvest_host ();
+    usleep (50000);
+  }
+  return TRUE;
+}
+ 
+// put the host in the list in a free slot. NOTE: this should never be called if we do not
+// already have a free slot.
+int save_remote_host (HostInfo *host) {
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    if (hosts[i] == NULL) {
+      hosts[i] = host;
+      return TRUE;
+    }
+  }
+  myAbort ("failed to find an empty slot: this is a programming error");
+  return FALSE;
+}
+
+// wait for all children to complete, report output to stdout
+// possible states when function is finished:
+// * no child was harvested, but children still exist
+// * no outstanding children
+// * a child was harvested
+int harvest_host () {
+
+  // check if any children have finished...
+  int status = 0;
+  int pid = waitpid (-1, &status, WNOHANG);
+
+  if (!pid) return -1; // there are outstanding children, but none have exited, no slot was opened
+  if ((pid == -1) && (errno == ECHILD)) return -2; // there are no outstanding children
+  if ((pid == -1) && (errno != ECHILD)) myAbort ("programming error?");
+
+  if (DEBUG) fprintf (stdout, "PID %d done\n", pid);
+
+  // find the host which has finished
+  int i;
+  int found = FALSE;
+  for (i = 0; (i < Nhosts) && !found; i++) {
+    if (!hosts[i]) continue; // unassigned slot
+    if (hosts[i][0].pid != pid) continue;
+    found = TRUE;
+    break;
+  }
+  myAssert (found, "Programming error: failed to matched finished job to known host!");
+  int slot = i;
+
+  HostInfo *host = hosts[slot];
+
+  // check on the status of this job and report any output
+  if (DEBUG) fprintf (stdout, "job finished for %s (%d)\n", host->hostname, pid);
+
+  int Nout, printHead;
+
+  // read stdout
+  EmptyIOBuffer (&host->stdout, 100, host->stdio[HOST_STDOUT]);
+  printHead = VERBOSE || (host->stdout.Nbuffer > 0);
+  if (printHead) fprintf (stdout, "--- stdout from %s --- (%d bytes, v2)\n", host->hostname, host->stdout.Nbuffer);
+  Nout = write (STDOUT_FILENO, host->stdout.buffer, host->stdout.Nbuffer);
+  if (Nout != host->stdout.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+  FlushIOBuffer (&host->stdout);
+  if (printHead) fprintf (stdout, "\n");
+	    
+  // read stderr
+  EmptyIOBuffer (&host->stderr, 100, host->stdio[HOST_STDERR]);
+  printHead = VERBOSE || (host->stderr.Nbuffer > 0);
+  if (printHead) fprintf (stdout, "--- stderr from %s --- (%d bytes, v2)\n", host->hostname, host->stderr.Nbuffer);
+  Nout = write (STDOUT_FILENO, host->stderr.buffer, host->stderr.Nbuffer);
+  if (Nout != host->stderr.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+  FlushIOBuffer (&host->stderr);
+  if (printHead) fprintf (stdout, "\n");
+
+  if (WIFEXITED(status)) {
+    if (DEBUG) fprintf (stdout, "normal completion, exit status is %d\n", WEXITSTATUS(status));
+    host->status = WEXITSTATUS(status);
+    if (host->status) {
+      fprintf (stdout, "job failed on %s\n", host->hostname);
+    }
+  } else {
+    host->status = -1;
+    fprintf (stdout, "job exited abnormally on %s\n", host->hostname);
+  }
+
+  // close opened connections
+  close (host->stdio[HOST_STDIN]);
+  close (host->stdio[HOST_STDOUT]);
+  close (host->stdio[HOST_STDERR]);
+
+  // free data associated with the host
+  free (host->hostname);
+  free (host->pathname);
+  FreeIOBuffer (&host->stdout);
+  FreeIOBuffer (&host->stderr);
+  free (host);
+
+  // free the slot
+  hosts[slot] = NULL;
+  return slot; 
+}
+
Index: /trunk/Ohana/src/addstar/src/loadICRF_save_remote.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_save_remote.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_save_remote.c	(revision 37807)
@@ -0,0 +1,90 @@
+# include "addstar.h"
+# include "loadICRF.h"
+# define DEBUG 1
+
+int strextend (char *input, char *format,...) {
+
+  char tmpextra[1024], tmpline[1024];
+  va_list argp;
+
+  va_start (argp, format);
+  vsnprintf (tmpextra, 1024, format, argp);
+  snprintf (tmpline, 1024, "%s %s", input, tmpextra);
+  strcpy (input, tmpline);
+
+  return TRUE;
+}
+
+int loadICRF_save_remote (ICRF_Stars *stars, int Nstars, HostTable *hosts, SkyRegion *region, char *fullname, AddstarClientOptions *options) {
+
+  char uniquer[12];
+  int TIME = time(NULL);
+  int PID = getpid();
+  snprintf (uniquer, 12, "%05d.%05d", PID, TIME % 100000);
+
+  // if this region is a parallel thing, save and launch remote
+  if (!PARALLEL) { 
+    loadICRF_catalog (stars, Nstars, region, fullname, options);
+  } else {
+    int N = hosts->index[region->hostID];
+    HostInfo *hostMach = &hosts->hosts[N];
+
+    // save to a unique filename
+    char filename[1024]; // CATDIR/tmpdir/ICRF.PID.index.fits
+    snprintf (filename, 1024, "%s/tmpdir/ICRF.%s.%05d.fits", CATDIR, uniquer, region->index);
+    if (!check_file_access (filename, FALSE, TRUE, TRUE)) exit (2);
+
+    // write the data to the given FITS file
+    loadICRF_save_stars (filename, stars, Nstars);
+
+    int slot = -1;
+    while (slot == -1) {
+      slot = find_empty_slot ();
+      if (slot == -1) {
+	usleep (50000);
+	slot = harvest_host();
+	myAssert (slot != -2, "we should not call harvest_host here if we have open slots");
+      }
+    }
+
+    // allocate a host for this job
+    HostInfo *host = NULL;
+    ALLOCATE (host, HostInfo, 1);
+    InitHost (host);
+
+    // we want to run this job on the host described by hostMach.  copy
+    // immutable data from hostMach to a locally allocated host:
+    host->hostID = hostMach->hostID;
+    host->hostname = strcreate (hostMach->hostname);
+    host->pathname = strcreate (hostMach->pathname);
+    InitIOBuffer (&host->stdout, 1000);
+    InitIOBuffer (&host->stderr, 1000);
+
+    // got a valid slot, so launch a new host
+
+    // need to generate the remote command
+    char command[1024];
+    snprintf (command, 1024, "loadICRF_client");
+    strextend (command, "-hostID %d", host->hostID);
+    strextend (command, "-D CATDIR %s", CATDIR);
+    strextend (command, "-hostdir %s", host->pathname);
+    strextend (command, "-cpt %s", region->name);
+    strextend (command, "-input %s", filename);
+    strextend (command, "-D ADDSTAR_RADIUS %f", options->radius);
+
+    fprintf (stderr, "command: %s\n", command);
+
+    // launch the job on the remote machine (no handshake)
+    int errorInfo = 0;
+    int pid = rconnect ("ssh", host->hostname, command, host->stdio, &errorInfo, FALSE);
+    if (!pid) {
+      if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", host->hostname, errorInfo);
+      exit (1);
+    }
+    host->pid = pid; // save for future reference
+    fprintf (stderr, "launch cpt %s on %s @ %d\n", region->name, host->pathname, pid);
+    
+    save_remote_host (host);
+  }
+  return TRUE;
+}
Index: /trunk/Ohana/src/addstar/src/loadICRF_table.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadICRF_table.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadICRF_table.c	(revision 37807)
@@ -0,0 +1,50 @@
+# include "addstar.h"
+# include "loadICRF.h"
+
+# define GET_COLUMN(OUT,NAME,TYPE) \
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+int loadICRF_table (SkyList *skylistInput, HostTable *hosts, char *filename, AddstarClientOptions *options) {
+  
+  int i, Nstars;
+
+  ICRF_Stars *stars = loadICRF_readstars (filename, &Nstars);
+
+  // sort the stars by RA
+  loadICRF_sortStars (stars, Nstars);
+
+  // scan through the stars, loading the containing catalogs
+  // skip through table for unsaved stars
+  for (i = 0; i < Nstars; i++) {
+    if (stars[i].flag) continue;
+
+    // scan forward until we read the UserPatch
+    if (stars[i].R < UserPatch.Rmin) continue;
+    if (stars[i].R > UserPatch.Rmax) break;
+    if (stars[i].D < UserPatch.Dmin) continue;
+    if (stars[i].D > UserPatch.Dmax) continue;
+
+    // identify the relevant catalog
+    SkyList *skylist = SkyRegionByPoint_List (skylistInput, -1, stars[i].R, stars[i].D);
+    if (skylist[0].Nregions == 0) {
+      SkyListFree (skylist);
+      continue;
+    }
+    SkyRegion *region = skylist[0].regions[0];
+
+    // select stars matching this region
+    int Nsubset;
+    ICRF_Stars *subset = loadICRF_make_subset (stars, Nstars, i, region, &Nsubset);
+
+    // In parallel mode, write out the subset to a disk file.  Block until a remote host
+    // is available.  In serial mode, just match against the appropriate region and save
+    loadICRF_save_remote (subset, Nsubset, hosts, region, skylist[0].filename[0], options);
+  }
+
+  // wait for last remote clients to finish
+  harvest_all ();
+
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/addstar/src/loadstarpar.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar.c	(revision 37807)
@@ -0,0 +1,54 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+/* This is the DVO program to upload Greg Green's stellar parametersq into a DVO database.
+   It is modeled on the loadwise program and is expected to be run only rarely (once?).
+   The stellar parameter data are delivered as *.fits files.  It does not allow a subset
+   of the sky to be uploaded; entire stellar parameter files are loaded if supplied on the
+   command line.
+
+   USAGE: loadstarpar -D CATDIR (catdir) (file.fits) [...more files]
+*/
+
+int main (int argc, char **argv) {
+
+  int i;
+  AddstarClientOptions options;
+
+  // need to construct these options with args_loadstarpar...
+  options = ConfigInit (&argc, argv);
+  options = args_loadstarpar (argc, argv, options);
+
+  // load the full sky description table (dvodb must exist)
+  SkyTable *sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, FALSE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  
+  // load the list of hosts
+  HostTable *hosts = NULL;
+  if (PARALLEL) {
+    hosts = HostTableLoad (CATDIR, sky->hosts);
+    if (!hosts) {
+      fprintf (stderr, "ERROR: failure reading Host Table %s for database %s\n", sky->hosts, CATDIR);
+      exit (1);
+    }    
+
+    // ensure that the paths are absolute path names
+    for (i = 0; i < hosts->Nhosts; i++) {
+      char *tmppath = abspath (hosts->hosts[i].pathname, DVO_MAX_PATH);
+      free (hosts->hosts[i].pathname);
+      hosts->hosts[i].pathname = tmppath;
+    }
+
+    // set up the array of active hosts
+    init_remote_hosts ();
+  }
+
+  // generate the subset matching the user-selected region
+  SkyList *skylist = SkyListByPatch (sky, -1, &UserPatch);
+
+  for (i = 1; i < argc; i++) {
+    fprintf (stderr, "loading %s\n", argv[i]);
+    loadstarpar_table (skylist, hosts, argv[i], &options);
+  }
+  exit (0);
+}  
Index: /trunk/Ohana/src/addstar/src/loadstarpar_catalog.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_catalog.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_catalog.c	(revision 37807)
@@ -0,0 +1,28 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+int loadstarpar_catalog (StarPar_Stars *stars, int Nstars, SkyRegion *region, char *filename, AddstarClientOptions *options) {
+
+  Catalog catalog;
+
+  // now we have all of the loaded stars in this catalog
+  catalog.filename = filename;
+  catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+  catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+  catalog.catflags = LOAD_AVES | LOAD_SECF | LOAD_STARPAR;
+  catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+    
+  // an error exit status here is a significant error
+  if (!dvo_catalog_open (&catalog, region, VERBOSE, "w")) {
+    fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+    exit (2);
+  }
+
+  find_matches_starpar (region, stars, Nstars, &catalog, options);
+    
+  dvo_catalog_save (&catalog, VERBOSE);
+  dvo_catalog_unlock (&catalog);
+  dvo_catalog_free (&catalog);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/addstar/src/loadstarpar_client.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_client.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_client.c	(revision 37807)
@@ -0,0 +1,27 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+int main (int argc, char **argv) {
+
+  AddstarClientOptions options;
+
+  // need to construct these options with args_loadstarpar...
+  options = ConfigInit (&argc, argv);
+  options = args_loadstarpar_client (argc, argv, options);
+
+  // client is called with a pointer to the file to be loaded
+
+  SkyTable *sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, FALSE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  
+  SkyList *skylist = SkyRegionByCPT (sky, CPT_FILE);
+
+  int Nstars;
+  StarPar_Stars *stars = loadstarpar_load_stars (INPUT, &Nstars);
+
+  loadstarpar_catalog (stars, Nstars, skylist->regions[0], CPT_FILE, &options);
+
+  free (stars);
+
+  exit (0);
+}
Index: /trunk/Ohana/src/addstar/src/loadstarpar_io.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_io.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_io.c	(revision 37807)
@@ -0,0 +1,191 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+int loadstarpar_save_stars (char *filename, StarPar_Stars *stars, int Nstars) {
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    myAbortF ("ERROR: cannot open file for output %s\n", filename);
+  }
+
+  gfits_fwrite_header  (f, &header);
+  gfits_fwrite_matrix  (f, &matrix);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  gfits_create_table_header (&theader, "BINTABLE", "STARPAR");
+
+  gfits_define_bintable_column (&theader, "D", "RA",       "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "DEC",      "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "GLON",     "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "GLAT",     "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "Ebv",      "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dEbv",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "DistMag",  "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dDistMag", "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "M_r",      "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dM_r",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "FeH",      "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dFeH",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "averef",   "", "", 1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "objID",    "", "", 1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "catID",    "", "", 1.0, FT_BZERO_INT32);
+
+  int i;
+  double       *ra       = NULL; ALLOCATE (ra      , double	 , Nstars); for (i = 0; i < Nstars; i++) ra      [i] = stars[i].starpar.R ;
+  double       *dec      = NULL; ALLOCATE (dec     , double	 , Nstars); for (i = 0; i < Nstars; i++) dec     [i] = stars[i].starpar.D ;
+  float        *glon     = NULL; ALLOCATE (glon    , float 	 , Nstars); for (i = 0; i < Nstars; i++) glon    [i] = stars[i].starpar.galLon  ;
+  float        *glat     = NULL; ALLOCATE (glat    , float 	 , Nstars); for (i = 0; i < Nstars; i++) glat    [i] = stars[i].starpar.galLat  ;
+  float        *Ebv      = NULL; ALLOCATE (Ebv     , float	 , Nstars); for (i = 0; i < Nstars; i++) Ebv     [i] = stars[i].starpar.Ebv     ;
+  float        *dEbv     = NULL; ALLOCATE (dEbv    , float	 , Nstars); for (i = 0; i < Nstars; i++) dEbv    [i] = stars[i].starpar.dEbv    ;
+  float        *DistMag  = NULL; ALLOCATE (DistMag , float	 , Nstars); for (i = 0; i < Nstars; i++) DistMag [i] = stars[i].starpar.DistMag ;
+  float        *dDistMag = NULL; ALLOCATE (dDistMag, float	 , Nstars); for (i = 0; i < Nstars; i++) dDistMag[i] = stars[i].starpar.dDistMag;
+  float        *M_r      = NULL; ALLOCATE (M_r     , float	 , Nstars); for (i = 0; i < Nstars; i++) M_r     [i] = stars[i].starpar.M_r     ;
+  float        *dM_r     = NULL; ALLOCATE (dM_r    , float	 , Nstars); for (i = 0; i < Nstars; i++) dM_r    [i] = stars[i].starpar.dM_r    ;
+  float        *FeH      = NULL; ALLOCATE (FeH     , float	 , Nstars); for (i = 0; i < Nstars; i++) FeH     [i] = stars[i].starpar.FeH     ;
+  float        *dFeH     = NULL; ALLOCATE (dFeH    , float	 , Nstars); for (i = 0; i < Nstars; i++) dFeH    [i] = stars[i].starpar.dFeH    ;
+  unsigned int *averef   = NULL; ALLOCATE (averef  , unsigned int, Nstars); for (i = 0; i < Nstars; i++) averef  [i] = stars[i].starpar.averef  ;
+  unsigned int *objID    = NULL; ALLOCATE (objID   , unsigned int, Nstars); for (i = 0; i < Nstars; i++) objID   [i] = stars[i].starpar.objID   ;
+  unsigned int *catID    = NULL; ALLOCATE (catID   , unsigned int, Nstars); for (i = 0; i < Nstars; i++) catID   [i] = stars[i].starpar.catID   ;
+
+  // generate the output array that carries the data
+  gfits_create_table (&theader, &ftable);
+
+  // add the columns to the output array
+  gfits_set_bintable_column (&theader, &ftable, "RA",       ra      , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "DEC",      dec     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "GLON",     glon    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "GLAT",     glat    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "Ebv",      Ebv     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dEbv",     dEbv    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "DistMag",  DistMag , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dDistMag", dDistMag, Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "M_r",      M_r     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dM_r",     dM_r    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "FeH",      FeH     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dFeH",     dFeH    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "averef",   averef  , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "objID",    objID   , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "catID",    catID   , Nstars);
+    
+  gfits_fwrite_Theader (f, &theader);
+  gfits_fwrite_table (f, &ftable);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  return TRUE;
+}
+
+
+# define GET_COLUMN(OUT,NAME,TYPE)					\
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+StarPar_Stars *loadstarpar_load_stars (char *filename, int *nstars) {
+
+  int i, Ncol;
+  off_t Nrow;
+  char type[16];
+
+  StarPar_Stars *stars = NULL;
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  header.buffer = NULL;
+  matrix.buffer = NULL;
+  ftable.buffer = NULL;
+  theader.buffer = NULL;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read header\n");
+    goto escape;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read matrix\n");
+    goto escape;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) goto escape;
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) goto escape;
+ 
+  GET_COLUMN(ra      , "RA"        ,   double);
+  GET_COLUMN(dec     , "DEC"       ,   double);
+  GET_COLUMN(glon    , "GLON"      ,   float);
+  GET_COLUMN(glat    , "GLAT"      ,   float);
+  GET_COLUMN(Ebv     , "Ebv"       ,   float);
+  GET_COLUMN(dEbv    , "dEbv"      ,   float);
+  GET_COLUMN(DistMag , "DistMag"   ,   float);
+  GET_COLUMN(dDistMag, "dDistMag"  ,   float);
+  GET_COLUMN(M_r     , "M_r"       ,   float);
+  GET_COLUMN(dM_r    , "dM_r"      ,   float);
+  GET_COLUMN(FeH     , "FeH"       ,   float);
+  GET_COLUMN(dFeH    , "dFeH"      ,   float);
+  GET_COLUMN(averef  , "averef"    ,   unsigned int);
+  GET_COLUMN(objID   , "objID"     ,   unsigned int);
+  GET_COLUMN(catID   , "catID"     ,   unsigned int);
+
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  ALLOCATE (stars, StarPar_Stars, Nrow);
+
+  for (i = 0; i < Nrow; i++) {
+    stars[i].R                = ra      [i];
+    stars[i].D	              = dec     [i];
+    stars[i].starpar.R        = ra      [i];
+    stars[i].starpar.D        = dec     [i];
+    stars[i].starpar.galLon   = glon    [i];
+    stars[i].starpar.galLat   = glat    [i];
+    stars[i].starpar.Ebv      = Ebv     [i];
+    stars[i].starpar.dEbv     = dEbv    [i];
+    stars[i].starpar.DistMag  = DistMag [i];
+    stars[i].starpar.dDistMag = dDistMag[i];
+    stars[i].starpar.M_r      = M_r     [i];
+    stars[i].starpar.dM_r     = dM_r    [i];
+    stars[i].starpar.FeH      = FeH     [i];
+    stars[i].starpar.dFeH     = dFeH    [i];
+    stars[i].starpar.averef   = averef  [i];
+    stars[i].starpar.objID    = objID   [i];
+    stars[i].starpar.catID    = catID   [i];
+  }
+
+  fclose (f);
+  *nstars = Nrow;
+  return stars;
+
+escape:
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  fclose (f);
+  return NULL;
+}
Index: /trunk/Ohana/src/addstar/src/loadstarpar_make_subset.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_make_subset.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_make_subset.c	(revision 37807)
@@ -0,0 +1,44 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+// assign stars in the given region to the subset (NOTE: stars are sorted by RA, start is
+// first entry in stars array in this region)
+
+StarPar_Stars *loadstarpar_make_subset (StarPar_Stars *stars, int Nstars, int start, SkyRegion *region, int *nsubset) {
+
+  int i;
+
+  StarPar_Stars *subset = NULL;
+
+  // collect array of (Stars *) stars in a new output catalog
+  int Nsubset = 0;
+  int NSUBSET = 3000;
+  ALLOCATE (subset, StarPar_Stars, NSUBSET);
+
+  // find the rest of the stars in this output region
+  for (i = start; i < Nstars; i++) {
+    if (stars[i].flag) continue;
+
+    // check if in skyregion
+    if (stars[i].R < region[0].Rmin) continue;
+    if (stars[i].R > region[0].Rmax) break;
+    if (stars[i].D < region[0].Dmin) continue;
+    if (stars[i].D > region[0].Dmax) continue;
+	  
+    // check if in UserPatch (a GLOBAL)
+    if (stars[i].R < UserPatch.Rmin) continue;
+    if (stars[i].R > UserPatch.Rmax) break;
+    if (stars[i].D < UserPatch.Dmin) continue;
+    if (stars[i].D > UserPatch.Dmax) continue;
+	  
+    subset[Nsubset] = stars[i];
+    Nsubset ++;
+
+    stars[i].flag = TRUE;
+
+    CHECK_REALLOCATE (subset, StarPar_Stars, NSUBSET, Nsubset, 10000);
+  }
+
+  *nsubset = Nsubset;
+  return subset;
+}
Index: /trunk/Ohana/src/addstar/src/loadstarpar_readstars.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_readstars.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_readstars.c	(revision 37807)
@@ -0,0 +1,164 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+# define GET_COLUMN(OUT,NAME,TYPE) \
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+StarPar_Stars *loadstarpar_readstars (char *filename, int *nstars) {
+
+  // read in the full FITS files ('cause I don't have a partial read option)
+  FILE *f = fopen (filename, "r");
+  if (f == NULL) Shutdown ("can't read stellar parameter file: %s", filename);
+
+  int i, Ncol;
+  off_t Nrow;
+
+  Header header;
+  Matrix matrix;
+  Header theader;
+  FTable ftable;
+  
+  // load in PHU segment (ignore)
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset header\n");
+    fclose (f);
+    return NULL;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset matrix\n");
+    gfits_free_header (&header);
+    fclose (f);
+    return NULL;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) {
+    fclose (f);
+    return NULL;
+  }
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
+    fclose (f);
+    return (NULL);
+  }
+
+  char type[16];
+
+  GET_COLUMN (glat,    "l",    float);
+  GET_COLUMN (glon,    "b",    float);
+  GET_COLUMN (conv,    "conv", byte);
+  GET_COLUMN (lnZ,     "lnZ",  float);
+  GET_COLUMN (DistMag, "DM",   float);
+  GET_COLUMN (Ebv,     "EBV",  float);
+  GET_COLUMN (M_r,     "Mr",   float);
+  GET_COLUMN (FeH,     "FeH",  float);
+
+  // free the memory associated with the FITS files
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  // the next FITS extension contains the error information
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset header\n");
+    fclose (f);
+    return NULL;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset matrix\n");
+    gfits_free_header (&header);
+    fclose (f);
+    return NULL;
+  }
+  fclose (f);
+
+  double *errImage = (double *) matrix.buffer;
+
+  myAssert (Nrow == matrix.Naxis[2], "size mismatch?");
+
+  int NstarsIn = matrix.Naxis[2];
+
+  double Rmin = +360.0;
+  double Rmax = -360.0;
+  double Dmin = +360.0;
+  double Dmax = -360.0;
+
+  int Nstars = 0;
+  int NSTARS = 0.1*NstarsIn;
+
+  StarPar_Stars *stars = NULL;
+  ALLOCATE (stars, StarPar_Stars, NSTARS);
+
+  // libdvo uses Liu et al 2011 (A&A 526, A16) for default galactic coords,
+  // but Greg Green / LSD use the older Reid et al 2004 (ApJ 616, 872) definition
+  CoordTransform *transform = InitTransform (COORD_GALACTIC_REID_2004, COORD_CELESTIAL);
+
+  for (i = 0; i < NstarsIn; i++) {
+
+    // skip stars based on conv and lnZ
+    if (!conv[i]) continue;
+    if (lnZ[i] < -15.0) continue;
+
+    double R, D;
+    ApplyTransform (&R, &D, glon[i], glat[i], transform);
+
+    Rmin = MIN (Rmin, R);
+    Rmax = MAX (Rmax, R);
+    Dmin = MIN (Dmin, D);
+    Dmax = MAX (Dmax, D);
+
+    float dEbv     = 0.5*(errImage[i*20 + 5*0 + 3] - errImage[i*20 + 5*0 + 1]);
+    float dDistMag = 0.5*(errImage[i*20 + 5*1 + 3] - errImage[i*20 + 5*1 + 1]);
+    float dM_r     = 0.5*(errImage[i*20 + 5*2 + 3] - errImage[i*20 + 5*2 + 1]);
+    float dFeH     = 0.5*(errImage[i*20 + 5*3 + 3] - errImage[i*20 + 5*3 + 1]);
+
+    stars[Nstars].R = R;
+    stars[Nstars].D = D;
+    stars[Nstars].flag  = FALSE;
+    stars[Nstars].found = FALSE;
+
+    // NOTE that we have both stars.R,D and stars.starpar.R,D.  stars.R,D are used 
+    // here (in parallel with addstar) to locate the objects.  BUT, in the database,
+    // starpar.R,D is the authoratative position for the object (either that coming 
+    // from Greg / Eddie's table or that coming from the simulation)
+    stars[Nstars].starpar.R      = R;
+    stars[Nstars].starpar.D      = D;
+    stars[Nstars].starpar.galLon = glon[i];
+    stars[Nstars].starpar.galLat = glat[i];
+
+    stars[Nstars].starpar.Ebv      = Ebv[i]    ;
+    stars[Nstars].starpar.dEbv     = dEbv      ;
+    stars[Nstars].starpar.DistMag  = DistMag[i];
+    stars[Nstars].starpar.dDistMag = dDistMag  ;
+    stars[Nstars].starpar.M_r      = M_r[i]    ;
+    stars[Nstars].starpar.dM_r     = dM_r      ;
+    stars[Nstars].starpar.FeH      = FeH[i]    ;
+    stars[Nstars].starpar.dFeH     = dFeH      ;
+
+    Nstars ++;
+
+    CHECK_REALLOCATE (stars, StarPar_Stars, NSTARS, Nstars, 10000);
+  }
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  *nstars = Nstars;
+  return (stars);
+}
+
+int loadstarpar_sortStars (StarPar_Stars *stars, int Nstars) {
+
+# define SWAPFUNC(A,B){ StarPar_Stars temp = stars[A]; stars[A] = stars[B]; stars[B] = temp; }
+# define COMPARE(A,B)(stars[A].R < stars[B].R)
+
+  OHANA_SORT (Nstars, COMPARE, SWAPFUNC);
+
+# undef SWAPFUNC
+# undef COMPARE
+  
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/addstar/src/loadstarpar_remote_hosts.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_remote_hosts.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_remote_hosts.c	(revision 37807)
@@ -0,0 +1,142 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+# include <sys/types.h>
+# include <sys/wait.h>
+
+# define DEBUG 1
+
+// we are running N parallel remote jobs on the remote hosts.  In the usual remote
+// processing (eg, relphot or relastro), we launch one job per remote host.  In this case,
+// we are launching one job foreach table (catalog.cpt) being touched.  individual hosts
+// may have more than one job active at a time.  
+
+int       Nhosts = 0;
+HostInfo **hosts = NULL;
+
+int init_remote_hosts () {
+
+  Nhosts = 10;
+  ALLOCATE (hosts, HostInfo *, Nhosts);
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    hosts[i] = NULL;
+  }
+  return TRUE;
+}
+
+int find_empty_slot () {
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    if (hosts[i] == NULL) return i;
+  }
+  return -1;
+}
+
+int harvest_all () {
+
+  int slot = -1;
+
+  while (slot != -2) {
+    slot = harvest_host ();
+    usleep (50000);
+  }
+  return TRUE;
+}
+ 
+// put the host in the list in a free slot. NOTE: this should never be called if we do not
+// already have a free slot.
+int save_remote_host (HostInfo *host) {
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    if (hosts[i] == NULL) {
+      hosts[i] = host;
+      return TRUE;
+    }
+  }
+  myAbort ("failed to find an empty slot: this is a programming error");
+  return FALSE;
+}
+
+// wait for all children to complete, report output to stdout
+// possible states when function is finished:
+// * no child was harvested, but children still exist
+// * no outstanding children
+// * a child was harvested
+int harvest_host () {
+
+  // check if any children have finished...
+  int status = 0;
+  int pid = waitpid (-1, &status, WNOHANG);
+
+  if (!pid) return -1; // there are outstanding children, but none have exited, no slot was opened
+  if ((pid == -1) && (errno == ECHILD)) return -2; // there are no outstanding children
+  if ((pid == -1) && (errno != ECHILD)) myAbort ("programming error?");
+
+  // find the host which has finished
+  int i;
+  int found = FALSE;
+  for (i = 0; (i < Nhosts) && !found; i++) {
+    if (!hosts[i]) continue; // unassigned slot
+    if (hosts[i][0].pid != pid) continue;
+    found = TRUE;
+    break;
+  }
+  myAssert (found, "Programming error: failed to matched finished job to known host!");
+  int slot = i;
+
+  HostInfo *host = hosts[slot];
+
+  // check on the status of this job and report any output
+  if (DEBUG) fprintf (stdout, "job finished for %s (%d)\n", host->hostname, pid);
+
+  int Nout, printHead;
+
+  // read stdout
+  EmptyIOBuffer (&host->stdout, 100, host->stdio[HOST_STDOUT]);
+  printHead = VERBOSE || (host->stdout.Nbuffer > 0);
+  if (printHead) fprintf (stdout, "--- stdout from %s --- (%d bytes, v2)\n", host->hostname, host->stdout.Nbuffer);
+  Nout = write (STDOUT_FILENO, host->stdout.buffer, host->stdout.Nbuffer);
+  if (Nout != host->stdout.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+  FlushIOBuffer (&host->stdout);
+  if (printHead) fprintf (stdout, "\n");
+	    
+  // read stderr
+  EmptyIOBuffer (&host->stderr, 100, host->stdio[HOST_STDERR]);
+  printHead = VERBOSE || (host->stderr.Nbuffer > 0);
+  if (printHead) fprintf (stdout, "--- stderr from %s --- (%d bytes, v2)\n", host->hostname, host->stderr.Nbuffer);
+  Nout = write (STDOUT_FILENO, host->stderr.buffer, host->stderr.Nbuffer);
+  if (Nout != host->stderr.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+  FlushIOBuffer (&host->stderr);
+  if (printHead) fprintf (stdout, "\n");
+
+  if (WIFEXITED(status)) {
+    if (DEBUG) fprintf (stdout, "normal completion, exit status is %d\n", WEXITSTATUS(status));
+    host->status = WEXITSTATUS(status);
+    if (host->status) {
+      fprintf (stdout, "job failed on %s\n", host->hostname);
+    }
+  } else {
+    host->status = -1;
+    fprintf (stdout, "job exited abnormally on %s\n", host->hostname);
+  }
+
+  // close opened connections
+  close (host->stdio[HOST_STDIN]);
+  close (host->stdio[HOST_STDOUT]);
+  close (host->stdio[HOST_STDERR]);
+
+  // free data associated with the host
+  free (host->hostname);
+  free (host->pathname);
+  FreeIOBuffer (&host->stdout);
+  FreeIOBuffer (&host->stderr);
+  free (host);
+
+  // free the slot
+  hosts[slot] = NULL;
+  return slot; 
+}
+
Index: /trunk/Ohana/src/addstar/src/loadstarpar_save_remote.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_save_remote.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_save_remote.c	(revision 37807)
@@ -0,0 +1,84 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+# define DEBUG 1
+
+int strextend (char *input, char *format,...) {
+
+  char tmpextra[1024], tmpline[1024];
+  va_list argp;
+
+  va_start (argp, format);
+  vsnprintf (tmpextra, 1024, format, argp);
+  snprintf (tmpline, 1024, "%s %s", input, tmpextra);
+  strcpy (input, tmpline);
+
+  return TRUE;
+}
+
+int loadstarpar_save_remote (StarPar_Stars *stars, int Nstars, HostTable *hosts, SkyRegion *region, char *fullname, AddstarClientOptions *options) {
+
+  char uniquer[12];
+  int TIME = time(NULL);
+  int PID = getpid();
+  snprintf (uniquer, 12, "%05d.%05d", PID, TIME % 100000);
+
+  // if this region is a parallel thing, save and launch remote
+  if (!PARALLEL) { 
+    loadstarpar_catalog (stars, Nstars, region, fullname, options);
+  } else {
+    int N = hosts->index[region->hostID];
+    HostInfo *hostMach = &hosts->hosts[N];
+
+    // save to a unique filename
+    char filename[1024]; // CATDIR/tmpdir/starpar.PID.index.fits
+    snprintf (filename, 1024, "%s/tmpdir/starpar.%s.%05d.fits", CATDIR, uniquer, region->index);
+
+    // write the data to the given FITS file
+    loadstarpar_save_stars (filename, stars, Nstars);
+
+    int slot = -1;
+    while (slot == -1) {
+      slot = find_empty_slot ();
+      if (slot == -1) {
+	usleep (50000);
+	slot = harvest_host();
+	myAssert (slot != -2, "we should not call harvest_host here if we have open slots");
+      }
+    }
+
+    // allocate a host for this job
+    HostInfo *host = NULL;
+    ALLOCATE (host, HostInfo, 1);
+
+    // we want to run this job on the host described by hostMach.  copy
+    // immutable data from hostMach to a locally allocated host:
+    host->hostID = hostMach->hostID;
+    host->hostname = strcreate (hostMach->hostname);
+    host->pathname = strcreate (hostMach->pathname);
+    InitIOBuffer (&host->stdout, 1000);
+    InitIOBuffer (&host->stderr, 1000);
+
+    // got a valid slot, so launch a new host
+
+    // need to generate the remote command
+    char command[1024];
+    snprintf (command, 1024, "loadstarpar_client");
+    strextend (command, "-hostID %d", host->hostID);
+    strextend (command, "-D CATDIR %s", CATDIR);
+    strextend (command, "-hostdir %s", host->pathname);
+    strextend (command, "-cpt %s", region->name);
+    strextend (command, "-input %s", filename);
+
+    // launch the job on the remote machine (no handshake)
+    int errorInfo = 0;
+    int pid = rconnect ("ssh", host->hostname, command, host->stdio, &errorInfo, FALSE);
+    if (!pid) {
+      if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", host->hostname, errorInfo);
+      exit (1);
+    }
+    host->pid = pid; // save for future reference
+    
+    save_remote_host (host);
+  }
+  return TRUE;
+}
Index: /trunk/Ohana/src/addstar/src/loadstarpar_table.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadstarpar_table.c	(revision 37807)
+++ /trunk/Ohana/src/addstar/src/loadstarpar_table.c	(revision 37807)
@@ -0,0 +1,50 @@
+# include "addstar.h"
+# include "loadstarpar.h"
+
+# define GET_COLUMN(OUT,NAME,TYPE) \
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+int loadstarpar_table (SkyList *skylistInput, HostTable *hosts, char *filename, AddstarClientOptions *options) {
+  
+  int i, Nstars;
+
+  StarPar_Stars *stars = loadstarpar_readstars (filename, &Nstars);
+
+  // sort the stars by RA
+  loadstarpar_sortStars (stars, Nstars);
+
+  // scan through the stars, loading the containing catalogs
+  // skip through table for unsaved stars
+  for (i = 0; i < Nstars; i++) {
+    if (stars[i].flag) continue;
+
+    // scan forward until we read the UserPatch
+    if (stars[i].R < UserPatch.Rmin) continue;
+    if (stars[i].R > UserPatch.Rmax) break;
+    if (stars[i].D < UserPatch.Dmin) continue;
+    if (stars[i].D > UserPatch.Dmax) continue;
+
+    // identify the relevant catalog
+    SkyList *skylist = SkyRegionByPoint_List (skylistInput, -1, stars[i].R, stars[i].D);
+    if (skylist[0].Nregions == 0) {
+      SkyListFree (skylist);
+      continue;
+    }
+    SkyRegion *region = skylist[0].regions[0];
+
+    // select stars matching this region
+    int Nsubset;
+    StarPar_Stars *subset = loadstarpar_make_subset (stars, Nstars, i, region, &Nsubset);
+
+    // In parallel mode, write out the subset to a disk file.  Block until a remote host
+    // is available.  In serial mode, just match against the appropriate region and save
+    loadstarpar_save_remote (subset, Nsubset, hosts, region, skylist[0].filename[0], options);
+  }
+
+  // wait for last remote clients to finish
+  harvest_all ();
+
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/addstar/src/loadsupercos_plates.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadsupercos_plates.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/loadsupercos_plates.c	(revision 37807)
@@ -118,5 +118,5 @@
 
     // for now, we define a fake coordinate system based on the boresite center
-    strcpy (image[Nimage].coords.ctype, "DEC--TAN");
+    InitCoords (&image[Nimage].coords, "DEC--TAN");
     
     image[Nimage].coords.crval1 = RAo;
@@ -126,12 +126,4 @@
     image[Nimage].coords.crpix2 = 0.5*image[Nimage].NY;
     image[Nimage].coords.cdelt1 = image[Nimage].coords.cdelt2 = scale;
-
-    image[Nimage].coords.pc1_1 = 0.0;
-    image[Nimage].coords.pc1_2 = 1.0;
-    image[Nimage].coords.pc2_1 = 1.0;
-    image[Nimage].coords.pc2_2 = 0.0;
-
-    image[Nimage].coords.Npolyterms = 0;
-    memset (image[Nimage].coords.polyterms, 0, 2*7*sizeof(float));
 
     Nimage ++;
Index: /trunk/Ohana/src/addstar/src/loadwise.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadwise.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/loadwise.c	(revision 37807)
@@ -40,5 +40,5 @@
   for (i = 1; i < argc; i++) {
       fprintf (stderr, "loading %s\n", argv[i]);
-      loadwise_rawdata (skylist, argv[i], options, ALLSKY);
+      loadwise_rawdata (skylist, argv[i], options);
   }
   exit (0);
Index: /trunk/Ohana/src/addstar/src/loadwise_rawdata.c
===================================================================
--- /trunk/Ohana/src/addstar/src/loadwise_rawdata.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/loadwise_rawdata.c	(revision 37807)
@@ -10,5 +10,5 @@
 # define DEBUG 0
 
-int loadwise_rawdata (SkyList *skytable, char *filename, AddstarClientOptions options, int ALLSKY) {
+int loadwise_rawdata (SkyList *skytable, char *filename, AddstarClientOptions options) {
   
   int i, j, verbose;
@@ -159,8 +159,15 @@
 	stars[Nstars+3][0].average.D = tstars[j].D;
 	
-	if (ALLSKY) {
-	  loadwise_star_allsky (&stars[Nstars], &buffer[offset], Nbyte - offset);
-	} else {
-	  loadwise_star_prelim (&stars[Nstars], &buffer[offset], Nbyte - offset);
+	switch (MODE) {
+	  case MODE_PRELIM:
+	    loadwise_star_prelim (&stars[Nstars], &buffer[offset], Nbyte - offset);
+	    break;
+	  case MODE_ALLSKY:
+	    loadwise_star_allsky (&stars[Nstars], &buffer[offset], Nbyte - offset);
+	    break;
+	  case MODE_ALLWISE:
+	  default:
+	    // loadwise_star_allwise (&stars[Nstars], &buffer[offset], Nbyte - offset);
+	    break;
 	}
 
Index: /trunk/Ohana/src/addstar/src/mkcmf.c
===================================================================
--- /trunk/Ohana/src/addstar/src/mkcmf.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/mkcmf.c	(revision 37807)
@@ -200,5 +200,5 @@
     
   /* bore site center guess */
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
   coords.crval1 = RA;
   coords.crval2 = DEC;
@@ -208,10 +208,4 @@
   coords.cdelt1 = 0.25/3600.0;
   coords.cdelt2 = 0.25/3600.0;
-
-  coords.pc1_1  = 1;
-  coords.pc2_2  = 1;
-  coords.pc1_2  = 0;
-  coords.pc2_1  = 0;
-  coords.Npolyterms = 1;
 
   // load stars and generate complete output fields
@@ -300,6 +294,6 @@
   gfits_modify (&header, "NASTRO",   "%d", 1, 100); 
 
-  if (imageID == -1) imageID = 1000.0*drand48();
-  if (sourceID == -1) sourceID = 100.0*drand48();
+  if (imageID == -1) imageID = 100000.0*drand48();
+  if (sourceID == -1) sourceID = 1000.0*drand48();
   gfits_modify (&header, "IMAGEID",  "%d", 1, imageID);
   gfits_modify (&header, "SOURCEID", "%d", 1, sourceID);
Index: /trunk/Ohana/src/addstar/src/resort_catalog.c
===================================================================
--- /trunk/Ohana/src/addstar/src/resort_catalog.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/resort_catalog.c	(revision 37807)
@@ -4,4 +4,5 @@
 void resort_catalog_measure (Catalog *catalog);
 void resort_catalog_lensing (Catalog *catalog);
+void resort_catalog_starpar (Catalog *catalog);
 
 void resort_catalog_old (Catalog *catalog) {
@@ -38,4 +39,5 @@
   resort_catalog_measure (catalog);
   resort_catalog_lensing (catalog);
+  resort_catalog_starpar (catalog);
 }
 
@@ -335,4 +337,141 @@
 }
 
+void resort_catalog_starpar (Catalog *catalog) {
+
+  off_t Naverage, Nstarpar;
+  StarPar *starpar;
+  Average *average;
+  off_t i, j, N, currentAve;
+
+  off_t *starparSeq = NULL;
+  off_t *averageSeq = NULL;
+  StarPar *starparTMP = NULL;
+
+  // struct timeval start, stop;
+  // gettimeofday (&start, NULL);
+
+  /* internal counters */
+  Nstarpar = catalog[0].Nstarpar;
+  Naverage = catalog[0].Naverage;
+
+  if (!Nstarpar) return;
+
+  starpar = catalog[0].starpar;
+  average = catalog[0].average;
+  
+  // we have a table of average objects and an unsorted table of measurements.  each measurement
+  // has a reference to the average object sequence (as well as an ID)
+  // starpar[i].averef -> average[averef]
+  // starpar[i].objID = average[averef].objID
+  // starpar[i].catID = average[averef].catID
+
+  // we want a sorted starpar array with all averef entries in sequence
+
+  ALLOCATE (starparSeq, off_t,   Nstarpar);
+  ALLOCATE (averageSeq, off_t,   Nstarpar);
+
+  for (i = 0; i < Nstarpar; i++) {
+    starparSeq[i] = i;
+    averageSeq[i] = starpar[i].averef;
+    
+    if (catalog[0].catformat >= DVO_FORMAT_PS1_V1) {
+      // earlier formats did not carry the objID or catID, so they are not available (we could assign on load, but we don't)
+      myAssert(average[averageSeq[i]].catID == starpar[starparSeq[i]].catID, "object / detection mismatch");
+# if (1)
+      myAssert(average[averageSeq[i]].objID == starpar[starparSeq[i]].objID, "object / detection mismatch");
+# else
+      // for reasons I do not understand, the mini dvodbs generated on stsci1X had a handful of detections with an inconsistency between averef and objID.  
+      // this happened for 28 detections in the dbs on /data/stsci1?.0/eugene/dvo3pi.20130616, but not at all (as far as I know) in the rest of LAP DVO
+      if (average[averageSeq[i]].objID != starpar[starparSeq[i]].objID) {
+	fprintf (stderr, "R");
+	starpar[starparSeq[i]].objID = average[averageSeq[i]].objID; // XXX I don't really like this...
+      }
+# endif
+    }
+  }
+  
+  // check that averageSeq is now in order
+  // for (i = 1; i < Nstarpar; i++) {
+  //   if (averageSeq[i] < averageSeq[i-1]) {
+  //     fprintf (stderr, "%d ", (int) i);
+  //   }
+  // }
+  // fprintf (stderr, "\n");
+
+  SortAveMatch(starparSeq, averageSeq, Nstarpar);
+  // MARKTIME("sort : %f sec\n", dtime);
+
+  // check that averageSeq is now in order
+  // for (i = 1; i < Nstarpar; i++) {
+  //   if (averageSeq[i] < averageSeq[i-1]) {
+  //     fprintf (stderr, "%d ", (int) i);
+  //   }
+  // }
+  // fprintf (stderr, "\n");
+
+  // copy the starpar entries in the sorted order
+  ALLOCATE (starparTMP, StarPar, Nstarpar);
+  for (i = 0; i < Nstarpar; i++) {
+    j = starparSeq[i];
+    starparTMP[i] = starpar[j];
+  }
+  // MARKTIME("assign starpar : %f sec\n", dtime);
+
+  // update the values of average.starparOffset and average.Nstarpar
+  FREE(starpar);
+  catalog[0].starpar = starparTMP;
+
+  N = 0;
+  currentAve = averageSeq[0];
+  average[currentAve].starparOffset = 0;
+  for (i = 0; i < Nstarpar; i++) {
+    if (averageSeq[i] != currentAve) {
+      // we have hit the next entry in the list
+      average[currentAve].Nstarpar = N;
+      N = 0;
+      currentAve = averageSeq[i];
+      average[currentAve].starparOffset = i;
+    }
+    N++;
+  }
+  // N++;
+  average[currentAve].Nstarpar = N;
+  // MARKTIME("update Nstarpar : %f sec\n", dtime);
+
+  int NstarparTotal = 0;
+  int starparOffsetOK = TRUE;
+  for (i = 0; i < Naverage; i++) {
+    NstarparTotal += catalog[0].average[i].Nstarpar;
+    if (VERBOSE && !(NstarparTotal <= catalog[0].Nstarpar)) {
+      fprintf (stderr, "too few starpar: %d %d %d\n", (int) i, NstarparTotal, (int) catalog[0].Nstarpar);
+    }
+    starparOffsetOK &= (catalog[0].average[i].starparOffset < catalog[0].Nstarpar);
+    if (VERBOSE && !(catalog[0].average[i].starparOffset < catalog[0].Nstarpar)) {
+      fprintf (stderr, "offset too large: %d %d %d\n", (int) i, catalog[0].average[i].Nstarpar, (int) catalog[0].Nstarpar);
+    }
+    starparOffsetOK &= (catalog[0].average[i].starparOffset + catalog[0].average[i].Nstarpar <= catalog[0].Nstarpar);
+    if (VERBOSE && !(catalog[0].average[i].starparOffset + catalog[0].average[i].Nstarpar <= catalog[0].Nstarpar)) {
+      fprintf (stderr, "orrset + Nstarpar too large: %d + %d > %d %d\n", (int) i, catalog[0].average[i].starparOffset, catalog[0].average[i].Nstarpar, (int) catalog[0].Nstarpar);
+    }
+  }
+
+  if (!starparOffsetOK) {
+    fprintf (stderr, "ERROR: catalog %s has an invalid starparOffset\n", catalog[0].filename);
+  }
+
+  if (NstarparTotal != catalog[0].Nstarpar) {
+    fprintf (stderr, "ERROR: catalog %s has an invalid Nstarpar\n", catalog[0].filename);
+  }
+
+  // MARKTIME("  match time %9.4f sec for %7lld starpars, %6lld average\n", dtime, (long long) Nstarpar, (long long) Naverage);
+
+  catalog[0].sorted = TRUE;
+
+  FREE (starparSeq);
+  FREE (averageSeq);
+
+  return;
+}
+
 // sort the measure or lensing Sequence based on the average Sequence entries
 void SortAveMatch (off_t *MEAS, off_t *AVE, off_t N) {
Index: /trunk/Ohana/src/addstar/src/sky_tessalation.c
===================================================================
--- /trunk/Ohana/src/addstar/src/sky_tessalation.c	(revision 37806)
+++ /trunk/Ohana/src/addstar/src/sky_tessalation.c	(revision 37807)
@@ -581,4 +581,7 @@
 
   memset (rectangle, 0, sizeof(SkyRectangle));
+
+  InitCoords (&rectangle[0].coords, "DEC--TAN");
+
   rectangle[0].coords.crval1 = CENTER_RA;
   rectangle[0].coords.crval2 = CENTER_DEC;
@@ -609,6 +612,4 @@
   rectangle[0].coords.cdelt2 = SCALE / 3600.0;
 
-  strcpy (rectangle[0].coords.ctype, "DEC--TAN");
-
   rectangle[0].NX = NX;
   rectangle[0].NY = NY;
@@ -699,12 +700,10 @@
   for (i = 0; i < nRA; i++) {
     memset (&ring[i], 0, sizeof(SkyRectangle));
-    memset (&ring[i].coords, 0, sizeof(Coords));
+
+    InitCoords (&ring[i].coords, "DEC--TAN");
     ring[i].coords.crval1 = i*dRA;
     ring[i].coords.crval2 = dec;
 
     ring[i].coords.pc1_1 = +1.0 * X_PARITY;
-    ring[i].coords.pc1_2 = +0.0;
-    ring[i].coords.pc2_1 = -0.0;
-    ring[i].coords.pc2_2 = +1.0;
   
     // range values are in projected degrees
@@ -718,6 +717,4 @@
     ring[i].coords.cdelt1 = SCALE / 3600.0;
     ring[i].coords.cdelt2 = SCALE / 3600.0;
-
-    strcpy (ring[i].coords.ctype, "DEC--TAN");
 
     ring[i].NX = NX*(1.0 + PADDING);
@@ -771,5 +768,5 @@
 
       memset (&ring[N], 0, sizeof(SkyRectangle));
-      memset (&ring[N].coords, 0, sizeof(Coords));
+      InitCoords (&ring[N].coords, "DEC--TAN");
 
       ring[N].coords.crval1 = ra / d2r;
@@ -779,7 +776,4 @@
 
       ring[N].coords.pc1_1 = +1.0 * X_PARITY;
-      ring[N].coords.pc1_2 = +0.0;
-      ring[N].coords.pc2_1 = -0.0;
-      ring[N].coords.pc2_2 = +1.0;
   
       // range values are in projected degrees
@@ -793,6 +787,4 @@
       ring[N].coords.cdelt1 = SCALE / 3600.0;
       ring[N].coords.cdelt2 = SCALE / 3600.0;
-
-      strcpy (ring[N].coords.ctype, "DEC--TAN");
 
       ring[N].NX = NX*(1.0 + PADDING);
@@ -1099,12 +1091,6 @@
 
   ALLOCATE (refcoords, Coords, 1);
-  refcoords[0].crval1 = refcoords[0].crval2 = 0.0;
-  refcoords[0].crpix1 = refcoords[0].crpix2 = 0.0;
+  InitCoords (refcoords, "DEC--TAN");
   refcoords[0].cdelt1 = refcoords[0].cdelt2 = scale / 3600;
-  refcoords[0].pc1_1 = refcoords[0].pc2_2 = 1.0;
-  refcoords[0].pc1_2 = refcoords[0].pc2_1 = 0.0;
-  refcoords[0].Npolyterms = 0;
-  memset (refcoords[0].polyterms, 0, 14*sizeof(float));
-  strcpy (refcoords[0].ctype, "DEC--TAN");
   return (TRUE);
 }
Index: /trunk/Ohana/src/addstar/test/relphot.parallel.dvo
===================================================================
--- /trunk/Ohana/src/addstar/test/relphot.parallel.dvo	(revision 37806)
+++ /trunk/Ohana/src/addstar/test/relphot.parallel.dvo	(revision 37807)
@@ -25,17 +25,23 @@
   if (not($?VERBOSE)) set VERBOSE = 0
 
-  # images are loaded into dvo with GPC1 photcodes, so we need to get the nominal zps for those filters
-  $zpt_nominal:g = 24.58
-  $zpt_nominal:r = 24.80
-  $zpt_nominal:i = 24.74
-  $zpt_nominal:z = 24.26
-  $zpt_nominal:y = 23.41
-
-  # klam is negative, so klam*(airmass - 1) increase the zero point
-  $klam_nominal:g = -0.15
-  $klam_nominal:r = -0.10
-  $klam_nominal:i = -0.04
-  $klam_nominal:z = -0.03
-  $klam_nominal:y = -0.03
+# images are loaded into dvo with GPC1 photcodes, so we need to get the nominal zps for those filters
+# $zpt_nominal:g = 24.58
+# $zpt_nominal:r = 24.80
+# $zpt_nominal:i = 24.74
+# $zpt_nominal:z = 24.26
+# $zpt_nominal:y = 23.41
+
+# klam is negative, so klam*(airmass - 1) increase the zero point
+# $klam_nominal:g = -0.15
+# $klam_nominal:r = -0.10
+# $klam_nominal:i = -0.04
+# $klam_nominal:z = -0.03
+# $klam_nominal:y = -0.03
+
+  $zpt_nominal:g = 24.563; $klam_nominal:g = -0.147
+  $zpt_nominal:r = 24.750; $klam_nominal:r = -0.085
+  $zpt_nominal:i = 24.611; $klam_nominal:i = -0.044
+  $zpt_nominal:z = 24.250; $klam_nominal:z = -0.033
+  $zpt_nominal:y = 23.320; $klam_nominal:y = -0.073
 
   # we have two sets of images: ubercaled and not-ubercaled
@@ -111,5 +117,5 @@
   mksequence $fileroot $catdir
   for i 0 mjd_nc[]
-    ckexposure $catdir mjd_nc[$i] zpt_nc[$i] exptime_nc[$i] airmass_nc[$i] $filt_nc:$i raw
+    ckexposure $catdir mjd_nc[$i] zpt_nc[$i] exptime_nc[$i] airmass_nc[$i] $filt_nc:$i raw 0
   end
 
@@ -121,5 +127,5 @@
 
   # run relphot on the serial db and check that the images now match the expected values
-  exec relphot g,r,i -v -region 9.5 10.5 19.5 20.5 -D CATDIR $catdir -D STAR_TOOFEW 1 -D SIGMA_LIM 0.07 -statmode WT_MEAN -cloud-limit 0.5 -keep-ubercal -D IMAGE_OFFSET 0.5 -update >& log.relphot.s0
+  exec relphot -images g,r,i -v -region 9.5 10.5 19.5 20.5 -D CATDIR $catdir -D STAR_TOOFEW 1 -D SIGMA_LIM 0.07 -statmode WT_MEAN -cloud-limit 0.5 -D IMAGE_OFFSET 0.5 -update >& log.relphot.s0
 
   for i 0 mjd_nc[]
@@ -128,11 +134,9 @@
     # matching the mean zp of photometric data, which we calculate in
     # 'init'
-    ckexposure $catdir mjd_nc[$i] $zpt_nc_$filt_nc:$i exptime_nc[$i] airmass_nc[$i] $filt_nc:$i relphot_nc
+    ckexposure $catdir mjd_nc[$i] $zpt_nc_$filt_nc:$i exptime_nc[$i] airmass_nc[$i] $filt_nc:$i relphot_nc 0
   end
 
   # run relphot on the parallel db and check that the images now match the expected values
-  exec relphot -parallel g,r,i -v -region 9.5 10.5 19.5 20.5 -D CATDIR $catdir.p0 -D STAR_TOOFEW 1 -D SIGMA_LIM 0.07 -statmode WT_MEAN -cloud-limit 0.5 -keep-ubercal -D IMAGE_OFFSET 0.5 -update >& log.relphot.p0
-
-  exec dvodist -in $catdir.p0 >& log.dvodist.in
+  exec relphot -images -parallel g,r,i -v -region 9.5 10.5 19.5 20.5 -D CATDIR $catdir.p0 -D STAR_TOOFEW 1 -D SIGMA_LIM 0.07 -statmode WT_MEAN -cloud-limit 0.5 -D IMAGE_OFFSET 0.5 -update >& log.relphot.p0
 
   for i 0 mjd_nc[]
@@ -141,5 +145,5 @@
     # matching the mean zp of photometric data, which we calculate in
     # 'init'
-    ckexposure $catdir.p0 mjd_nc[$i] $zpt_nc_$filt_nc:$i exptime_nc[$i] airmass_nc[$i] $filt_nc:$i relphot_nc
+    ckexposure $catdir.p0 mjd_nc[$i] $zpt_nc_$filt_nc:$i exptime_nc[$i] airmass_nc[$i] $filt_nc:$i relphot_nc 1
   end
 
@@ -148,6 +152,6 @@
 
 macro ckexposure
-  if ($0 != 8)
-    echo "ckexposure (catdir) (mjd) (zpt) (exptime) (airmass) (filter) (mode)"
+  if ($0 != 9)
+    echo "ckexposure (catdir) (mjd) (zpt) (exptime) (airmass) (filter) (mode) (isParallel)"
     echo "  mode == raw or corr"
     break
@@ -163,4 +167,8 @@
   $FILTER      = $6
   $MODE        = $7
+  $PARALLEL    =
+  if ($8) 
+    $PARALLEL = "-parallel"
+  end
 
   # XXX need a function to extract the nominal zpt for a given filter / photcode from the db
@@ -182,5 +190,5 @@
 
   skyregion {$RA_CENTER - 0.2/dcos($DEC_CENTER)} {$RA_CENTER + 0.2/dcos($DEC_CENTER)} {$DEC_CENTER - 0.2} {$DEC_CENTER + 0.2} 
-  mextract ra dec mag xccd yccd where (abs(time - $MJD_IMAGE) < 0.0001)
+  mextract $PARALLEL ra dec mag xccd yccd where (abs(time - $MJD_IMAGE) < 0.0001)
   if (not(ra[])) 
     echo "no matching data for MJD = $MJD_IMAGE"
@@ -344,5 +352,5 @@
     # the fake images have inconsistent ra,dec and airmass,sidtime values
     if ($VERBOSE) echo addstar $ROOT.$ix.$iy.cmf -D CATDIR $CATDIR -D CAMERA gpc1 -quick-airmass
-    exec addstar $ROOT.$ix.$iy.cmf -D CATDIR $CATDIR -D CAMERA gpc1 -quick-airmass >& /dev/null
+    exec addstar $ROOT.$ix.$iy.cmf -D CATDIR $CATDIR -D CAMERA gpc1 -quick-airmass >& log.addstar
     # costs 10/36 sec / chip
   end
@@ -432,4 +440,5 @@
   $catdir = $tmp1/$tmp2
 
+  if ($VERBOSE) echo rsync -auv $catdir/ $catdir.p0/
   exec rsync -auv $catdir/ $catdir.p0/ >& log.rsync
   mkdir $catdir.d1
@@ -442,4 +451,5 @@
   exec echo "3 $hostname $catdir.d3" >> $catdir.p0/HostTable.dat
 
+  if ($VERBOSE) echo dvodist -out $catdir.p0
   exec dvodist -out $catdir.p0 >& log.dvodist.out
 end
Index: /trunk/Ohana/src/checkastro/src/ImageOps.c
===================================================================
--- /trunk/Ohana/src/checkastro/src/ImageOps.c	(revision 37806)
+++ /trunk/Ohana/src/checkastro/src/ImageOps.c	(revision 37807)
@@ -130,6 +130,4 @@
   }
 
-  BuildChipMatch (image, Nimage);
-
   char output[128];
   snprintf (output, 128, "checkastrom.%+3.0f.%+3.0f.%03.0f.%03.0f.dat", UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax);
@@ -148,14 +146,9 @@
     name = GetPhotcodeNamebyCode (image[i].photcode);
 
-    off_t myMosaic = FindMosaicForImage (image, Nimage, i);
-
     /* only check exposure center */
     double Rexp = NAN, Dexp = NAN, Rccd = NAN, Dccd = NAN;
 
-    if (myMosaic) {
-      myMosaic --;
-      XY_to_RD (&Rccd, &Dccd, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
-      XY_to_RD (&Rexp, &Dexp, 0.0, 0.0, &image[myMosaic].coords);
-    }
+    XY_to_RD (&Rccd, &Dccd, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
+    XY_to_RD (&Rexp, &Dexp, 0.0, 0.0, &image[i].parent->coords);
 
     char *date = ohana_sec_to_date(image[i].tzero);
Index: /trunk/Ohana/src/checkastro/src/bcatalog.c
===================================================================
--- /trunk/Ohana/src/checkastro/src/bcatalog.c	(revision 37806)
+++ /trunk/Ohana/src/checkastro/src/bcatalog.c	(revision 37807)
@@ -191,13 +191,8 @@
   /* project coordinates to a plane centered on the object with units of arcsec */
   Coords coords;
+  InitCoords (&coords, "DEC--SIN");
   coords.crval1 = Rc;
   coords.crval2 = Dc;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   // convert all average R,D values to X,Y:
Index: /trunk/Ohana/src/checkastro/src/load_images.c
===================================================================
--- /trunk/Ohana/src/checkastro/src/load_images.c	(revision 37806)
+++ /trunk/Ohana/src/checkastro/src/load_images.c	(revision 37807)
@@ -17,4 +17,6 @@
   MARKTIME("  convert image table: %f sec\n", dtime);
 
+  BuildChipMatch (image, Nimage);
+
   // select the images which overlap the selected sky regions
   subset = select_images (skylist, image, Nimage, &LineNumber, &Nsubset);
Index: /trunk/Ohana/src/checkastro/src/select_images.c
===================================================================
--- /trunk/Ohana/src/checkastro/src/select_images.c	(revision 37806)
+++ /trunk/Ohana/src/checkastro/src/select_images.c	(revision 37807)
@@ -43,6 +43,4 @@
 
   if (VERBOSE) fprintf (stderr, "finding images\n");
-  BuildChipMatch (timage, Ntimage);
-  MARKTIME("build chip match: %f sec\n", dtime);
 
   nimage = 0;
@@ -83,17 +81,9 @@
     }
     
-    // this adds 1.3 sec for 3M images
-    off_t myMosaic = FindMosaicForImage (timage, Ntimage, i);
-    if (!myMosaic) {
-      fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
-    myMosaic --;
-
     /* only check exposure center */
     if (!strcmp(&timage[i].coords.ctype[4], "-DIS")) {
       XY_to_RD (&Rexp, &Dexp, 0.0, 0.0, &timage[i].coords);
     } else {
-      XY_to_RD (&Rexp, &Dexp, 0.0, 0.0, &timage[myMosaic].coords);
+      XY_to_RD (&Rexp, &Dexp, 0.0, 0.0, &timage[i].parent->coords);
     }
     Rexp = ohana_normalize_angle_to_midpoint (Rexp, RmidSkyRegion);
Index: /trunk/Ohana/src/delstar/src/ImageOpsFixLAP.c
===================================================================
--- /trunk/Ohana/src/delstar/src/ImageOpsFixLAP.c	(revision 37806)
+++ /trunk/Ohana/src/delstar/src/ImageOpsFixLAP.c	(revision 37807)
@@ -320,12 +320,8 @@
     if ((Nimage_valid[i] + Nimage_invalid[i] == 0) || (found_ratio < 0.995)) {
       double r, d;
-      if (!FindMosaicForImage (image, Nimage, i)) { 
-	fprintf (stderr, "failed to get astrometry for %s\n", image[i].name);
+      if (!strcmp(&image[i].coords.ctype[4], "-DIS")) {
+	XY_to_RD (&r, &d, 0.0, 0.0, &image[i].coords);
       } else {
-	if (!strcmp(&image[i].coords.ctype[4], "-DIS")) {
-	  XY_to_RD (&r, &d, 0.0, 0.0, &image[i].coords);
-	} else {
-	  XY_to_RD (&r, &d, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
-	}
+	XY_to_RD (&r, &d, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
       }
       char *date = ohana_sec_to_date (image[i].tzero);
Index: /trunk/Ohana/src/delstar/src/delete_duplicate_measures.c
===================================================================
--- /trunk/Ohana/src/delstar/src/delete_duplicate_measures.c	(revision 37806)
+++ /trunk/Ohana/src/delstar/src/delete_duplicate_measures.c	(revision 37807)
@@ -61,9 +61,13 @@
     }
 
-    delete_duplicate_measures_catalog (&catalog);
-
-    if (UPDATE) {
-      // XXX skip if nothing was deleted
-      dvo_catalog_save_complete (&catalog, VERBOSE2);
+    if (delete_duplicate_measures_catalog (&catalog)) {
+      // skip if nothing was deleted
+      if (UPDATE) {
+	if (!dvo_catalog_backup (&catalog, TRUE)) {
+	  fprintf (stderr, "ERROR: failed to make backup for catalog %s\n", catalog.filename);
+	  exit (1);
+	}
+	dvo_catalog_save_complete (&catalog, VERBOSE2);
+      }
     }
 
@@ -234,5 +238,5 @@
     measureDrop[j] = TRUE;
     off_t N = measure[j].averef;
-    fprintf (stderr, "0x%08x 0x%08x %8.4f %8.4f %5d\n", measure[j].imageID, measure[j].detID, average[N].R, average[N].D, measure[j].photcode);
+    if (VERBOSE) fprintf (stderr, "0x%08x 0x%08x %8.4f %8.4f %5d\n", measure[j].imageID, measure[j].detID, average[N].R, average[N].D, measure[j].photcode);
   }
 
@@ -385,4 +389,5 @@
   off_t NdelAves = Naverage - catalog[0].Naverage;
   off_t NdelMeas = Nmeasure - catalog[0].Nmeasure;
+
   if (NdelAves || NdelMeas) {
     fprintf (stderr, "deleting "OFF_T_FMT" measures and "OFF_T_FMT" averages : %s\n",  NdelMeas, NdelAves, catalog[0].filename);
@@ -404,5 +409,6 @@
   FREE (measureAveOut);
 
-  return TRUE;
+  if (NdelAves || NdelMeas) return TRUE;
+  return FALSE;
 }
 
Index: /trunk/Ohana/src/delstar/src/delete_imagename.c
===================================================================
--- /trunk/Ohana/src/delstar/src/delete_imagename.c	(revision 37806)
+++ /trunk/Ohana/src/delstar/src/delete_imagename.c	(revision 37807)
@@ -24,4 +24,6 @@
   }
 
+  BuildChipMatch (image, Nimage);
+
   /* find image in db by name */
   imlist = find_images_name (db, IMAGENAME, &Nimlist);
@@ -33,6 +35,4 @@
     if (VERBOSE) fprintf (stderr, "deleting %s\n", image[j].name);
     
-    // XXX EAM : need to handle failure
-    FindMosaicForImage (image, Nimage, j);
     skylist = SkyListByImage (sky, -1, &image[j]);
 
Index: /trunk/Ohana/src/delstar/src/delete_times.c
===================================================================
--- /trunk/Ohana/src/delstar/src/delete_times.c	(revision 37806)
+++ /trunk/Ohana/src/delstar/src/delete_times.c	(revision 37807)
@@ -31,4 +31,6 @@
   }
 
+  BuildChipMatch (image, Nimage);
+
   /* find images for time range, delete each image */ 
   imlist = find_images_time (db, START, END, PHOTCODE, &Nimlist);
@@ -39,6 +41,4 @@
     if (VERBOSE) fprintf (stderr, "finding regions for %s\n", image[j].name);
 
-    // XXX EAM : need to handle failure
-    FindMosaicForImage (image, Nimage, j);
     skyset = SkyListByImage (sky, -1, &image[j]);
 
Index: /trunk/Ohana/src/dvomerge/Makefile
===================================================================
--- /trunk/Ohana/src/dvomerge/Makefile	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/Makefile	(revision 37807)
@@ -23,5 +23,7 @@
 dvoverify_client : $(BIN)/dvoverify_client.$(ARCH)
 
-all: dvomerge dvomerge_client dvoconvert dvosecfilt dvosecfilt_client dvoverify dvoverify_client
+dvoutils        : $(BIN)/dvoutils.$(ARCH)
+
+all: dvomerge dvomerge_client dvoconvert dvosecfilt dvosecfilt_client dvorepair dvoverify dvoverify_client dvoutils
 
 #  $(SRC)/dvomergeContinue.$(ARCH).o
@@ -120,8 +122,12 @@
 $(SRC)/dvorepairImagesVsMeasures.$(ARCH).o \
 $(SRC)/dvorepairDeleteImageList.$(ARCH).o \
+$(SRC)/dvorepairDeleteImagesByExternID.$(ARCH).o \
+$(SRC)/dvorepairDeleteImagesByExternID.$(ARCH).o \
 $(SRC)/dvorepairFixImages.$(ARCH).o \
 $(SRC)/psps_ids.$(ARCH).o \
 $(SRC)/LoadImages.$(ARCH).o \
 $(SRC)/ReadDeleteList.$(ARCH).o \
+$(SRC)/ReadDeleteListExternID.$(ARCH).o \
+$(SRC)/myIndex.$(ARCH).o \
 $(SRC)/match_image.$(ARCH).o \
 $(SRC)/help.$(ARCH).o \
@@ -149,5 +155,14 @@
 $(BIN)/dvoverify_client.$(ARCH) : $(DVOVERIFY_CLIENT)
 
-INSTALL = dvomerge dvomerge_client dvoconvert dvosecfilt dvosecfilt_client dvorepair dvoverify dvoverify_client
+DVOUTILS = \
+$(SRC)/dvoutils.$(ARCH).o \
+$(SRC)/dvoutils_args.$(ARCH).o \
+$(SRC)/dvoutils_uniq_images.$(ARCH).o \
+$(SRC)/dvoutils_load_image_index.$(ARCH).o
+
+$(DVOUTILS)  : $(INC)/dvoutils.h
+$(BIN)/dvoutils.$(ARCH) : $(DVOUTILS)
+
+INSTALL = dvomerge dvomerge_client dvoconvert dvosecfilt dvosecfilt_client dvorepair dvoverify dvoverify_client dvoverify
 
 # dependancy rules for binary code #########################
Index: /trunk/Ohana/src/dvomerge/include/dvomerge.h
===================================================================
--- /trunk/Ohana/src/dvomerge/include/dvomerge.h	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/include/dvomerge.h	(revision 37807)
@@ -41,7 +41,22 @@
 int    REPLACE_BY_PHOTCODE;
 int    FORCE_MERGE;
+char  *UPDATE_CATFORMAT;
+
+int    MATCHED_TABLES;
 
 char *SINGLE_CPT;
 SkyRegion UserPatch;  // used by MODE CAT
+
+# ifndef MAX_INT
+# define MAX_INT 2147483647
+# endif
+
+typedef struct {
+  int minID;
+  int maxID;
+  int *index;
+  int Nindex;
+  int NINDEX;
+} myIndexType;
 
 typedef struct {
@@ -138,4 +153,9 @@
 int   	   add_lens_link     	  PROTO((Average *average, off_t *next, off_t Nlensing, off_t NLENSING));
 Lensing   *sort_lensing     	  PROTO((Average *average, off_t Naverage, Lensing *lensing, off_t Nlensing, off_t *next));
+
+off_t 	  *build_starpar_links    PROTO((Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar));
+off_t 	  *init_starpar_links     PROTO((Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar));
+int   	   add_star_link     	  PROTO((Average *average, off_t *next, off_t Nstarpar, off_t NSTARPAR));
+StarPar   *sort_starpar     	  PROTO((Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar, off_t *next));
 
 off_t 	  *init_missing_links     PROTO((Average *average, off_t Naverage, Missing *missing, off_t Nmissing));
@@ -200,2 +220,12 @@
 int dvoCreateID (char *catdir);
 
+myIndexType *myIndexInit ();
+int myIndexFree (myIndexType *myIndex);
+int myIndexUpdateLimits (myIndexType *myIndex, int value);
+int myIndexSetRange (myIndexType *myIndex);
+int myIndexSetEntry (myIndexType *myIndex, int value, int entry);
+int myIndexGetEntry (myIndexType *myIndex, int value);
+
+int dvorepairDeleteImagesByExternID (int argc, char **argv);
+int RepairTableCPT_V1(char *cptFilenameSrc, char *cptFilenameTgt, char *cpsFilenameSrc, char *cpsFilenameTgt, Measure *measure, off_t Nmeasure, Image *image, off_t Nimage, myIndexType *imageIDindex, char catformat);
+int *ReadDeleteListExternID(char *filename, int *nindex);
Index: /trunk/Ohana/src/dvomerge/include/dvoutils.h
===================================================================
--- /trunk/Ohana/src/dvomerge/include/dvoutils.h	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/include/dvoutils.h	(revision 37807)
@@ -0,0 +1,34 @@
+# include <ohana.h>
+# include <dvo.h>
+# include <signal.h>
+# include <sys/time.h>
+# include <time.h>
+# include <zlib.h>
+
+/* solaris requires both of these instead of ip.h:
+   # include <sys/socket.h>
+   # include <netinet/in.h>
+*/
+
+/* linux is happy with this, not solaris */
+# include <netinet/ip.h>
+# include <netdb.h>
+# include <arpa/inet.h>
+# include <glob.h>
+
+typedef enum {
+  DVOUTILS_NONE,
+  DVOUTILS_UNIQ_IMAGES,
+} DVOUTILS_OP_TYPE;
+
+int VERBOSE;
+int DVOUTILS_OP;
+
+char *IMAGES_LIST;
+
+int main (int argc, char **argv);
+int dvoutils_args (int *argc, char **argv);
+int dvoutils_uniq_images (char *filename);
+int *dvoutils_load_image_index (char *filename, int *nindex);
+
+Image *dvoutils_load_image_table (char *filename, int *nimage);
Index: /trunk/Ohana/src/dvomerge/src/ImageOps.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/ImageOps.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/ImageOps.c	(revision 37807)
@@ -18,5 +18,4 @@
   }
   if (m == -1) return (NULL);
-  if (!FindMosaicForImage (image, Nimage, m)) return (NULL);
   return (&image[m]);
 }
Index: /trunk/Ohana/src/dvomerge/src/LoadCatalog.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/LoadCatalog.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/LoadCatalog.c	(revision 37807)
@@ -8,5 +8,5 @@
 
     // always load all of the data (if any exists)
-    catalog[0].catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF | LOAD_LENSING | LOAD_LENSOBJ;
+    catalog[0].catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF | LOAD_LENSING | LOAD_LENSOBJ | LOAD_STARPAR;
 
     catalog[0].catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
Index: /trunk/Ohana/src/dvomerge/src/ReadDeleteListExternID.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/ReadDeleteListExternID.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/ReadDeleteListExternID.c	(revision 37807)
@@ -0,0 +1,93 @@
+# include "dvomerge.h"
+
+int *ReadDeleteListExternID(char *filename, int *nindex) {
+
+  int j, index, Nindex, NINDEX, *indexList, Nline;
+  int Nstart, Nbytes, Nread, status;
+  char *c0, *c1, *space, *buffer;
+
+  char *indexPoint = NULL;
+  FILE *f = fopen (filename, "r");
+  myAssert(f, "failed to open delete list");
+  
+  Nindex = 0;
+  NINDEX = 1000;
+  ALLOCATE(indexList, int, NINDEX);
+
+  ALLOCATE (buffer, char, 0x10001);
+  bzero (buffer, 0x10001);
+
+  Nstart = 0;
+  Nline = 0;
+  while (1) {
+    Nbytes = 0x10000 - Nstart;
+    bzero (&buffer[Nstart], Nbytes);
+    Nread = fread (&buffer[Nstart], 1, Nbytes, f);
+    if (ferror (f)) {
+      perror ("error reading data file");
+      exit (1);
+    }
+    if (Nread == 0) break; // end of data
+    // nbytes = Nread + Nstart;
+    
+    status = TRUE;
+    c0 = buffer; 
+    while (status) {
+      // identify the range of the line
+      c1 = strchr (c0, '\n');
+      if (c1 == (char *) NULL) {
+	Nstart = strlen (c0);
+	memmove (buffer, c0, Nstart);
+	break;
+      } else {
+	*c1 = 0;
+      }      
+      if (*c0 == '#') goto next_line;
+
+      // confirm we have 9 fields broken by 8 spaces:
+      space = c0; // pointer to track the space-separated words
+      indexPoint = c0; // pointer to the ID on this line
+
+      for (j = 0; j < 8; j++) {
+	space = strchr(space, ' '); 
+	if (!space) {
+	  fprintf (stderr, "line only has %d word(s)\n", j + 1);
+	  goto next_line;
+	}
+	space ++;
+      }
+
+      // save the value we have found:
+      index = atoi(indexPoint);
+      indexList[Nindex] = index;
+
+      // fprintf (stderr, "index: %d, line: %s\n", index, c0);
+
+      Nindex ++;
+      if (Nindex == NINDEX) {
+	NINDEX += 1000;
+	REALLOCATE (indexList, int, NINDEX);
+      }
+      
+    next_line:
+      Nline ++;
+      c0 = c1 + 1;
+    }
+  }
+
+  *nindex = Nindex;
+  return (indexList);
+}
+
+/* Delete List Format:
+   image o5252g0035o.140669.cm.51017.smf[XY11.hdr] (219974) : 368 vs 375
+   image o5252g0051o.140685.cm.51123.smf[XY11.hdr] (226417) : 350 vs 356
+   image o5399g0207o.194950.cm.98351.smf[XY46.hdr] (1683277) : 10341 vs 10347
+   image o5464g0284o.230438.cm.123774.smf[XY21.hdr] (2634913) : 214 vs 242
+   image o5464g0305o.230459.cm.123795.smf[XY21.hdr] (2635758) : 232 vs 257
+   image o5465g0306o.231171.cm.124201.smf[XY01.hdr] (2654941) : 0 vs 494
+   image o5465g0306o.231171.cm.124201.smf[XY02.hdr] (2654942) : 10 vs 813
+   image o5465g0306o.231171.cm.124201.smf[XY10.hdr] (2654947) : 0 vs 534
+   image o5465g0306o.231171.cm.124201.smf[XY11.hdr] (2654948) : 0 vs 447
+*/
+
Index: /trunk/Ohana/src/dvomerge/src/args.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/args.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/args.c	(revision 37807)
@@ -37,4 +37,11 @@
   }
 
+  /* extra error messages */
+  MATCHED_TABLES = FALSE;
+  if ((N = get_argument (*argc, argv, "-matched-tables"))) {
+    MATCHED_TABLES = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
   /* use a different photcode file to define mean values */
   if ((N = get_argument (*argc, argv, "-photcode-file"))) {
@@ -48,4 +55,11 @@
   if ((N = get_argument (*argc, argv, "-force-merge"))) {
     FORCE_MERGE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  UPDATE_CATFORMAT = NULL;
+  if ((N = get_argument (*argc, argv, "-update-catformat"))) {
+    remove_argument (N, argc, argv);
+    UPDATE_CATFORMAT = strcreate (argv[N]);
     remove_argument (N, argc, argv);
   }
@@ -185,4 +199,11 @@
   }
 
+  /* extra error messages */
+  MATCHED_TABLES = FALSE;
+  if ((N = get_argument (*argc, argv, "-matched-tables"))) {
+    MATCHED_TABLES = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
   /* replace measurement, don't duplicate */
   REPLACE_BY_PHOTCODE = FALSE;
Index: /trunk/Ohana/src/dvomerge/src/build_links.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/build_links.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/build_links.c	(revision 37807)
@@ -382,3 +382,153 @@
 }
 
-
+/********  StarPar ********************************************************************************/
+
+/* build the initial links assuming the table is sorted, 
+   not partial, and has a correct set of average[].starparOffset,Nstarpar values */
+off_t *init_starpar_links (Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar) {
+
+  off_t i, j, N;
+  off_t *next_star;
+
+  N = 0;
+
+  ALLOCATE (next_star, off_t, Nstarpar);
+  for (i = 0; i < Naverage; i++) {
+    if (!average[i].Nstarpar) continue;
+    for (j = 0; j < average[i].Nstarpar - 1; j++, N++) {
+      next_star[N] = N + 1;
+      if (N >= Nstarpar) {
+	fprintf (stderr, "WARNING: N out of bounds (1)\n");
+      }
+    }
+    next_star[N] = -1;
+    if (N >= Nstarpar) {
+      fprintf (stderr, "WARNING: N out of bounds (2)\n");
+    }
+
+    if (N >= Nstarpar) {
+      fprintf (stderr, "overflow in init_starpar_links\n");
+      abort ();
+    }
+    N++;
+  }
+  return (next_star);
+}
+
+/* construct starpar links which are valid FOR THIS LOAD
+ * - if we have a full load, we will get links which can
+ *   be used by other programs (eg, relphot, etc)
+ * - if we have a partial load, the links are only valid
+ *   for that partial load
+ */ 
+
+off_t *build_starpar_links (Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar) {
+
+  off_t i, m, k, Nm, averef;
+  off_t *next_star;
+
+  ALLOCATE (next_star, off_t, Nstarpar);
+
+  /* reset the Nm, offset values for average */
+  for (i = 0; i < Naverage; i++) {
+    average[i].starparOffset = -1;
+    average[i].Nstarpar     =  0;
+  }
+
+  for (Nm = 0; Nm < Nstarpar; Nm++) {
+    averef = starpar[Nm].averef;
+    m = average[averef].starparOffset;  
+    next_star[Nm] = -1;
+
+    if (m == -1) { /* no links yet for source */
+      average[averef].starparOffset = Nm;
+      average[averef].Nstarpar     = 1;
+      continue;
+    }
+
+    for (k = 0; next_star[m] != -1; k++) {
+      m = next_star[m];
+      if (m >= Nstarpar) {
+	fprintf (stderr, "WARNING: m out of bounds (1)\n");
+      }
+    }
+
+    average[averef].Nstarpar = k + 2;
+    next_star[m] = Nm;
+    if (m >= Nstarpar) {
+      fprintf (stderr, "WARNING: m out of bounds (2)\n");
+    }
+  }
+  return (next_star);
+}
+
+/* average[].starparOffset, average[].Nstarpar are valid within an addstar run */
+int add_star_link (Average *average, off_t *next_star, off_t Nstarpar, off_t NSTARPAR) {
+
+  off_t k, m;
+
+  /* if we have trouble, check validity of next_star[m] : m < Nstarpar */
+  m = average[0].starparOffset;  
+
+  for (k = 0; k < average[0].Nstarpar - 1; k++)  {
+    m = next_star[m];
+    if (m >= NSTARPAR) {
+      fprintf (stderr, "WARNING: m out of bounds (3)\n");
+    }
+  }
+
+  /* set up references */
+  next_star[Nstarpar] = -1;
+  if (Nstarpar >= NSTARPAR) {
+    fprintf (stderr, "WARNING: Nstarpar out of bounds (1)\n");
+  }
+
+  if (m == -1) {
+    average[0].starparOffset = Nstarpar;
+  } else {
+    next_star[m] = Nstarpar;
+    if (m >= NSTARPAR) {
+      fprintf (stderr, "WARNING: m out of bounds (4)\n");
+    }
+  }
+
+  return (TRUE);
+}
+
+StarPar *sort_starpar (Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar, off_t *next_star) {
+
+  off_t i, k, n, np, N;
+  StarPar *tmpstarpar;
+
+  /*
+  for (i = 0; i < Naverage; i++) {
+    if (average[i].Nstarpar != 4) {
+      fprintf (stderr, "check %d %d %d\n", (int) i, (int) average[i].Nstarpar, (int) average[i].starparOffset);
+    }
+  }
+  */
+
+  /* fix order of StarPar (memory intensive, but fast) */
+  N = 0; 
+  ALLOCATE (tmpstarpar, StarPar, Nstarpar);
+  for (i = 0; i < Naverage; i++) {
+    if (!average[i].Nstarpar) continue;
+    n = average[i].starparOffset;
+    average[i].starparOffset = N;
+    for (k = 0; k < average[i].Nstarpar; k++, N++) {
+      if (n == -1) {
+	fprintf (stderr, "entry after %d has a problem\n", (int) np);
+	abort();
+      }
+      tmpstarpar[N] = starpar[n]; 
+      if (starpar[n].averef != i) abort();
+      tmpstarpar[N].averef = i;
+      np = n;
+      n = next_star[n];
+    }
+  }
+  free (starpar);
+  return (tmpstarpar);
+}
+
+
Index: /trunk/Ohana/src/dvomerge/src/dvo_image_merge_dbs.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvo_image_merge_dbs.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvo_image_merge_dbs.c	(revision 37807)
@@ -113,9 +113,4 @@
   create_IDmap_lookup (IDmap);
 
-  if (!out[0].swapped) {
-    gfits_convert_Image ((Image *) out[0].ftable.buffer, sizeof(Image), Nout);
-    out[0].swapped = TRUE;
-  }
-
   Nout += Nimages;
   IDstart += Nimages;
Index: /trunk/Ohana/src/dvomerge/src/dvomergeUpdate_catalogs.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeUpdate_catalogs.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeUpdate_catalogs.c	(revision 37807)
@@ -58,8 +58,20 @@
     }
 
+# ifdef OLD_CODE
+    // NOTE: i was having trouble dropping objects on edges with the option below.  Since an object can move outside the catalog grab the neighbors.
     // SkyListByBounds will return neighbor catalogs if the boundaries exactly match (due to rounding).  Since the regions are not infinitely small, 
     // compare to a slightly reduced footprint
     float dPos = 2.0/3600.0;
     outlist = SkyListByBounds (outsky, -1, inlist[0].regions[i][0].Rmin + dPos, inlist[0].regions[i][0].Rmax - dPos, inlist[0].regions[i][0].Dmin + dPos, inlist[0].regions[i][0].Dmax - dPos);
+# endif
+
+    if (MATCHED_TABLES) {
+      // if we know the output will have the same depth / layout as the input, just choose the matched table
+      outlist = SkyRegionByIndex (outsky, inlist[0].regions[i][0].index);
+    } else {
+      // Since an object can move outside the catalog grab the neighbors.
+      float dPos = 2.0/3600.0;
+      outlist = SkyListByBounds (outsky, -1, inlist[0].regions[i][0].Rmin - dPos, inlist[0].regions[i][0].Rmax + dPos, inlist[0].regions[i][0].Dmin - dPos, inlist[0].regions[i][0].Dmax + dPos);
+    }
 
     OutputStatus *outstat = OutputStatusInit (outlist->Nregions);
@@ -163,7 +175,11 @@
       LoadCatalog (&outcatalog, outlist[0].regions[j], outcatalog.filename, "w", NsecfiltOutput);
 
-      // if no catalog already exists, use the input catalog to define the format
+      // if no catalog already exists, use the input catalog to define the format or the specified format
       if (outcatalog.Naves_disk == 0) {
-	outcatalog.catformat = incatalog.catformat;
+	if (UPDATE_CATFORMAT) {
+	  outcatalog.catformat = dvo_catalog_catformat (UPDATE_CATFORMAT);
+	} else {
+	  outcatalog.catformat = incatalog.catformat;
+	}
       }
 
@@ -187,9 +203,13 @@
       SetProtect (FALSE);
 
+      if (!dvo_catalog_unlock (&outcatalog)) {
+	fprintf (stderr, "ERROR: failed to unlock catalog %s\n", outlist[0].filename[j]);
+	exit (1);
+      }
+
       if (!dvo_catalog_unlink_backup (&outcatalog, TRUE)) {
 	fprintf (stderr, "WARNING: failed to remove backup for catalog %s\n", outlist[0].filename[j]);
       }
 
-      dvo_catalog_unlock (&outcatalog);
       dvo_catalog_free (&outcatalog);
 
@@ -256,4 +276,5 @@
     if (PARALLEL_INPUT)      { snprintf (tmpline, DVO_MAX_PATH, "%s -parallel-input",  command); strcpy (command, tmpline); }
     if (FORCE_MERGE)         { snprintf (tmpline, DVO_MAX_PATH, "%s -force-merge",     command); strcpy (command, tmpline); }
+    if (MATCHED_TABLES)      { snprintf (tmpline, DVO_MAX_PATH, "%s -matched-tables",  command); strcpy (command, tmpline); }
 
     // add some config variables:
Index: /trunk/Ohana/src/dvomerge/src/dvorepair.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepair.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvorepair.c	(revision 37807)
@@ -3,7 +3,7 @@
 int main (int argc, char **argv) {
 
-  fprintf (stderr, "this program needs to be updated to load old format Measure tables (pre PV1_V5) in which dR,dD are saved, not R,D\n");
-  fprintf (stderr, "reminder: relastro can re-construct R,D from X,Y; FtableToMeasure and vice versa could just NAN those values\n");
-  exit (2);
+  // fprintf (stderr, "this program needs to be updated to load old format Measure tables (pre PV1_V5) in which dR,dD are saved, not R,D\n");
+  // fprintf (stderr, "reminder: relastro can re-construct R,D from X,Y; FtableToMeasure and vice versa could just NAN those values\n");
+  // exit (2);
 
   dvorepair_help(argc, argv);
@@ -13,4 +13,5 @@
   if (!strcmp(argv[1], "-images-vs-measures")) dvorepairImagesVsMeasures(argc, argv);
   if (!strcmp(argv[1], "-delete-image-list")) dvorepairDeleteImageList(argc, argv);
+  if (!strcmp(argv[1], "-delete-images-by-extern-id")) dvorepairDeleteImagesByExternID(argc, argv);
   if (!strcmp(argv[1], "-fix-images")) dvorepairFixImages(argc, argv);
   dvorepair_help(0, NULL);
Index: /trunk/Ohana/src/dvomerge/src/dvorepairCPT.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairCPT.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairCPT.c	(revision 37807)
@@ -119,4 +119,5 @@
     thisImage = MatchImage (image, Nimage, measure[i].t, measure[i].photcode, measure[i].imageID);
     XY_to_RD (&average[Nave].R, &average[Nave].D, measure[i].Xccd, measure[i].Yccd, &thisImage[0].coords);
+    average[Nave].R = ohana_normalize_angle (average[Nave].R);
 
     average[Nave].Nmeasure = 1;
Index: /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImageList.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImageList.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImageList.c	(revision 37807)
@@ -114,8 +114,4 @@
     index = deleteIDs[i];
     seq = imageIdx[index];
-    if (!FindMosaicForImage (image, Nimage, seq)) {
-      fprintf (stderr, "cannot find mosaic for %s, skipping\n", image[seq].name);
-      continue;
-    }
 
     XY_to_RD(&Rthis, &Dthis, 0, 0, &image[seq].coords);
@@ -404,4 +400,5 @@
     thisImage = MatchImage (image, Nimage, measure[i].t, measure[i].photcode, measure[i].imageID);
     XY_to_RD (&average[Nave].R, &average[Nave].D, measure[i].Xccd, measure[i].Yccd, &thisImage[0].coords);
+    average[Nave].R = ohana_normalize_angle (average[Nave].R);
 
     average[Nave].Nmeasure = 1;
Index: /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImagesByExternID.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImagesByExternID.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImagesByExternID.c	(revision 37807)
@@ -0,0 +1,770 @@
+# include "dvomerge.h"
+
+// delete images based on a text table of the target IDs
+// * load the delete table file (validate format)
+// * load the Images.dat file
+// * create an index for imageID (seq = imageIDindex[i])
+// * create an index of the imageIDs to be deleted (delete (T/F) = imageDELindex[i])
+// * determine the full RA & DEC range of the images to be deleted
+// * scan over all catalog files in the specified RA & DEC range
+// * load the cpm file (pad if short, identify the padded section)
+// * create a new cpm file
+// * loop over detections : only keep those not from images to be deleted
+// * load the cpt file
+// * rebuild the cpt file or delete the matching entries?
+
+int SaveImageTable (Image *image, off_t Nimage, char *catdir, FITS_DB *oldDB);
+Image *DeleteSelectedImages (Image *image, off_t Nimage, int *deleteImage, myIndexType *imageIDindex, off_t *NnewImage);
+
+int dvorepairDeleteImagesByExternID (int argc, char **argv) {
+
+  FITS_DB db;  // database handle pointing to input image table
+  
+  int i, j, N, NmeasureNew, Ndelete, Nvalid;
+  off_t Nimage, Nmeasure;
+  int nPass, raPass;
+
+  Image *image;
+  Measure *measure;
+  Measure *measureNew;
+
+  char *cpmFilenameSrc = NULL;
+  char *cptFilenameSrc = NULL;
+  char *cpsFilenameSrc = NULL;
+  char *cpmFilenameTgt = NULL;
+  char *cptFilenameTgt = NULL;
+  char *cpsFilenameTgt = NULL;
+
+  char *imageFilename = NULL;
+
+  char catformat;
+
+  N = get_argument (argc, argv, "-delete-images-by-extern-id");
+  myAssert(N == 1, "programming error: -delete-images-by-extern-id must be first argument");
+  remove_argument (N, &argc, argv);
+
+  VERBOSE = FALSE;
+  if ((N = get_argument (argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: dvorepair -delete-images-by-extern-id (catdir) (deleteList)\n");
+    fprintf (stderr, "  catdir : database of interest\n");
+    fprintf (stderr, "  deleteList : table from dvorepair giving images to be deleted\n");
+    exit (2);
+  }
+
+  char *catdir = argv[1];
+  char *delList = argv[2];
+
+  // load the image data
+  ALLOCATE(imageFilename, char, strlen(catdir) + 12);
+  sprintf (imageFilename, "%s/Images.dat", catdir);
+  if ((image = LoadImages (&db, imageFilename, &Nimage)) == NULL) return (FALSE);
+  BuildChipMatch (image, Nimage); // needed for astrometric tranformations
+
+  // generate an index for imageIDs
+  myIndexType *externIDindex = myIndexInit();
+  myIndexType *imageIDindex = myIndexInit();
+
+  // set the min and max ID values for the two strucures:
+  for (i = 0; i < Nimage; i++) {
+    myIndexUpdateLimits (imageIDindex,  image[i].imageID);
+
+    if (!image[i].externID) continue;
+    myAssert (image[i].imageID, "image must have imageID > 0");
+    myIndexUpdateLimits (externIDindex, image[i].externID);
+  }
+
+  myIndexSetRange (externIDindex);
+  myIndexSetRange (imageIDindex);
+
+  fprintf (stderr, "loaded %d images, image ID range: %d - %d, extern ID range: %d - %d\n", (int) Nimage, imageIDindex->minID, imageIDindex->maxID, externIDindex->minID, externIDindex->maxID);
+			
+  // deleteImage[i] will be TRUE if we want to delete that image
+  int *deleteImage = NULL;
+  ALLOCATE (deleteImage, int, Nimage);
+  for (i = 0; i < Nimage; i++) {
+    deleteImage[i] = FALSE;
+  }
+
+  // assign the externIDs and imageIDs to their index structures
+  for (i = 0; i < Nimage; i++) {
+    myIndexSetEntry (imageIDindex,  image[i].imageID,  i);
+
+    if (!image[i].externID) continue;
+    myIndexSetEntry (externIDindex, image[i].externID, i);
+  }
+
+  // read the list of externIDs to delete
+  int NdeleteIDs;
+  int *deleteIDs = ReadDeleteListExternID(delList, &NdeleteIDs);
+
+  int NdeleteImages = 0;
+  for (i = 0; i < NdeleteIDs; i++) {
+    int externID = deleteIDs[i];
+    myAssert(externID > 0, "extern ID to delete cannot be <= 0");
+    
+    // we do not require all externIDs to be deleted to actually be in this db,
+    // but skip any externIDs not in this db (myIndexGetEntry returns -1)
+    int n = myIndexGetEntry(externIDindex, externID);
+    if (n < 0) continue;
+
+    if (deleteImage[n]) {
+      fprintf (stderr, "multiple entries for extern ID %d == imageID %d, re-run\n", externID, image[n].imageID);
+    }
+    deleteImage[n] = TRUE;
+    NdeleteImages ++;
+
+    if (VERBOSE) fprintf (stderr, "delete %s extern ID: %d = image ID %d (seq %d)\n", image[n].name, externID, image[n].imageID, n);
+  }
+
+  if (NdeleteImages == 0) {
+    fprintf (stderr, "no images to be deleted in this database, exiting\n");
+    exit (0);
+  }
+
+  // find the RA & DEC range of the images we want to delete
+  double Rmin = 360.0; 
+  double Rmax = 0.0;
+  double Dmin = +90.0;
+  double Dmax = -90.0;
+  double Qmin = 360.0;
+  double Qmax =   0.0;
+  for (i = 0; i < Nimage; i++) {
+    if (!deleteImage[i]) continue;
+
+    double Rthis, Dthis, Qthis;
+    XY_to_RD(&Rthis, &Dthis, 0, 0, &image[i].coords);
+    Rmin = MIN(Rthis, Rmin);
+    Rmax = MAX(Rthis, Rmax);
+    Dmin = MIN(Dthis, Dmin);
+    Dmax = MAX(Dthis, Dmax);
+    Qthis = (Rthis < 180.0) ? Rthis : Rthis - 360.0;
+    Qmin = MIN(Qthis, Qmin);
+    Qmax = MAX(Qthis, Qmax);
+
+    // fprintf (stderr, "image @ %f,%f\n", Rthis, Dthis);
+
+    XY_to_RD(&Rthis, &Dthis, image[i].NX, 0, &image[i].coords);
+    Rmin = MIN(Rthis, Rmin);
+    Rmax = MAX(Rthis, Rmax);
+    Dmin = MIN(Dthis, Dmin);
+    Dmax = MAX(Dthis, Dmax);
+    Qthis = (Rthis < 180.0) ? Rthis : Rthis - 360.0;
+    Qmin = MIN(Qthis, Qmin);
+    Qmax = MAX(Qthis, Qmax);
+
+    XY_to_RD(&Rthis, &Dthis, 0, image[i].NY, &image[i].coords);
+    Rmin = MIN(Rthis, Rmin);
+    Rmax = MAX(Rthis, Rmax);
+    Dmin = MIN(Dthis, Dmin);
+    Dmax = MAX(Dthis, Dmax);
+    Qthis = (Rthis < 180.0) ? Rthis : Rthis - 360.0;
+    Qmin = MIN(Qthis, Qmin);
+    Qmax = MAX(Qthis, Qmax);
+
+    XY_to_RD(&Rthis, &Dthis, image[i].NX, image[i].NY, &image[i].coords);
+    Rmin = MIN(Rthis, Rmin);
+    Rmax = MAX(Rthis, Rmax);
+    Dmin = MIN(Dthis, Dmin);
+    Dmax = MAX(Dthis, Dmax);
+    Qthis = (Rthis < 180.0) ? Rthis : Rthis - 360.0;
+    Qmin = MIN(Qthis, Qmin);
+    Qmax = MAX(Qthis, Qmax);
+  }
+
+  double dQ = Qmax - Qmin;
+  double dR = Rmax - Rmin;
+
+  // load the sky table for the existing database
+  SkyTable *insky = SkyTableLoadOptimal (catdir, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
+  myAssert(insky, "can't read SkyTable");
+  SkyTableSetFilenames (insky, catdir, "cpt");
+  
+  // XXX apply this...generate the subset matching the user-selected region
+  SkyRegion UserPatch;
+
+  if (dR < dQ + 0.1) {
+    fprintf (stderr, "R,D range: %f - %f, %f - %f\n", Rmin, Rmax, Dmin, Dmax);
+    nPass = 1;
+  } else {
+    fprintf (stderr, "R,D range: %f - %f, %f - %f\n", Qmin, Qmax, Dmin, Dmax);
+    nPass = 2;
+  }
+  
+  ALLOCATE(cpmFilenameSrc, char, strlen(catdir) + 64);
+  ALLOCATE(cptFilenameSrc, char, strlen(catdir) + 64);
+  ALLOCATE(cpsFilenameSrc, char, strlen(catdir) + 64);
+  ALLOCATE(cpmFilenameTgt, char, strlen(catdir) + 64);
+  ALLOCATE(cptFilenameTgt, char, strlen(catdir) + 64);
+  ALLOCATE(cpsFilenameTgt, char, strlen(catdir) + 64);
+
+  int NdeleteTotal = 0;
+
+  for (raPass = 0; raPass < nPass; raPass++) {
+    
+    UserPatch.Dmin = Dmin - 0.1;
+    UserPatch.Dmax = Dmax + 0.1;
+
+    if (dR < dQ + 0.1) {
+      UserPatch.Rmin = Rmin - 0.1;
+      UserPatch.Rmax = Rmax + 0.1;
+    } else {
+      if (raPass == 0) {
+	UserPatch.Rmin = 0.0;
+	UserPatch.Rmax = Qmax + 0.1;
+      } else {
+	UserPatch.Rmin = Qmin + 360.0 - 0.1;
+	UserPatch.Rmax = 360.0;
+      }
+    }
+
+    SkyList *inlist = SkyListByPatch (insky, -1, &UserPatch);
+  
+    fprintf (stderr, "%d cpt regions affected\n", (int) inlist->Nregions);
+
+    // SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+    // depth = inlist[0].regions[Ns][0].depth;
+  
+    // loop over the populated input regions
+    int Ncheck = 0;
+    for (i = 0; i < inlist[0].Nregions; i++) {
+      if (!inlist[0].regions[i][0].table) continue;
+
+      sprintf (cpmFilenameSrc, "%s/%s.cpm", catdir, inlist[0].regions[i][0].name);
+      sprintf (cptFilenameSrc, "%s/%s.cpt", catdir, inlist[0].regions[i][0].name);
+      sprintf (cpsFilenameSrc, "%s/%s.cps", catdir, inlist[0].regions[i][0].name);
+      sprintf (cpmFilenameTgt, "%s/%s.cpm.broken", catdir, inlist[0].regions[i][0].name);
+      sprintf (cptFilenameTgt, "%s/%s.cpt.broken", catdir, inlist[0].regions[i][0].name);
+      sprintf (cpsFilenameTgt, "%s/%s.cps.broken", catdir, inlist[0].regions[i][0].name);
+
+      Header cpmHeaderPHU;
+      Header cpmHeaderTBL;
+      FTable cpmFtable;
+
+      /*** read and examine the CPM file ***/
+      {
+	if (VERBOSE) fprintf (stderr, "check %s\n", cpmFilenameSrc);
+
+	cpmFtable.header = &cpmHeaderTBL;
+
+	// open cpm file
+	FILE *cpmFile = fopen(cpmFilenameSrc, "r");
+	if (!cpmFile) continue;
+	// myAssert(cpmFile, "failed to open cpm file");
+    
+	// load the cpm header
+	if (!gfits_fread_header (cpmFile, &cpmHeaderPHU)) {
+	  myAbort("failure to cpm header");
+	}
+
+	// move to TBL header
+	int Nbytes = cpmHeaderPHU.datasize + gfits_data_size (&cpmHeaderPHU);
+	fseeko (cpmFile, Nbytes, SEEK_SET);
+
+	// read cpm TBL header
+	if (!gfits_fread_header (cpmFile, &cpmHeaderTBL)) { 
+	  myAbort("can't read header for cpm table");
+	}
+
+	// read Measure table data : format is irrelevant here */
+	if (!gfits_fread_ftable_data (cpmFile, &cpmFtable, TRUE)) { 
+	  myAbort("can't read data for cpm table");
+	}
+
+	int NbytesPerRow;
+	gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
+
+	// this function can only handle PS1_V5 and later formats
+	// NOTE: FtableToMeasure (and equivalent) free the data associated with cpmFtable.
+	// We are left with the data in measure.
+	measure = FtableToMeasure (&cpmFtable, NULL, &Nmeasure, &catformat);
+	myAssert(measure, "failed to convert ftable to measure data");
+    
+	Nvalid = (int)(cpmFtable.validsize / NbytesPerRow);
+	Nvalid = MIN(Nmeasure, Nvalid);
+
+	// close the input cpm file
+	fclose(cpmFile);
+
+	// allocate an output array of measures (to replace, if needed)
+	ALLOCATE (measureNew, Measure, Nvalid);
+
+	NmeasureNew = 0;
+	Ndelete = 0;
+
+	// examine all measurements: find ones that need to be deleted
+	for (j = 0; j < Nvalid; j++) {
+	  int imageID = measure[j].imageID;
+	  myAssert(imageID, "measure is missing an image ID");
+	  // XXX this case is valid if we have REF detections (no associated image)
+
+	  int N = myIndexGetEntry(imageIDindex, imageID);
+	  if (N < 0) {
+	    // this detection comes from a non-existant image, delete
+	    Ndelete ++;
+	    continue;
+	  }
+
+	  if (deleteImage[N]) {
+	    Ndelete ++;
+	    continue;
+	  }
+	  measureNew[NmeasureNew] = measure[j];
+	  NmeasureNew ++;
+	}
+      }
+
+      NdeleteTotal += Ndelete;
+
+      // if we actually want to delete any measurements, write out a new cpm file
+      if (Ndelete > 0) {
+
+	fprintf (stderr, "deleting %d of %d (keep %d) detections from %s -> %s\n", (int) Ndelete, (int) Nvalid, (int) NmeasureNew, cpmFilenameSrc, cpmFilenameTgt);
+      
+	// the CPT and CPS tables need to be regenerated.  This must happen first because, in the process, we also update measure->averef
+	// what about other tables?
+	RepairTableCPT_V1(cptFilenameSrc, cptFilenameTgt, cpsFilenameSrc, cpsFilenameTgt, measureNew, NmeasureNew, image, Nimage, imageIDindex, catformat);
+
+	// convert internal to external format 
+	if (!MeasureToFtable (&cpmFtable, NULL, measureNew, NmeasureNew, catformat)) {
+	  myAbort("trouble converting format");
+	}
+
+	// rename the old cpm file:
+	if (rename (cpmFilenameSrc, cpmFilenameTgt)) {
+	  perror ("tried to rename file");
+	  exit (2);
+	}
+
+	// create and write the output file
+	FILE *cpmFile = fopen(cpmFilenameSrc, "w");
+	myAssert(cpmFile, "failed to open cpt file");
+	
+	// write PHU header
+	if (!gfits_fwrite_header (cpmFile, &cpmHeaderPHU)) {
+	  myAbort("can't write primary header");
+	}
+
+	Matrix matrix;
+
+	// write the PHU matrix; this is probably a NOP, do I have to keep it in?
+	gfits_create_matrix (&cpmHeaderPHU, &matrix);
+	if (!gfits_fwrite_matrix  (cpmFile, &matrix)) {
+	  myAbort("can't write primary matrix");
+	}
+	gfits_free_matrix (&matrix);
+	
+	// write the table data
+	if (!gfits_fwrite_ftable_range (cpmFile, &cpmFtable, 0, NmeasureNew, 0, NmeasureNew)) {
+	  myAbort("can't write table data");
+	}
+	fclose (cpmFile);
+
+	gfits_free_table (&cpmFtable);
+      } else {
+	if (VERBOSE) fprintf (stderr, "nothing to delete in %s\n", cpmFilenameSrc);
+      }
+
+      gfits_free_header (&cpmHeaderPHU);
+      gfits_free_header (&cpmHeaderTBL);
+      free (measure);
+      free (measureNew);
+
+      Ncheck ++;
+      if (Ncheck % 1000 == 0) {
+	fprintf (stderr, "%s...", inlist[0].regions[i][0].name);
+      }
+    }
+    fprintf (stderr, "\n");
+    SkyListFree(inlist);
+  }
+
+  fprintf (stderr, "Deleted %d detections\n", NdeleteTotal);
+
+  off_t NnewImage;
+  Image *newImage = DeleteSelectedImages (image, Nimage, deleteImage, imageIDindex, &NnewImage);
+
+  SaveImageTable (newImage, NnewImage, catdir, &db);
+
+  myIndexFree (externIDindex);
+  myIndexFree (imageIDindex);
+
+  free (imageFilename);
+  free (deleteImage);
+  free (deleteIDs);
+
+  free(cpmFilenameSrc);
+  free(cptFilenameSrc);
+  free(cpsFilenameSrc);
+  free(cpmFilenameTgt);
+  free(cptFilenameTgt);
+  free(cpsFilenameTgt);
+
+  SkyTableFree(insky);
+
+  gfits_db_free (&db);
+
+  exit (0);
+}
+
+Image *DeleteSelectedImages (Image *image, off_t Nimage, int *deleteImage, myIndexType *imageIDindex, off_t *NnewImage) {
+
+  // we need to remove the (WRP) images, but we also want to remove the DIS [PHU] image entries if they have
+  // had all of their children deleted
+
+  // first, generate an array of the number of children for each parent
+
+  int *Nchildren = NULL;
+  ALLOCATE (Nchildren, int, Nimage);
+  memset (Nchildren, 0, Nimage*sizeof(int));
+
+  int i;
+  for (i = 0; i < Nimage; i++) {
+    if (!image[i].parentID) continue;
+    if (strcmp(&image[i].coords.ctype[4], "-WRP")) {
+      fprintf (stderr, "warning: child with non WRP coords : %s\n", image[i].name);
+    }
+
+    int n = myIndexGetEntry (imageIDindex, image[i].parentID);
+    myAssert (n != -1, "invalid parentID?");
+    myAssert (n < Nimage, "invalid parentID??");
+    Nchildren[n] ++;
+  }
+
+  // now subtract the children which need to be deleted
+  for (i = 0; i < Nimage; i++) {
+    if (!deleteImage[i]) continue;
+    if (!image[i].parentID) continue; // should I warn on this?
+
+    int n = myIndexGetEntry (imageIDindex, image[i].parentID);
+    myAssert (n != -1, "invalid parentID?");
+    myAssert (n < Nimage, "invalid parentID??");
+    Nchildren[n] --;
+    myAssert (Nchildren[n] >= 0, "over deleted???");
+  }
+  
+  // now check for any parent and delete it
+  for (i = 0; i < Nimage; i++) {
+    if (strcmp(&image[i].coords.ctype[4], "-DIS")) continue;
+    myAssert (!image[i].parentID, "parent ID for DIS image??");
+    if (!Nchildren[i]) deleteImage[i] = TRUE;
+  }
+
+  fprintf (stderr, "deleting the following images:\n");
+
+  off_t Nnew = 0;
+  Image *newImage = NULL;
+  ALLOCATE (newImage, Image, Nimage);
+  for (i = 0; i < Nimage; i++) {
+    if (deleteImage[i]) { 
+      fprintf (stderr, "DELETE: %s : %d : %d : %d\n", image[i].name, image[i].imageID, image[i].parentID, image[i].externID);
+      continue;
+    }
+    newImage[Nnew] = image[i];
+    Nnew ++;
+  }
+
+  REALLOCATE (newImage, Image, Nnew);
+
+  *NnewImage = Nnew;
+  return newImage;
+}
+
+int SaveImageTable (Image *image, off_t Nimage, char *catdir, FITS_DB *oldDB) {
+
+  FITS_DB db;
+
+  char ImageCatSrc[DVO_MAX_PATH];
+  char ImageCatTgt[DVO_MAX_PATH];
+
+  // we are creating a new image table (what about db ID?)
+  snprintf (ImageCatSrc, DVO_MAX_PATH, "%s/Images.dat", catdir);
+  snprintf (ImageCatTgt, DVO_MAX_PATH, "%s/Images.dat.broken", catdir);
+
+  // rename the old cpt file:
+  if (rename (ImageCatSrc, ImageCatTgt)) {
+    perror ("tried to rename file");
+    exit (2);
+  }
+
+  /* setup image table format and lock */
+  db.mode    = dvo_catalog_catmode ("SPLIT");
+  db.format  = dvo_catalog_catformat ("PS1_V5");
+  int status = dvo_image_lock (&db, ImageCatSrc, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) {
+    fprintf (stderr, "ERROR: failure to lock image catalog %s", db.filename);
+    exit (2);
+  }
+
+  /* load or create the image table */
+  if (db.dbstate != LCK_EMPTY) {
+    fprintf (stderr, "image table %s exists, exiting", db.filename);
+    exit (2);
+  }
+  dvo_image_create (&db, GetZeroPoint());
+
+  // I want to keep the old DVO_DBID
+  char dbID[33];
+  int imageIDmax;
+  if (!gfits_scan (&oldDB->header, "DVO_DBID", "%s", 1, dbID)) {
+    fprintf (stderr, "source image table is missing DVO_DBID, exiting\n");
+    exit (3);
+  }
+  if (!gfits_scan (&oldDB->header, "IMAGEID", "%d", 1, &imageIDmax)) {
+    fprintf (stderr, "max image ID is missing, exiting\n");
+    exit (3);
+  }
+  gfits_modify (&db.header, "IMAGEID", "%d", 1, imageIDmax);      
+  gfits_modify (&db.header, "DVO_DBID", "%s", 1, dbID);      
+
+  /* add the new image and save */
+  dvo_image_addrows (&db, image, Nimage);
+  dvo_image_update (&db, VERBOSE);
+  dvo_image_unlock (&db); /* unlock? */
+
+  return TRUE;
+}
+
+int RepairTableCPT_V1(char *cptFilenameSrc, char *cptFilenameTgt, char *cpsFilenameSrc, char *cpsFilenameTgt, Measure *measure, off_t Nmeasure, Image *image, off_t Nimage, myIndexType *imageIDindex, char catformat) {
+
+  off_t *averefMatch;
+  off_t i, NaveMax, Naverage, NAVERAGE, NaverageOut, Nave, Nout, Nold;
+  int *found, Nsecfilt;
+
+  Average *average, *averageOut;
+
+  Matrix matrix;
+
+  Header cptHeaderPHU;
+  Header cptHeaderTBL;
+  FTable cptFtable;
+
+  cptFtable.header = &cptHeaderTBL;
+
+  NaveMax = 0;
+  NAVERAGE = 1000;
+  ALLOCATE (average, Average, NAVERAGE);
+  memset (average, 0, NAVERAGE*sizeof(Average));
+
+  ALLOCATE (found, int, NAVERAGE);
+  memset (found, 0, NAVERAGE*sizeof(int));
+
+  // examine all measurements and create new objects as needed
+  // we start with a valid, sorted dvo database, so we use averef to link objects
+  for (i = 0; i < Nmeasure; i++) {
+    Nave = measure[i].averef;
+
+    // we only allocate as many as we need
+    if (Nave >= NAVERAGE) {
+      Nold = NAVERAGE;
+      NAVERAGE = MAX(Nave + 1000, NAVERAGE + 1000);
+      REALLOCATE (average, Average, NAVERAGE);
+      memset (&average[Nold], 0, (NAVERAGE - Nold)*sizeof(Average));
+
+      REALLOCATE (found, int, NAVERAGE);
+      memset (&found[Nold], 0, (NAVERAGE - Nold)*sizeof(int));
+    }
+
+    // this measure matches an existing average, just check it is OK and bump Nmeasure
+    if (found[Nave]) {
+      average[Nave].Nmeasure ++;
+      myAssert(average[Nave].objID == measure[i].objID, "objIDs do not match!");
+      myAssert(average[Nave].catID == measure[i].catID, "catIDs do not match!");
+      continue;
+    }
+
+    NaveMax = MAX(Nave, NaveMax);
+
+    found[Nave] = TRUE;
+
+    // we are going to leave most of the elements of average unset: they are the result of 
+    // the relastro analysis for this object and can be recreated with a call to relastro
+
+    // need to find image so we can use ccd coordinates to determine RA & DEC
+    int n = myIndexGetEntry (imageIDindex, measure[i].imageID);
+    myAssert (n > -1, "impossible!");
+    
+    dvo_average_init (&average[Nave]);
+
+    // I actually have measure->R,D, I could just use those...
+    XY_to_RD (&average[Nave].R, &average[Nave].D, measure[i].Xccd, measure[i].Yccd, &image[n].coords);
+
+    average[Nave].Nmeasure = 1;
+    average[Nave].Nmissing = 0;
+
+    // assume the resulting table set is unsorted
+    average[Nave].measureOffset = -1;
+    average[Nave].missingOffset = -1;
+
+    average[Nave].objID = measure[i].objID;
+    average[Nave].catID = measure[i].catID;
+    average[Nave].extID = CreatePSPSObjectID(average[Nave].R, average[Nave].D);
+  }
+  Naverage = NaveMax + 1;
+
+  // we now have an average table, but there will be holes due to deleted measurements 
+  // create a new average table with only existing entries
+
+  ALLOCATE (averageOut, Average, Naverage);
+  memset (averageOut, 0, Naverage*sizeof(Average));
+
+  ALLOCATE (averefMatch, off_t, Naverage);
+  memset (averefMatch, 0, Naverage*sizeof(int));
+
+  Nave = 0;
+  for (i = 0; i < Naverage; i++) {
+    if (!found[i]) continue;
+    averageOut[Nave] = average[i]; // use a memcpy?
+    averefMatch[i] = Nave;
+    Nave ++;
+  }
+  NaverageOut = Nave;
+
+  // modify measure.averef to match the new sequence
+  for (i = 0; i < Nmeasure; i++) {
+    Nave = measure[i].averef;
+    Nout = averefMatch[Nave];
+    myAssert(Nout < NaverageOut, "output averef is wrong");
+    
+    myAssert(average[Nave].objID == measure[i].objID, "objIDs do not match");
+    myAssert(average[Nave].catID == measure[i].catID, "objIDs do not match");
+    myAssert(averageOut[Nout].objID == measure[i].objID, "objIDs do not match");
+    myAssert(averageOut[Nout].catID == measure[i].catID, "objIDs do not match");
+
+    measure[i].averef = Nout;
+  }
+
+  fprintf (stderr, "cpt file : %d obj -> %d obj (%s -> %s)\n", (int) Naverage, (int) NaverageOut, cptFilenameSrc, cptFilenameTgt);
+
+  // open source cpt file
+  FILE *cptFile = fopen(cptFilenameSrc, "r");
+  myAssert(cptFile, "failed to open cpt file");
+
+  // load the cpt header (use for CATID, RA, DEC range, filenames)
+  if (!gfits_fread_header (cptFile, &cptHeaderPHU)) {
+    myAbort("failure to cpt header");
+  }
+
+  // update the output header
+  gfits_modify (&cptHeaderPHU, "NSTARS",     OFF_T_FMT, 1,  NaverageOut);
+  gfits_modify (&cptHeaderPHU, "NMEAS",      OFF_T_FMT, 1,  Nmeasure);
+  gfits_modify (&cptHeaderPHU, "NMISS",      "%d",      1,  0);
+  gfits_modify_alt (&cptHeaderPHU, "SORTED", "%t",      1,  FALSE);
+
+  gfits_scan (&cptHeaderPHU, "NSECFILT",     "%d",      1,  &Nsecfilt);
+
+  /* convert internal to external format */
+  if (!AverageToFtable (&cptFtable, averageOut, NaverageOut, catformat, NULL)) {
+    myAbort("trouble converting format");
+  }
+  fclose(cptFile);
+  
+  // rename the old cpt file:
+  if (rename (cptFilenameSrc, cptFilenameTgt)) {
+    perror ("tried to rename file");
+    exit (2);
+  }
+
+  // create and write the output file
+  cptFile = fopen(cptFilenameSrc, "w");
+  myAssert(cptFile, "failed to open cpt file");
+    
+  // write PHU header
+  if (!gfits_fwrite_header (cptFile, &cptHeaderPHU)) {
+    myAbort("can't write primary header");
+  }
+
+  // write the PHU matrix; this is probably a NOP, do I have to keep it in?
+  gfits_create_matrix (&cptHeaderPHU, &matrix);
+  if (!gfits_fwrite_matrix  (cptFile, &matrix)) {
+    myAbort("can't write primary matrix");
+  }
+  gfits_free_matrix (&matrix);
+
+  // write the table data
+  if (!gfits_fwrite_ftable_range (cptFile, &cptFtable, 0, NaverageOut, 0, NaverageOut)) {
+    myAbort("can't write table data");
+  }
+  fclose(cptFile);
+
+  gfits_free_table (&cptFtable);
+  gfits_free_header (&cptHeaderPHU);
+  gfits_free_header (&cptHeaderTBL);
+  free (average);
+  free (averageOut);
+
+  free (found);
+  free (averefMatch);
+
+  { 
+    Header cpsHeaderPHU;
+    Header cpsHeaderTBL;
+    FTable cpsFtable;
+
+    SecFilt *secfilt = NULL;
+
+    cpsFtable.header = &cpsHeaderTBL;
+
+    // open source cpt file
+    FILE *cpsFile = fopen(cpsFilenameSrc, "r");
+    myAssert(cpsFile, "failed to open cps file");
+    
+    // load the cps header (use for CATID, RA,DEC range, filenames)
+    if (!gfits_fread_header (cpsFile, &cpsHeaderPHU)) {
+      myAbort("failure to cps header");
+    }
+
+    int Nrows = Nsecfilt*NaverageOut;
+    ALLOCATE (secfilt, SecFilt, Nrows);
+
+    for (i = 0; i < Nrows; i++) {
+      dvo_secfilt_init (&secfilt[i]);
+    }
+
+    /* convert internal to external format */
+    if (!SecFiltToFtable (&cpsFtable, secfilt, Nrows, catformat)) {
+      myAbort("trouble converting format");
+    }
+    fclose(cpsFile);
+
+    // rename the old cpt file:
+    if (rename (cpsFilenameSrc, cpsFilenameTgt)) {
+      perror ("tried to rename file");
+      exit (2);
+    }
+
+    // create and write the output file
+    cpsFile = fopen(cpsFilenameSrc, "w");
+    myAssert(cpsFile, "failed to open cps file");
+    
+    // write PHU header
+    if (!gfits_fwrite_header (cpsFile, &cpsHeaderPHU)) {
+      myAbort("can't write primary header");
+    }
+
+    // write the PHU matrix; this is probably a NOP, do I have to keep it in?
+    gfits_create_matrix (&cpsHeaderPHU, &matrix);
+    if (!gfits_fwrite_matrix  (cpsFile, &matrix)) {
+      myAbort("can't write primary matrix");
+    }
+    gfits_free_matrix (&matrix);
+
+    // write the table data
+    if (!gfits_fwrite_ftable_range (cpsFile, &cpsFtable, 0, Nrows, 0, Nrows)) {
+      myAbort("can't write table data");
+    }
+    fclose(cpsFile);
+
+    gfits_free_table (&cpsFtable);
+    gfits_free_header (&cpsHeaderPHU);
+    gfits_free_header (&cpsHeaderTBL);
+    free (secfilt);
+  }
+
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvorepairFixImages.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairFixImages.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairFixImages.c	(revision 37807)
@@ -130,4 +130,8 @@
 int MarkMosaicsToDelete(Image *image, off_t Nimage, int *deleteIndex) {
 
+  myAbort ("this function needs to be redone with a concept to match the old BuildChipMatch");
+
+# if (0)  
+
   off_t i;
   
@@ -140,4 +144,6 @@
      marked.  Now find all WRP images for which none/some/all images are to be deleted.
   */
+
+  
 
   // make a copy of the ChipMatch table (generated by BuildChipMatch, see libdvo/src/mosaic_astrom.c) 
@@ -237,4 +243,6 @@
   free (ChipByMosaicID);
 
+# endif
+
   return TRUE;
 }
Index: /trunk/Ohana/src/dvomerge/src/dvorepairFixTables.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairFixTables.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairFixTables.c	(revision 37807)
@@ -259,4 +259,5 @@
     thisImage = MatchImage (image, Nimage, measure[i].t, measure[i].photcode, measure[i].imageID);
     XY_to_RD (&average[Nave].R, &average[Nave].D, measure[i].Xccd, measure[i].Yccd, &thisImage[0].coords);
+    average[Nave].R = ohana_normalize_angle (average[Nave].R);
 
     average[Nave].Nmeasure = 1;
Index: /trunk/Ohana/src/dvomerge/src/dvoutils.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoutils.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/dvoutils.c	(revision 37807)
@@ -0,0 +1,20 @@
+# include "dvoutils.h"
+
+int main (int argc, char **argv) {
+
+  // check various options
+  dvoutils_args (&argc, argv);
+
+  switch (DVOUTILS_OP) {
+    
+    case DVOUTILS_UNIQ_IMAGES:
+      dvoutils_uniq_images(IMAGES_LIST);
+      exit (0);
+
+    default:
+      fprintf (stderr, "ERROR: unknown option\n");
+      exit (2);
+  }
+
+  exit (2);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvoutils_args.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoutils_args.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/dvoutils_args.c	(revision 37807)
@@ -0,0 +1,37 @@
+# include "dvoutils.h"
+
+int dvoutils_args (int *argc, char **argv) {
+
+  int N;
+
+  VERBOSE = FALSE;
+  if ((N = get_argument (*argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  if ((N = get_argument (*argc, argv, "-verbose"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  DVOUTILS_OP = DVOUTILS_NONE;
+  if ((N = get_argument (*argc, argv, "-uniq-images"))) {
+    DVOUTILS_OP = DVOUTILS_UNIQ_IMAGES;
+    remove_argument (N, argc, argv);
+    IMAGES_LIST = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+
+  if (DVOUTILS_OP == DVOUTILS_NONE) {
+    fprintf (stderr, "ERROR: no valid mode selected\n");
+    exit (2);
+  }
+
+  if (*argc != 1) {
+    fprintf (stderr, "USAGE: dvoutils -uniq-images (filename) [-v,-verbose]\n\n");
+    fprintf (stderr, "  -v : VERBOSE\n");
+    exit (2);
+  }
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/dvomerge/src/dvoutils_load_image_index.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoutils_load_image_index.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/dvoutils_load_image_index.c	(revision 37807)
@@ -0,0 +1,97 @@
+# include "dvoutils.h"
+
+// load the array of EXTERN_ID from the ImageIndex.fits tables
+int *dvoutils_load_image_index (char *filename, int *nindex) {
+
+  int Ncol;
+  off_t Nrow;
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+  char type[16];
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image index file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image index header\n");
+    fclose (f);
+    return NULL;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image index matrix\n");
+    gfits_free_header (&header);
+    fclose (f);
+    return NULL;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) {
+    fclose (f);
+    return NULL;
+  }
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
+    fclose (f);
+    return (NULL);
+  }
+  fclose (f);
+
+  int *externID = gfits_get_bintable_column_data (&theader, &ftable, "EXTERN_ID", type, &Nrow, &Ncol);
+  myAssert (!strcmp(type, "int"), "wrong column type");
+
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  *nindex = Nrow;
+
+  return externID;
+}
+
+Image *dvoutils_load_image_table (char *filename, int *nimage) {
+
+  FITS_DB db;
+
+  gfits_db_init (&db);
+  db.lockstate = LCK_SOFT;
+  db.timeout   = 120.0;
+
+  if (!gfits_db_lock (&db, filename)) {
+    fprintf (stderr, "error opening image catalog %s (1)\n", filename);
+    exit (3);
+  }
+
+  if (db.dbstate == LCK_EMPTY) {
+    fprintf (stderr, "note: image catalog is empty\n");
+    gfits_db_free  (&db);
+    return NULL;
+  }
+
+  int status = dvo_image_load (&db, TRUE, FALSE);
+  gfits_db_close (&db);
+    
+  if (!status) {
+    fprintf (stderr, "problem loading image database table\n");
+    exit (4);
+  }
+
+  off_t Nimage;
+  Image *image = gfits_table_get_Image (&db.ftable, &Nimage, &db.swapped);
+  if (!image) {
+    fprintf (stderr, "ERROR: failed to read images\n");
+    exit (5);
+  }
+
+  *nimage = Nimage;
+  return image;
+}
Index: /trunk/Ohana/src/dvomerge/src/dvoutils_uniq_images.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoutils_uniq_images.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/dvoutils_uniq_images.c	(revision 37807)
@@ -0,0 +1,169 @@
+# include "dvoutils.h"
+
+# ifndef MAX_INT
+# define MAX_INT 2147483647
+# endif
+
+int dvoutils_uniq_images(char *filename) {
+
+  // given a list of Images.dat tables, find the unique subset of extern_id
+  // format of the input list is just (dirname)/Images.dat.  commented lines
+  // starting with # are allowed
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "cannot read input list %s\n", filename);
+    exit (1);
+  }
+
+  int    minID  = -1;
+  int    maxID  = -1;
+  int  *IDlist  = NULL;
+  int  *DBfound = NULL;
+  int  *DBindex = NULL;
+  
+  int Ndb = 0;
+  int NDB = 100;
+  char **DBlist = NULL;
+  ALLOCATE (DBlist, char *, NDB); // list of the names of the image database tables.  
+
+  char imFile[1024];
+  while (scan_line_maxlen (f, imFile, 1024) != EOF) {
+
+    stripwhite (imFile);
+
+    if (imFile[0] == '#') continue;
+
+    if (strchr(imFile, ' ') != NULL) {
+      fprintf (stderr, "line has extra spaces\n%s\n", imFile);
+      exit (2);
+    }
+
+    int Nimage;
+    // Image *image = dvoutils_load_image_table (imFile, &Nimage);
+    int *extern_id = dvoutils_load_image_index (imFile, &Nimage);
+    if (!extern_id) continue;
+
+    DBlist[Ndb] = strcreate (imFile);
+
+    // now loop over all of the images and accumulate the array of extern_id values (skip
+    // the ones with extern_id == 0)
+
+    // things I need to track:
+    // minID, maxID (only allocate an array of length maxID - minID + 1 + padding)
+
+    int myMaxID = 0;
+    int myMinID = MAX_INT;
+
+    int i;
+    for (i = 0; i < Nimage; i++) {
+      if (!extern_id[i]) continue;
+      myMaxID = MAX(extern_id[i], myMaxID);
+      myMinID = MIN(extern_id[i], myMinID);
+    }
+
+    fprintf (stderr, "read %s, %d images, %d - %d vs %d - %d\n", imFile, (int) Nimage, myMinID, myMaxID, minID, maxID);
+
+    // if this is new, we treat it a bit differently
+    if (!IDlist) {
+      int Nindex = myMaxID - myMinID + 1;
+      ALLOCATE (IDlist,  int,  Nindex); // number of times this ID is seen.  IDlist[i] corresponds to extern_id = i + minID
+      ALLOCATE (DBfound, int,  Nindex); // index of first DB in which this ID is seen (if n = DBfound[i], the correspond ID was seen in DB n
+      ALLOCATE (DBindex, int,  Nindex); // sequence of this image in the first DB for which this ID was found
+
+      for (i = 0; i < Nindex; i++) {
+	IDlist[i]  =  0;
+	DBfound[i] = -1;
+	DBindex[i] = -1;
+      }
+
+      minID = myMinID;
+      maxID = myMaxID;
+    } else {
+      int newMinID = MIN(myMinID, minID);
+      int newMaxID = MAX(myMaxID, maxID);
+
+      int Nindex = newMaxID - newMinID + 1;
+
+      int oldNindex = maxID - minID + 1;
+      myAssert (oldNindex <= Nindex, "impossible!");
+
+      int *newIDlist  = NULL;
+      int *newDBfound = NULL;
+      int *newDBindex = NULL;
+
+      ALLOCATE (newIDlist,  int, Nindex);
+      ALLOCATE (newDBfound, int, Nindex);
+      ALLOCATE (newDBindex, int, Nindex);
+
+      for (i = 0; i < Nindex; i++) {
+	newIDlist[i]  =  0;
+	newDBfound[i] = -1;
+	newDBindex[i] = -1;
+      }
+      
+      // if myMinID >= minID, newMinID = minID -> offset = 0
+      // if myMinID <  minID, newMinID = myMinID -> offset = minID - myMinID
+      int offset = minID - newMinID;
+
+      // save the old values
+      memcpy (&newIDlist [offset], IDlist,  oldNindex*sizeof(int));
+      memcpy (&newDBfound[offset], DBfound, oldNindex*sizeof(int));
+      memcpy (&newDBindex[offset], DBindex, oldNindex*sizeof(int));
+
+      free (IDlist);
+      free (DBfound);
+      free (DBindex);
+
+      IDlist  = newIDlist;
+      DBfound = newDBfound;
+      DBindex = newDBindex;
+
+      minID = newMinID;
+      maxID = newMaxID;
+    }
+
+    for (i = 0; i < Nimage; i++) {
+      if (!extern_id[i]) continue;
+      int n = extern_id[i] - minID;
+      if (IDlist[n] == 0) {
+	DBfound[n] = Ndb;
+	DBindex[n] = i;
+      }
+      IDlist[n] ++;
+    }
+
+    if (0) {
+      fprintf (stderr, "------------ %s ----------\n", imFile);
+
+      for (i = 0; i < Nimage; i++) {
+	if (!extern_id[i]) continue;
+	fprintf (stderr, "%d => %d\n", extern_id[i], extern_id[i] - minID);
+      }
+
+      int Nindex = maxID - minID + 1;
+      
+      for (i = 0; i < Nindex; i++) {
+	if (!IDlist[i]) continue;
+	fprintf (stderr, "%d : %d : %d\n", i, i + minID, IDlist[i]);
+      }
+    }
+
+    // we are done with the db file, bump the counter
+    Ndb ++;
+    CHECK_REALLOCATE (DBlist, char *, NDB, Ndb, 100);
+  }
+
+  int Nindex = maxID - minID + 1;
+
+  int i;
+  for (i = 0; i < Nindex; i++) {
+    if (IDlist[i] < 2) continue;
+    int n = DBfound[i];
+    myAssert (n > -1, "impossible!");
+    fprintf (stdout, "%d : %d -- in %s (%d) : %d \n", i + minID, IDlist[i], DBlist[n], DBfound[i], DBindex[i]);
+  }
+
+  exit (0);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvoverify_utils.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoverify_utils.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/dvoverify_utils.c	(revision 37807)
@@ -238,6 +238,6 @@
     }
     // lensingOffset needs to be in range for each object
-    lensingOffsetOK &= (catalog.average[i].lensingOffset < catalog.Nlensing);
-    if (VERBOSE && !(catalog.average[i].lensingOffset < catalog.Nlensing)) {
+    lensingOffsetOK &= (!catalog.Nlensing || (catalog.average[i].lensingOffset < catalog.Nlensing));
+    if (VERBOSE && catalog.Nlensing && !(catalog.average[i].lensingOffset < catalog.Nlensing)) {
       fprintf (stderr, "lensingOffset >= catalog.Nlensing: %d %d %d\n", i, catalog.average[i].lensingOffset, (int) catalog.Nlensing);
     }
Index: /trunk/Ohana/src/dvomerge/src/merge_catalogs_new.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/merge_catalogs_new.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/merge_catalogs_new.c	(revision 37807)
@@ -11,5 +11,5 @@
   
   off_t i, j, offset;
-  off_t NAVERAGE, NMEASURE, NLENSING, Naverage, Nmeasure, Nlensing, NsecfiltIn, NsecfiltOut, Nm;
+  off_t NAVERAGE, NMEASURE, NLENSING, NSTARPAR, Naverage, Nmeasure, Nlensing, Nstarpar, NsecfiltIn, NsecfiltOut, Nm;
 
   Naverage = output[0].Naverage;
@@ -81,4 +81,20 @@
       output[0].average[Naverage].Nlensing = Nm;
 
+      Nm = 0;
+      for (j = 0; j < input[0].average[i].Nstarpar; j++) {
+	  offset = input[0].average[i].starparOffset + j;
+
+	  output[0].starpar[Nstarpar] = input[0].starpar[offset];
+	  output[0].starpar[Nstarpar].averef = Naverage;
+
+	  Nstarpar ++;
+	  Nm ++;
+	  if (Nstarpar == NSTARPAR) {
+	      NSTARPAR += 1000;
+	      REALLOCATE (output[0].starpar, StarPar, NSTARPAR);
+	  }
+      }
+      output[0].average[Naverage].Nstarpar = Nm;
+
       Naverage ++;
       if (Naverage == NAVERAGE) {
@@ -91,7 +107,9 @@
   REALLOCATE (output[0].measure, Measure, MAX (Nmeasure, 1));
   REALLOCATE (output[0].lensing, Lensing, MAX (Nlensing, 1));
+  REALLOCATE (output[0].starpar, StarPar, MAX (Nstarpar, 1));
   REALLOCATE (output[0].secfilt, SecFilt, NsecfiltOut*MAX (Naverage, 1));
   output[0].Naverage = Naverage;
   output[0].Nlensing = Nlensing;
+  output[0].Nstarpar = Nstarpar;
   output[0].Nsecf_mem = Naverage * NsecfiltOut;
 
Index: /trunk/Ohana/src/dvomerge/src/merge_catalogs_old.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/merge_catalogs_old.c	(revision 37806)
+++ /trunk/Ohana/src/dvomerge/src/merge_catalogs_old.c	(revision 37807)
@@ -4,5 +4,5 @@
 # define IN_REGION(R,D) (					\
     ((D) >= region[0].Dmin) && ((D) < region[0].Dmax) &&	\
-    ((R) >= region[0].Rmin)  && ((R) < region[0].Rmax))
+    ((R) >= region[0].Rmin) && ((R) < region[0].Rmax))
 
 // merge the input data into the output catalog
@@ -14,6 +14,6 @@
   double *X1, *Y1, *X2, *Y2;
   double dX, dY, dR;
-  off_t *N1, *N2, *next_meas, *next_lens;
-  off_t Nave, NAVE, Nmeas, NMEAS, Nmatch, Nlens, NLENS;
+  off_t *N1, *N2, *next_meas, *next_lens, *next_star;
+  off_t Nave, NAVE, Nmeas, NMEAS, Nmatch, Nlens, NLENS, Nstar, NSTAR;
   int NsecfiltIn;
   int NsecfiltOut;
@@ -53,4 +53,5 @@
   NMEAS = Nmeas = output[0].Nmeasure;
   NLENS = Nlens = output[0].Nlensing;
+  NSTAR = Nstar = output[0].Nstarpar;
 
   // current max obj ID for this catalog
@@ -64,4 +65,6 @@
    * coordops.update.c).  We use the center of the region (catalog) for crval1,2. 
    */
+
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -70,11 +73,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
-  strcpy (tcoords.ctype, "DEC--ARC");
 
   if (VERBOSE) fprintf (stderr, "merging %s into %s\n", input[0].filename, output[0].filename);
@@ -120,7 +117,9 @@
     next_meas = init_measure_links (output[0].average, Nave, output[0].measure, Nmeas);
     next_lens = init_lensing_links (output[0].average, Nave, output[0].lensing, Nlens);
+    next_star = init_starpar_links (output[0].average, Nave, output[0].starpar, Nstar);
   } else {
     next_meas = build_measure_links (output[0].average, Nave, output[0].measure, Nmeas);
     next_lens = build_lensing_links (output[0].average, Nave, output[0].lensing, Nlens);
+    next_star = build_starpar_links (output[0].average, Nave, output[0].starpar, Nstar);
   }    
 
@@ -184,8 +183,13 @@
       REALLOCATE (output[0].measure, Measure, NMEAS);
     }
-    if (Nlens + input[0].average[N].Nmeasure >= NLENS) {
-      NLENS = Nlens + input[0].average[N].Nmeasure + 1000;
+    if (Nlens + input[0].average[N].Nlensing >= NLENS) {
+      NLENS = Nlens + input[0].average[N].Nlensing + 1000;
       REALLOCATE (next_lens, off_t, NLENS);
       REALLOCATE (output[0].lensing, Lensing, NLENS);
+    }
+    if (Nstar + input[0].average[N].Nstarpar >= NSTAR) {
+      NSTAR = Nstar + input[0].average[N].Nstarpar + 1000;
+      REALLOCATE (next_star, off_t, NSTAR);
+      REALLOCATE (output[0].starpar, StarPar, NSTAR);
     }
 
@@ -274,4 +278,22 @@
     }
 
+    // if lensing measurements exist, add them too
+    if (output[0].starpar) {
+      for (Nin = 0; Nin < input[0].average[N].Nstarpar; Nin++) {
+	/* add to end of lensing list */
+	add_star_link (&output[0].average[n], next_star, Nstar, NSTAR);
+	
+	// set the new starpar
+	off_t staroff = input[0].average[N].starparOffset + Nin;
+	output[0].starpar[Nstar] = input[0].starpar[staroff];
+
+	output[0].starpar[Nstar].averef   = n;
+	output[0].starpar[Nstar].objID    = output[0].average[n].objID;
+	output[0].starpar[Nstar].catID    = output[0].catID;
+	output[0].average[n].Nstarpar ++;
+	Nstar ++;
+      }
+    }
+
     // update the average properties to reflect the incoming entries:
     // if the original value is NAN but the input value is not, accept the input:
@@ -319,4 +341,9 @@
       REALLOCATE (output[0].lensing, Lensing, NLENS);
     }
+    if (Nstar + input[0].average[N].Nstarpar >= NSTAR) {
+      NSTAR = Nstar + input[0].average[N].Nstarpar + 1000;
+      REALLOCATE (next_star, off_t, NSTAR);
+      REALLOCATE (output[0].starpar, StarPar, NSTAR);
+    }
     if (Nave >= NAVE) {
       NAVE = Nave + 1000;
@@ -326,5 +353,8 @@
 
     if (input[0].found_t[N] >= 0) continue;
-    if (!IN_REGION (input[0].average[N].R, input[0].average[N].D)) continue;
+
+    // if we are using MATCHED_TABLES, we are going to leave the edge cases in their
+    // source catalog, even if they have leaked beyond the edge
+    if (!MATCHED_TABLES && !IN_REGION (input[0].average[N].R, input[0].average[N].D)) continue;
 
     // XXX should we accept the input measurements for these fields?
@@ -413,4 +443,30 @@
     }
 
+    /** add starpar for this input average object **/
+    if (output[0].starpar) {
+      output[0].average[Nave].starparOffset  = Nstar;
+      for (Nin = 0; Nin < input[0].average[N].Nstarpar; Nin ++) {
+	// supply the starpar values from this detection
+	off_t staroff = input[0].average[N].starparOffset + Nin;
+	output[0].starpar[Nstar]           = input[0].starpar[staroff];
+
+	// the following starpar elements cannot be set until here:
+	output[0].starpar[Nstar].averef   = Nave;
+	output[0].starpar[Nstar].objID    = output[0].average[Nave].objID;
+	output[0].starpar[Nstar].catID    = output[0].catID;
+
+	// as we add starpar, update Nstarpar to match
+	output[0].average[Nave].Nstarpar ++;
+
+	/* we set next[Nstar] to -1 here, and update correctly below */
+	next_star[Nstar] = -1;
+	Nstar ++;
+      }
+      int Ngroup = input[0].average[N].Nstarpar;
+      for (j = 0; j < Ngroup - 1; j++) {
+	next_star[Nstar - Ngroup + j] = Nstar - Ngroup + j + 1;
+      }
+    }
+
     Nave ++;
   }
@@ -421,4 +477,5 @@
   REALLOCATE (output[0].measure, Measure, Nmeas);
   REALLOCATE (output[0].lensing, Lensing, Nlens);
+  REALLOCATE (output[0].starpar, StarPar, Nstar);
  
 # define NOSORT 0
@@ -429,4 +486,5 @@
     output[0].measure = sort_measure (output[0].average, Nave, output[0].measure, Nmeas, next_meas);
     output[0].lensing = sort_lensing (output[0].average, Nave, output[0].lensing, Nlens, next_lens);
+    output[0].starpar = sort_starpar (output[0].average, Nave, output[0].starpar, Nstar, next_star);
   }
 
@@ -445,4 +503,5 @@
   output[0].Nmeasure = Nmeas;
   output[0].Nlensing = Nlens;
+  output[0].Nstarpar = Nstar;
   output[0].Nsecf_mem = Nave*NsecfiltOut;
   if (VERBOSE) fprintf (stderr, "Nstars, Nave, Nmeas, Nlens: "OFF_T_FMT" "OFF_T_FMT" "OFF_T_FMT" "OFF_T_FMT", ("OFF_T_FMT" matches)\n",  Nstars,  Nave,  Nmeas,  Nlens, Nmatch);
@@ -450,4 +509,5 @@
   free (next_meas);
   free (next_lens);
+  free (next_star);
 
   free (X2);
Index: /trunk/Ohana/src/dvomerge/src/myIndex.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/myIndex.c	(revision 37807)
+++ /trunk/Ohana/src/dvomerge/src/myIndex.c	(revision 37807)
@@ -0,0 +1,72 @@
+# include "dvomerge.h"
+
+myIndexType *myIndexInit () {
+
+  myIndexType *myIndex;
+  ALLOCATE (myIndex, myIndexType, 1);
+
+  myIndex->minID = MAX_INT;
+  myIndex->maxID = 0;
+
+  myIndex->NINDEX = 1000;
+  myIndex->Nindex = 0;
+
+  ALLOCATE (myIndex->index, int, myIndex->NINDEX);
+
+  return myIndex;
+}
+
+int myIndexFree (myIndexType *myIndex) {
+
+  free (myIndex->index);
+  free (myIndex);
+  return TRUE;
+}
+
+// set minID and maxID externally
+int myIndexUpdateLimits (myIndexType *myIndex, int value) {
+
+  myIndex->maxID = MAX(value, myIndex->maxID);
+  myIndex->minID = MIN(value, myIndex->minID);
+
+  return TRUE;
+}
+
+// once minID and maxID are set, this function defines the range and inits
+int myIndexSetRange (myIndexType *myIndex) {
+
+  myIndex->Nindex = myIndex->maxID - myIndex->minID + 1;
+  myIndex->NINDEX = myIndex->Nindex;
+  
+  REALLOCATE (myIndex->index, int, myIndex->NINDEX);
+
+  int i;
+  for (i = 0; i < myIndex->Nindex; i++) {
+    myIndex->index[i] = -1;
+  }
+
+  return TRUE;
+}
+
+// once minID and maxID are set, this function defines the range and inits
+int myIndexSetEntry (myIndexType *myIndex, int value, int entry) {
+
+  int n = value - myIndex->minID;
+
+  myAssert (n >= 0, "impossible!");
+  myAssert(myIndex->index[n] == -1, "stomping on an earlier index");
+
+  myIndex->index[n] = entry;
+  return n;
+}
+
+// once minID and maxID are set, this function defines the range and inits
+int myIndexGetEntry (myIndexType *myIndex, int value) {
+
+  if (value < myIndex->minID) return -1; // not in this index
+  if (value > myIndex->maxID) return -1; // not in this index
+
+  int n = value - myIndex->minID;
+
+  return myIndex->index[n];
+}
Index: /trunk/Ohana/src/dvopsps/src/insert_detections_dvopsps_catalog.c
===================================================================
--- /trunk/Ohana/src/dvopsps/src/insert_detections_dvopsps_catalog.c	(revision 37806)
+++ /trunk/Ohana/src/dvopsps/src/insert_detections_dvopsps_catalog.c	(revision 37807)
@@ -239,7 +239,8 @@
   }
 
-  PrintIOBuffer (buffer, "(%lu, ", detection->objID);	    // objID
-  PrintIOBuffer (buffer,  "%lu, ", detection->detectID);    // detectID
-  PrintIOBuffer (buffer,  "%lu, ", detection->ippObjID);    // ippObjID
+  // XXX I needed OFF_T_FMT on my 32bit ubuntu laptop; ok on 64bit?
+  PrintIOBuffer (buffer, "("OFF_T_FMT", ", detection->objID);	    // objID
+  PrintIOBuffer (buffer,  OFF_T_FMT", ", detection->detectID);    // detectID
+  PrintIOBuffer (buffer,  OFF_T_FMT", ", detection->ippObjID);    // ippObjID
   PrintIOBuffer (buffer,  "%u,  ", detection->ippDetectID); // ippDetectID
   PrintIOBuffer (buffer,  "%d,  ", detection->imageID);	    // imageID
Index: /trunk/Ohana/src/dvopsps/src/insert_objects_dvopsps_catalog.c
===================================================================
--- /trunk/Ohana/src/dvopsps/src/insert_objects_dvopsps_catalog.c	(revision 37806)
+++ /trunk/Ohana/src/dvopsps/src/insert_objects_dvopsps_catalog.c	(revision 37807)
@@ -256,5 +256,5 @@
 // XXX this bit could/should be autocoded...
   PrintIOBuffer (ave_buffer, " (");
-  PrintIOBuffer (ave_buffer, "%lu, ", average->extID);          
+  PrintIOBuffer (ave_buffer, OFF_T_FMT", ", average->extID);          
   PrintIOBuffer (ave_buffer, "%u,  ", average->objID);          
   PrintIOBuffer (ave_buffer, "%u,  ", average->catID);          
Index: /trunk/Ohana/src/dvosplit/include/dvosplit.h
===================================================================
--- /trunk/Ohana/src/dvosplit/include/dvosplit.h	(revision 37806)
+++ /trunk/Ohana/src/dvosplit/include/dvosplit.h	(revision 37807)
@@ -22,4 +22,5 @@
 } AveLinks;
 
+int    FULL_TABLE;
 int    VERBOSE;
 char  *OUTDIR;
Index: /trunk/Ohana/src/dvosplit/src/args.c
===================================================================
--- /trunk/Ohana/src/dvosplit/src/args.c	(revision 37806)
+++ /trunk/Ohana/src/dvosplit/src/args.c	(revision 37807)
@@ -57,4 +57,11 @@
   }
 
+  // override input catalog format (PS1_V1, PS1_REF, etc)
+  FULL_TABLE = FALSE;
+  if ((N = get_argument (argc, argv, "-full-table"))) {
+    remove_argument (N, &argc, argv);
+    FULL_TABLE = TRUE;
+  }
+
   /* extra error messages */
   VERBOSE = FALSE;
Index: /trunk/Ohana/src/dvosplit/src/split_averages.c
===================================================================
--- /trunk/Ohana/src/dvosplit/src/split_averages.c	(revision 37806)
+++ /trunk/Ohana/src/dvosplit/src/split_averages.c	(revision 37807)
@@ -27,5 +27,5 @@
   incatalog[0].catflags = LOAD_AVES | LOAD_SECF;
 
-  if (incatalog[0].catmode == DVO_MODE_SPLIT) {
+  if ((incatalog[0].catmode == DVO_MODE_SPLIT) && !FULL_TABLE) {
     Nblocks = incatalog[0].Naves_disk / NROWS;
     if (incatalog[0].Naves_disk % NROWS) Nblocks ++;
@@ -36,14 +36,13 @@
   for (block = 0; block < Nblocks; block++) {
 
-    if (incatalog[0].catmode == DVO_MODE_SPLIT) {
+    if ((incatalog[0].catmode == DVO_MODE_SPLIT) && !FULL_TABLE) {
       // read up to NROWS at a time
       dvo_catalog_load_segment (incatalog, VERBOSE, block*NROWS, NROWS);
       fprintf (stderr, "splitting %s (averages) .. %d of %d\n", incatalog[0].filename, block, Nblocks);
+      assert (block*NROWS == incatalog[0].Naves_off);
     } else {
       dvo_catalog_load (incatalog, VERBOSE);
       fprintf (stderr, "splitting %s (averages)\n", incatalog[0].filename);
     }
-
-    assert (block*NROWS == incatalog[0].Naves_off);
 
     // distribute data to the output catalogs
@@ -54,4 +53,8 @@
       inD = incatalog[0].average[ave].D;
 
+      if (incatalog[0].average[ave].Nmeasure == 0) {
+	fprintf (stderr, "WARNING: object with no measurements, skipping %d (%f, %f)\n", averef, inR, inD);
+	continue;
+      }
       // which of the outcatalogs contains this coordinate?
 
@@ -98,25 +101,29 @@
 
     // write out the new values
-    for (cat = 0; cat < outlist[0].Nregions; cat++) {
-      outcatalogs[cat].catflags = LOAD_AVES | LOAD_SECF;
+    if (!FULL_TABLE) {
+      for (cat = 0; cat < outlist[0].Nregions; cat++) {
+	outcatalogs[cat].catflags = LOAD_AVES | LOAD_SECF;
 
-      dvo_catalog_save (&outcatalogs[cat], VERBOSE);
-      // fprintf (stderr, "secfilt: %d %d %d %d\n", outcatalogs[cat].Nsecf_mem, outcatalogs[cat].Nsecf_disk, outcatalogs[cat].Nsecf_off, outcatalogs[cat].Naverage, outcatalogs[cat].Nsecfilt);
+	dvo_catalog_save (&outcatalogs[cat], VERBOSE);
+	// fprintf (stderr, "secfilt: %d %d %d %d\n", outcatalogs[cat].Nsecf_mem, outcatalogs[cat].Nsecf_disk, outcatalogs[cat].Nsecf_off, outcatalogs[cat].Naverage, outcatalogs[cat].Nsecfilt);
 
-      // advance the pointers and free the current data
-      // XXX these should be done within save segment:
-      outcatalogs[cat].Naves_disk += outcatalogs[cat].Naverage;
-      outcatalogs[cat].Naves_off  += outcatalogs[cat].Naverage;
-      outcatalogs[cat].Nsecf_disk += outcatalogs[cat].Nsecfilt * outcatalogs[cat].Naverage;
-      outcatalogs[cat].Nsecf_off  += outcatalogs[cat].Nsecfilt * outcatalogs[cat].Naverage;
-      outcatalogs[cat].Nsecfilt    = Nsecfilt;
+	// advance the pointers and free the current data
+	// XXX these should be done within save segment:
+	outcatalogs[cat].Naves_disk += outcatalogs[cat].Naverage;
+	outcatalogs[cat].Naves_off  += outcatalogs[cat].Naverage;
+	outcatalogs[cat].Nsecf_disk += outcatalogs[cat].Nsecfilt * outcatalogs[cat].Naverage;
+	outcatalogs[cat].Nsecf_off  += outcatalogs[cat].Nsecfilt * outcatalogs[cat].Naverage;
+	outcatalogs[cat].Nsecfilt    = Nsecfilt;
 
-      outcatalogs[cat].Naverage    = 0;
-      outcatalogs[cat].Nsecf_mem   = 0;
+	outcatalogs[cat].Naverage    = 0;
+	outcatalogs[cat].Nsecf_mem   = 0;
+      }
     }
   }
 
-  for (cat = 0; cat < outlist[0].Nregions; cat++) {
-    dvo_catalog_free_data (&outcatalogs[cat]);
+  if (!FULL_TABLE) {
+    for (cat = 0; cat < outlist[0].Nregions; cat++) {
+      dvo_catalog_free_data (&outcatalogs[cat]);
+    }
   }
 
Index: /trunk/Ohana/src/dvosplit/src/split_measures.c
===================================================================
--- /trunk/Ohana/src/dvosplit/src/split_measures.c	(revision 37806)
+++ /trunk/Ohana/src/dvosplit/src/split_measures.c	(revision 37807)
@@ -27,5 +27,5 @@
   // }
 
-  if (incatalog[0].catmode == DVO_MODE_SPLIT) {
+  if ((incatalog[0].catmode == DVO_MODE_SPLIT) && !FULL_TABLE) {
     Nblocks = incatalog[0].Nmeas_disk / NROWS;
     if (incatalog[0].Nmeas_disk % NROWS) Nblocks ++;
@@ -37,13 +37,12 @@
 
     // read up to NROWS at a time
-    if (incatalog[0].catmode == DVO_MODE_SPLIT) {
+    if ((incatalog[0].catmode == DVO_MODE_SPLIT) && !FULL_TABLE) {
       dvo_catalog_load_segment (incatalog, VERBOSE, block*NROWS, NROWS);
       fprintf (stderr, "splitting %s (measures) .. %d of %d\n", incatalog[0].filename, block, Nblocks);
+      assert (block*NROWS == incatalog[0].Nmeas_off);
     } else {
       dvo_catalog_load (incatalog, VERBOSE);
       fprintf (stderr, "splitting %s (measures)\n", incatalog[0].filename);
     }
-
-    assert (block*NROWS == incatalog[0].Nmeas_off);
 
     for (meas = 0; meas < incatalog[0].Nmeasure; meas++) {
@@ -52,7 +51,14 @@
       Ncat = outcat[averef];
 
+      int averef_out = outref[averef];
+      if (averef_out >= outcatalogs[Ncat].Naverage) {
+	fprintf (stderr, "mismatch 1\n");
+	abort();
+      }
+
       Nout = outcatalogs[Ncat].Nmeasure;
       outcatalogs[Ncat].measure[Nout] = incatalog[0].measure[meas];
       outcatalogs[Ncat].measure[Nout].averef = outref[averef];
+
       outcatalogs[Ncat].Nmeasure++;
 
@@ -64,12 +70,20 @@
     dvo_catalog_free_data (incatalog);
 
+    if (!FULL_TABLE) {
+      for (cat = 0; cat < outlist[0].Nregions; cat++) {
+	outcatalogs[cat].catflags = LOAD_MEAS;
+	dvo_catalog_save (&outcatalogs[cat], VERBOSE);
+
+	outcatalogs[cat].Nmeas_disk += outcatalogs[cat].Nmeasure;
+	outcatalogs[cat].Nmeas_off  += outcatalogs[cat].Nmeasure;
+	outcatalogs[cat].Nmeasure    = 0;
+      }
+    }
+  }
+
+  if (FULL_TABLE) {
     for (cat = 0; cat < outlist[0].Nregions; cat++) {
-      outcatalogs[cat].catflags = LOAD_MEAS;
+      outcatalogs[cat].catflags = LOAD_AVES | LOAD_SECF | LOAD_MEAS;
       dvo_catalog_save (&outcatalogs[cat], VERBOSE);
-
-      outcatalogs[cat].Nmeas_disk += outcatalogs[cat].Nmeasure;
-      outcatalogs[cat].Nmeas_off  += outcatalogs[cat].Nmeasure;
-      outcatalogs[cat].Nmeasure    = 0;
-
     }
   }
Index: /trunk/Ohana/src/fakeastro/Makefile
===================================================================
--- /trunk/Ohana/src/fakeastro/Makefile	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/Makefile	(revision 37807)
@@ -0,0 +1,77 @@
+default: fakeastro fakeastro_client
+help:
+	@echo "make options: fakeastro (default)"
+
+include ../../Makefile.System
+HOME    =       $(ROOT)/src/fakeastro
+BIN	=	$(HOME)/bin
+LIB	=	$(HOME)/lib
+SRC	=	$(HOME)/src
+MAN	=	$(HOME)/doc
+INC	= 	$(HOME)/include
+include ../../Makefile.Common
+
+# programs may add their own internal requirements here
+FULL_CFLAGS   = $(BASE_CFLAGS)
+FULL_CPPFLAGS = $(BASE_CPPFLAGS)
+FULL_LDFLAGS  = -lkapa -ldvo -lFITS -lohana $(BASE_LDFLAGS)
+
+fakeastro: $(BIN)/fakeastro.$(ARCH)
+fakeastro_client: $(BIN)/fakeastro_client.$(ARCH)
+
+install: $(DESTBIN)/fakeastro $(DESTBIN)/fakeastro_client $(DESTBIN)/testparallax
+
+FAKEASTRO = \
+$(SRC)/ConfigInit.$(ARCH).o	     \
+$(SRC)/SetSignals.$(ARCH).o 	     \
+$(SRC)/Shutdown.$(ARCH).o 	     \
+$(SRC)/args.$(ARCH).o		     \
+$(SRC)/initialize.$(ARCH).o	     \
+$(SRC)/fakeastro.$(ARCH).o	     \
+$(SRC)/fakeastro_galaxy.$(ARCH).o    \
+$(SRC)/StarOps.$(ARCH).o    \
+$(SRC)/make_fakestars.$(ARCH).o \
+$(SRC)/make_fakeqsos.$(ARCH).o \
+$(SRC)/make_subset.$(ARCH).o \
+$(SRC)/save_fakestars.$(ARCH).o \
+$(SRC)/fakestar_catalog.$(ARCH).o \
+$(SRC)/fakestar_io.$(ARCH).o \
+$(SRC)/insert_fakestar.$(ARCH).o \
+$(SRC)/gaussian.$(ARCH).o \
+$(SRC)/fakeastro_images.$(ARCH).o \
+$(SRC)/fakeastro_images_region.$(ARCH).o \
+$(SRC)/load_template_images.$(ARCH).o \
+$(SRC)/make_fake_images.$(ARCH).o \
+$(SRC)/fit_fake_stars.$(ARCH).o \
+$(SRC)/fitpoly.$(ARCH).o \
+$(SRC)/AstromOffsetMapOps.$(ARCH).o \
+$(SRC)/airmass.$(ARCH).o \
+$(SRC)/get_image_patch.$(ARCH).o \
+$(SRC)/load_fake_stars.$(ARCH).o \
+$(SRC)/make_fake_stars.$(ARCH).o \
+$(SRC)/make_fake_stars_catalog.$(ARCH).o \
+$(SRC)/save_fake_stars.$(ARCH).o \
+$(SRC)/match_fake_stars.$(ARCH).o \
+$(SRC)/fakeastro_2mass.$(ARCH).o \
+$(SRC)/make_2mass_measures.$(ARCH).o \
+$(SRC)/remote_hosts.$(ARCH).o 
+
+#$(SRC)/fakeastro_images.$(ARCH).o    \
+
+$(FAKEASTRO): $(INC)/fakeastro.h
+$(BIN)/fakeastro.$(ARCH): $(FAKEASTRO)
+
+FAKEASTRO_CLIENT = \
+$(SRC)/ConfigInit.$(ARCH).o	     \
+$(SRC)/SetSignals.$(ARCH).o 	     \
+$(SRC)/Shutdown.$(ARCH).o 	     \
+$(SRC)/args.$(ARCH).o		     \
+$(SRC)/initialize.$(ARCH).o	     \
+$(SRC)/fakeastro_client.$(ARCH).o	\
+$(SRC)/fakestar_io.$(ARCH).o \
+$(SRC)/fakestar_catalog.$(ARCH).o \
+$(SRC)/insert_fakestar.$(ARCH).o \
+
+$(FAKEASTRO_CLIENT): $(INC)/fakeastro.h
+$(BIN)/fakeastro_client.$(ARCH): $(FAKEASTRO_CLIENT)
+
Index: /trunk/Ohana/src/fakeastro/doc/example.sh
===================================================================
--- /trunk/Ohana/src/fakeastro/doc/example.sh	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/doc/example.sh	(revision 37807)
@@ -0,0 +1,80 @@
+
+$Z_GAL = 500.0
+$R_GAL = 2000.0
+$N_GAL = 10000000
+
+macro mkgal
+ create f 0 $N_GAL
+ set r = $R_GAL*sqrt(rnd(f))
+ set L = rnd(f)*360.0
+ gaussdev z r[] 0.0 $Z_GAL
+ set B = atan2 (z , r)
+
+ set RA = L
+ set DEC = B
+
+ csystem G C RA DEC
+end
+
+macro showgal
+  region 0 0 90 ait
+  box
+  cgrid -c red
+  section default -imtool none ; parity 0 1
+  cdensify img RA DEC 
+  set limg = log(img)
+  tv limg -0.1 2.0
+  tvcolor rainbow
+end
+
+macro check.proper.motion
+
+  # create a bunch of test stars:
+  create n 0 10000
+  set L = 360.0*rnd(n)
+  set B = dasin(2.0*rnd(n) - 1.0)
+
+  # choose a hardwired proper motion
+  set uL = 1.0*(rnd(n) - 0.5)
+  set uB = 1.0*(rnd(n) - 0.5)
+
+  set Bo = B + uB/3600.0
+  set Lo = L + uL/3600.0/dcos(B)
+
+  set R = L
+  set D = B
+  set Ro = Lo
+  set Do = Bo
+
+  csystem G C R D
+  csystem G C Ro Do
+
+  set uR1 = (Ro - R)*3600.0*dcos(D)
+  set uD1 = (Do - D)*3600.0
+
+  $Xo  = 282.8594812080
+  $xo  =  32.9319185700
+  $phi = -62.8717488056
+
+  $delta_G = 90 + $phi
+  $alpha_G = $Xo - 90
+
+  # I got this wrong:
+  # set C1 = dcos(D)*dcos($phi) + dsin(D)*dcos(R)*dsin($phi)*dsin($Xo) + dsin(D)*dsin(R)*dsin($phi)*dcos($Xo)
+  # set C2 =                              dcos(R)*dsin($phi)*dcos($Xo) +         dsin(R)*dsin($phi)*dsin($Xo)
+
+  # corrected version
+    set C1 = dcos(D)*dcos($phi) + dsin(D)*dcos(R)*dsin($phi)*dsin($Xo) - dsin(D)*dsin(R)*dsin($phi)*dcos($Xo)
+    set C2 =                           -1*dcos(R)*dsin($phi)*dcos($Xo) -         dsin(R)*dsin($phi)*dsin($Xo)
+
+  # set C1 = dcos(D)*dsin($delta_G) - dsin(D)*dcos($delta_G)*dcos(R - $alpha_G)
+  # set C2 =                                  dcos($delta_G)*dsin(R - $alpha_G)
+
+  set cB = 1.0 / sqrt(C1^2 + C2^2)
+
+  set uR2 = cB * (C1 * uL - C2 * uB)
+  set uD2 = cB * (C1 * uB + C2 * uL)
+
+  set duR = uR1 - uR2
+  set duD = uD1 - uD2
+end
Index: /trunk/Ohana/src/fakeastro/include/fakeastro.h
===================================================================
--- /trunk/Ohana/src/fakeastro/include/fakeastro.h	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/include/fakeastro.h	(revision 37807)
@@ -0,0 +1,200 @@
+# include <ohana.h>
+# include <dvo.h>
+# include <kapa.h>
+# include <signal.h>
+# include <assert.h>
+# include <pthread.h>
+
+# define RESETTIME { gettimeofday (&startTimer, (void *) NULL); }
+
+typedef enum {OP_NONE, OP_GALAXY, OP_IMAGES, OP_2MASS} FakeastroOp;
+
+typedef struct {
+  int   NfakeImage;
+  int   NFAKEIMAGE;
+  Image *fakeImage;
+
+  int   NtrueImage;
+  int   NTRUEIMAGE;
+  Image *trueImage;
+} ImageInfo;
+
+typedef struct {
+  double R, D;
+  StarPar starpar;
+  int flag; // in a subset?
+  int found; // assigned to an object?
+} FakeAstro_Stars;
+
+typedef struct {
+  double Rref; // "reference" coordinate of the fake stars
+  double Dref;
+  Average  average;
+  Measure  measure;
+  StarPar  starpar;
+  int found;
+} Stars;
+
+// structure to hold coordinate fitting terms
+typedef struct {
+    int Npts;
+    int Nterms;
+    int Norder;
+    int Nsums;
+    int Nelems;
+    double **sum;
+    double **xsum;
+    double **ysum;
+    double **xfit;
+    double **yfit;
+} CoordFit;
+
+/* used in find_matches, find_matches_refstars */
+# define IN_REGION(R,D) ( \
+((D) >= region[0].Dmin) && ((D) < region[0].Dmax) && \
+((R) >= region[0].Rmin) && ((R) < region[0].Rmax))
+
+# define myAbortF(FORMAT,...) { fprintf (stderr, FORMAT, __VA_ARGS__); abort(); }
+
+/**** global variables set in parameter file ***/
+
+# define DVO_MAX_PATH 1024
+
+char   GSCFILE[DVO_MAX_PATH];
+char   CATDIR[DVO_MAX_PATH];
+char   SKY_TABLE[DVO_MAX_PATH];
+char   MasterPhotcodeFile[DVO_MAX_PATH];
+
+char   CATMODE[16];    /* raw, mef, split, mysql */
+char   CATFORMAT[16];  /* internal, elixir, loneos, panstarrs */
+int    SKY_DEPTH;      /* depth of catalog tables, fix usage */
+
+int    HOST_ID;
+char  *HOSTDIR;
+char  *CPT_FILE;
+char  *INPUT;
+
+int    IMAGE_ID;
+
+char  *IMAGES_INPUT;
+char  *CATDIR_INPUT;
+char  *CATDIR_OUTPUT;
+
+int    PARALLEL;
+int    PARALLEL_MANUAL;
+int    PARALLEL_SERIAL;
+
+int    VERBOSE;
+int    VERBOSE2;
+int    TESTING;
+int    ONE_BIG_CHIP;
+
+int    FORCE;
+int    UNIFORM_RADEC;
+
+int    FAKEASTRO_NLOOP;
+int    FAKEASTRO_NSTARS;
+int    FAKEASTRO_NQSO_ICRF;
+int    FAKEASTRO_NQSO_ZERO;
+float  FAKEASTRO_ZGAL; // parsec
+float  FAKEASTRO_RGAL; // parsec
+char   FAKEASTRO_REF_EPOCH[80];
+char   FAKEASTRO_2MASS_EPOCH[80];
+
+float  RADIUS;
+
+SkyRegion UserPatch;
+
+FakeastroOp FAKEASTRO_OP;
+
+/*** fakeastro prototypes ***/
+
+void          ConfigInit          PROTO((int *argc, char **argv));
+void          GetConfig           PROTO((char *config, char *field, char *format, int N, void *ptr));
+int           args                PROTO((int *argc, char **argv));
+int           args_client         PROTO((int *argc, char **argv));
+
+void          set_db              PROTO((FITS_DB *in));
+int           Shutdown            PROTO((char *format, ...)) OHANA_FORMAT(printf, 1, 2);
+void          TrapSignal          PROTO((int sig));
+void          SetProtect          PROTO((int mode));
+int           SetSignals          PROTO((void));
+
+void initialize (int argc, char **argv);
+void initialize_client (int argc, char **argv);
+
+int fakeastro_galaxy ();
+FakeAstro_Stars *make_fakestars (int Nstars);
+int sortStars (FakeAstro_Stars *stars, int Nstars);
+
+FakeAstro_Stars *make_subset (FakeAstro_Stars *stars, int Nstars, int start, SkyRegion *region, int *nsubset);
+
+int save_fakestars (FakeAstro_Stars *stars, int Nstars, HostTable *hosts, SkyRegion *region, char *fullname);
+
+FakeAstro_Stars *fakestar_load_stars (char *filename, int *nstars);
+int fakestar_save_stars (char *filename, FakeAstro_Stars *stars, int Nstars);
+
+int harvest_host ();
+int save_remote_host (HostInfo *host);
+int harvest_all ();
+int find_empty_slot ();
+int init_remote_hosts ();
+
+int fakestar_catalog (FakeAstro_Stars *stars, int Nstars, SkyRegion *region, char *filename);
+int insert_fakestar (SkyRegion *region, FakeAstro_Stars *stars, int Nstars, Catalog *catalog);
+
+double gaussian (double x, double mean, double sigma);
+void gauss_init (int Nbin);
+double rnd_gauss (double mean, double sigma);
+double int_gauss (int i);
+
+int strextend (char *input, char *format,...);
+
+int fakeastro_images ();
+Image *load_template_images (int *nimage);
+Image *make_fake_images (Image *image, int *nfakeImage);
+
+int fakeastro_images_region (ImageInfo *imageInfo, Image *refImage, int NrefImage, SkyTable *skyTableInput, SkyTable *skyTableOutput, SkyRegion *innerRegion);
+
+SkyRegion *get_image_patch (Image *image);
+SkyRegion *get_mosaic_patch (Image *image);
+
+int SkyRegionsOverlap (SkyRegion *region, SkyRegion *patch);
+int SkyRegionHasPoint (SkyRegion *region, double R, double D);
+SkyRegion *SkyRegionExpand (SkyRegion *region, float boundary);
+
+Catalog *load_fake_stars (SkyList *skylist, int *ncatalog);
+
+Stars *make_fake_stars (Catalog *catalog, int Ncatalog, SkyList *skylist, Image *image, Stars *stars, int *nstars);
+Stars *make_fake_stars_catalog (Stars *stars, int *nstars, SkyRegion *patch, Catalog *catalog, Image *image);
+
+int save_fake_stars (SkyTable *sky, Image *image, Stars *stars, int Nstars);
+int match_fake_stars (Stars *stars, unsigned int NstarsIn, SkyRegion *region, Catalog *catalog);
+
+int InitStar (Stars *star);
+
+float airmass (float secz_image, double ra, double dec, double st, double latitude);
+float azimuth (double ha, double dec, double latitude);
+
+int fit_fake_stars (Stars *stars, int Nstars, Image *image);
+
+double **array_init (int Nx, int Ny);
+void array_free (double **array, int Nx);
+CoordFit *fit_init (int order);
+void fit_free (CoordFit *fit);
+void fit_add (CoordFit *fit, double x1, double y1, double x2, double y2, double wt);
+int fit_eval (CoordFit *fit);
+void fit_apply (CoordFit *fit, double *x2, double *y2, double x1, double y1);
+double **poly2d_dx (double **poly, int Nx, int Ny);
+double **poly2d_dy (double **poly, int Nx, int Ny);
+double **poly2d_copy (double **poly, int Nx, int Ny);
+double poly2d_eval (double **poly, int Nx, int Ny, double x, double y);
+int fit_apply_coords (CoordFit *fit, Coords *coords, int keepRef);
+
+int save_astrom_table ();
+AstromOffsetTable *get_astrom_table ();
+
+FakeAstro_Stars *make_fakeqsos (int Nstars, int ICRF);
+
+int fakeastro_2mass ();
+int make_2mass_measures (Catalog *catalog);
Index: /trunk/Ohana/src/fakeastro/src/AstromOffsetMapOps.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/AstromOffsetMapOps.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/AstromOffsetMapOps.c	(revision 37807)
@@ -0,0 +1,16 @@
+# include "fakeastro.h"
+
+static AstromOffsetTable *table = NULL;
+
+int save_astrom_table () {
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", CATDIR);
+  AstromOffsetMapSave (table, mapfile);
+
+  return TRUE;
+}
+
+AstromOffsetTable *get_astrom_table () {
+  return table;
+}
Index: /trunk/Ohana/src/fakeastro/src/ConfigInit.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/ConfigInit.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/ConfigInit.c	(revision 37807)
@@ -0,0 +1,111 @@
+# include "fakeastro.h"
+
+void ConfigInit (int *argc, char **argv) {
+
+  char  *config, *file;
+  char CatdirPhotcodeFile[256];
+
+  /*** load configuration info ***/
+  file = SelectConfigFile (argc, argv, "ptolemy");
+  config = LoadConfigFile (file);
+  if (config == (char *) NULL) {
+    fprintf (stderr, "ERROR: can't find configuration file %s\n", file);
+    if (file != (char *) NULL) free (file);
+    exit (0);
+  }
+  if (VERBOSE) fprintf (stderr, "loaded config file: %s\n", file);
+
+  // set defaults for all of these if they are not used by parallel / remote clients
+  // if (!ScanConfig (config, "ADDSTAR_RADIUS",         "%lf", 0, &ADDSTAR_RADIUS))   ADDSTAR_RADIUS = 1.0;
+
+  if ((FAKEASTRO_OP == OP_GALAXY) || (FAKEASTRO_OP == OP_2MASS)) {
+    // force CATDIR to be absolute (so parallel mode will work)
+    GetConfig (config, "CATDIR",                 "%s",  0, CATDIR);
+    char *tmpcatdir = abspath (CATDIR, DVO_MAX_PATH);
+    strcpy (CATDIR, tmpcatdir);
+    free (tmpcatdir);
+  }
+
+  GetConfig (config, "GSCFILE",                "%s",  0, GSCFILE);
+  ScanConfig(config, "CATMODE",                "%s",  0, CATMODE);
+  ScanConfig(config, "CATFORMAT",              "%s",  0, CATFORMAT);
+  GetConfig (config, "PHOTCODE_FILE",          "%s",  0, MasterPhotcodeFile);
+
+  if (!ScanConfig (config, "SKY_DEPTH",         "%d",  0, &SKY_DEPTH)) SKY_DEPTH = 2;
+  if (!ScanConfig (config, "SKY_TABLE",         "%s",  0, SKY_TABLE)) SKY_TABLE[0] = 0;
+
+  
+  if (!ScanConfig (config, "FAKEASTRO_NLOOP", "%d", 0, &FAKEASTRO_NLOOP)) {
+    FAKEASTRO_NLOOP = 1;
+  }
+  if (!ScanConfig (config, "FAKEASTRO_NSTARS", "%d", 0, &FAKEASTRO_NSTARS)) {
+    FAKEASTRO_NSTARS = 5000000;
+  }
+  if (!ScanConfig (config, "FAKEASTRO_NQSO_ICRF", "%d", 0, &FAKEASTRO_NQSO_ICRF)) {
+    FAKEASTRO_NQSO_ICRF = 5000;
+  }
+  if (!ScanConfig (config, "FAKEASTRO_NQSO_ZERO", "%d", 0, &FAKEASTRO_NQSO_ZERO)) {
+    FAKEASTRO_NQSO_ZERO = 200000;
+  }
+  if (!ScanConfig (config, "FAKEASTRO_ZGAL", "%f", 0, &FAKEASTRO_ZGAL)) {
+    FAKEASTRO_ZGAL = 500.0; // parsec
+  }
+  if (!ScanConfig (config, "FAKEASTRO_RGAL", "%f", 0, &FAKEASTRO_RGAL)) {
+    FAKEASTRO_RGAL = 2000.0; // parsec
+  }
+  if (!ScanConfig (config, "FAKEASTRO_REF_EPOCH", "%s", 0, FAKEASTRO_REF_EPOCH)) {
+    strcpy (FAKEASTRO_REF_EPOCH, "2000/01/01,00:00:00"); // epoch of truth positions
+  }
+  if (!ScanConfig (config, "FAKEASTRO_2MASS_EPOCH", "%s", 0, FAKEASTRO_2MASS_EPOCH)) {
+    strcpy (FAKEASTRO_2MASS_EPOCH, "2000/01/01,00:00:00"); // epoch of 2MASS astrometry
+  }
+
+  /* set the default search radius */
+  if (!ScanConfig (config, "ADDSTAR_RADIUS", "%f", 0, &RADIUS)) {
+    GetConfig (config, "RADIUS", "%f", 0, &RADIUS);
+  }
+  if (RADIUS < 0.0001) {
+    fprintf (stderr, "absurd match radius %f\n", RADIUS);
+    exit (2);
+  }
+
+  if (*CATMODE == 0) strcpy (CATMODE, "RAW");
+  if (*CATFORMAT == 0) strcpy (CATFORMAT, "ELIXIR");
+
+  char *CATDIR_CHECK = (FAKEASTRO_OP == OP_IMAGES) ? CATDIR_OUTPUT : CATDIR;
+
+  // OP_2MASS is adding detections to an existing db, the others require and empty db
+  if (FAKEASTRO_OP != OP_2MASS) {
+    // check for existence of CATDIR
+    struct stat filestat;
+    int status = stat (CATDIR_CHECK, &filestat);
+    if (!FORCE && (status == 0)) {
+      fprintf (stderr, "directory %s exists, refusing to contaminate\n", CATDIR);
+      exit (1);
+    }
+  }
+
+  /* update master photcode table if not defined */
+  sprintf (CatdirPhotcodeFile, "%s/Photcodes.dat", CATDIR_CHECK);
+  if (!LoadPhotcodes (CatdirPhotcodeFile, MasterPhotcodeFile, TRUE)) {
+    fprintf (stderr, "error loading photcode table %s\n", CatdirPhotcodeFile);
+    exit (1);
+  }
+  SetZeroPoint (25.0);
+
+  free (config);
+  free (file);
+
+}
+
+void GetConfig (char *config, char *field, char *format, int N, void *ptr) {
+
+  char *status;
+
+  status = ScanConfig (config, field, format, N, ptr);
+  if (status == NULL) {
+    fprintf (stderr, "error in config, cannot find %s\n", field);
+    exit (1);
+  }
+  return;
+}
Index: /trunk/Ohana/src/fakeastro/src/SetSignals.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/SetSignals.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/SetSignals.c	(revision 37807)
@@ -0,0 +1,122 @@
+# include "fakeastro.h"
+
+static int Protect = FALSE;
+static int Trapped = FALSE;
+
+void TrapSignal (int sig) {
+    fprintf (stderr, "trapped signal %d\n", sig);
+    if (sig == 11) {
+      fprintf (stderr, "seg fault\n");
+      exit (1);
+    }
+    if (Protect) {
+      Trapped = TRUE;
+      fprintf (stderr, "blocking until protected sections are clear\n");
+      return;
+    }
+    Shutdown ("halted by signal (trapped)");
+}    
+
+void SetProtect (int mode) {
+  Protect = mode;
+  if (Trapped && !Protect) Shutdown ("halted by signal (protect)");
+}
+
+int SetSignals () {
+
+  int i;
+
+  /* disable almost all signal interrupts */
+  for (i = 0; i < 36; i++) {
+    switch (i) {
+      /* can't redirect this signals */
+    case SIGKILL:    /* kill -9: cannot be caught or ignored */
+    case SIGSTOP:    /* SIGSTOP: cannot be caught or ignored */
+      /* ignore these signals */
+    case SIGCHLD:    /* child halted: ignore */
+    case SIGCONT:    /* continue - maintain this action */
+    case SIGTSTP:    /* stop signal sent from tty - why ignore? */
+    case SIGURG:     /* socket signal, ignore this */
+# ifdef SIGPWR
+    case SIGPWR:     /* power failure - why ignore this? (Sys V) */
+# endif
+# ifdef SIGWINCH
+    case SIGWINCH:   /* window resized (4.3BSD) */
+# endif
+      break;
+      
+    default:
+      signal (i, TrapSignal);
+    }
+  }
+  return (TRUE);
+}
+/*
+
+       Signal     Value     Action   Comment
+       -------------------------------------------------------------------------
+       SIGHUP        1        A      Hangup detected on controlling terminal
+                                     or death of controlling process
+       SIGINT        2        A      Interrupt from keyboard
+       SIGQUIT       3        A      Quit from keyboard
+       SIGILL        4        A      Illegal Instruction
+       SIGABRT       6        C      Abort signal from abort(3)
+       SIGFPE        8        C      Floating point exception
+       SIGKILL       9       AEF     Kill signal
+       SIGSEGV      11        C      Invalid memory reference
+       SIGPIPE      13        A      Broken pipe: write to pipe with no readers
+       SIGALRM      14        A      Timer signal from alarm(2)
+       SIGTERM      15        A      Termination signal
+       SIGUSR1   30,10,16     A      User-defined signal 1
+       SIGUSR2   31,12,17     A      User-defined signal 2
+       SIGCHLD   20,17,18     B      Child stopped or terminated
+       SIGCONT   19,18,25            Continue if stopped
+       SIGSTOP   17,19,23    DEF     Stop process
+       SIGTSTP   18,20,24     D      Stop typed at tty
+       SIGTTIN   21,21,26     D      tty input for background process
+       SIGTTOU   22,22,27     D      tty output for background process
+
+       Next various other signals.
+
+       Signal       Value     Action   Comment
+       ---------------------------------------------------------------------
+       SIGTRAP        5         CG     Trace/breakpoint trap
+       SIGIOT         6         CG     IOT trap. A synonym for SIGABRT
+       SIGEMT       7,-,7       G
+       SIGBUS      10,7,10      AG     Bus error
+       SIGSYS      12,-,12      G      Bad argument to routine (SVID)
+       SIGSTKFLT    -,16,-      AG     Stack fault on coprocessor
+       SIGURG      16,23,21     BG     Urgent condition on socket (4.2 BSD)
+       SIGIO       23,29,22     AG     I/O now possible (4.2 BSD)
+       SIGPOLL                  AG     A synonym for SIGIO (System V)
+       SIGCLD       -,-,18      G      A synonym for SIGCHLD
+       SIGXCPU     24,24,30     AG     CPU time limit exceeded (4.2 BSD)
+       SIGXFSZ     25,25,31     AG     File size limit exceeded (4.2 BSD)
+       SIGVTALRM   26,26,28     AG     Virtual alarm clock (4.2 BSD)
+       SIGPROF     27,27,29     AG     Profile alarm clock
+       SIGPWR      29,30,19     AG     Power failure (System V)
+       SIGINFO      29,-,-      G      A synonym for SIGPWR
+       SIGLOST      -,-,-       AG     File lock lost
+       SIGWINCH    28,28,20     BG     Window resize signal (4.3 BSD, Sun)
+       SIGUNUSED    -,31,-      AG     Unused signal
+       (Here - denotes that a signal is absent; there where three values are given, the first one is usually  valid  for  alpha  and
+       sparc,  the  middle  one  for i386 and ppc, the last one for mips. Signal 29 is SIGINFO / SIGPWR on an alpha but SIGLOST on a
+       sparc.)
+
+       The letters in the "Action" column have the following meanings:
+
+       A      Default action is to terminate the process.
+
+       B      Default action is to ignore the signal.
+
+       C      Default action is to dump core.
+
+       D      Default action is to stop the process.
+
+       E      Signal cannot be caught.
+
+       F      Signal cannot be ignored.
+
+       G      Not a POSIX.1 conformant signal.
+
+*/
Index: /trunk/Ohana/src/fakeastro/src/Shutdown.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/Shutdown.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/Shutdown.c	(revision 37807)
@@ -0,0 +1,30 @@
+# include "fakeastro.h"
+
+static FITS_DB *db;
+
+void set_db (FITS_DB *in) {
+  db = in;
+}
+
+/* clean up open / locked ImageCat before shutting down */
+int Shutdown (char *format, ...) {  
+  va_list argp;
+  char *formatplus;
+  
+  ALLOCATE (formatplus, char, strlen(format));
+  strcpy (formatplus, format);
+  strcat (formatplus, "\n");
+
+  va_start (argp, format);
+  vfprintf (stderr, formatplus, argp);
+  free (formatplus);
+  va_end (argp);
+
+  SetProtect (TRUE);
+  gfits_db_close (db);
+  fprintf (stderr, "ERROR: relastro halted\n");
+  exit (1);
+}
+
+
+/* XXX this is probably not needed anymore : just protect the write statements */
Index: /trunk/Ohana/src/fakeastro/src/StarOps.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/StarOps.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/StarOps.c	(revision 37807)
@@ -0,0 +1,10 @@
+# include "fakeastro.h"
+
+int InitStar (Stars *star) {
+
+
+    dvo_measure_init (&star[0].measure);
+    dvo_average_init (&star[0].average);
+    star[0].found = -1; // found == -1 -> not yet found (use enums?)
+    return TRUE;
+}
Index: /trunk/Ohana/src/fakeastro/src/airmass.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/airmass.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/airmass.c	(revision 37807)
@@ -0,0 +1,50 @@
+# include "fakeastro.h"
+
+static int Nbad_airmass = 0;
+
+float airmass (float secz_image, double ra, double dec, double st, double latitude) {
+
+  double hour, cosz, secz;
+  double rdec, rlat;
+
+  /* ra, dec, latitude in dec deg; st in dec hours */
+
+  /* hour : hour angle in degrees */
+  rdec = RAD_DEG*dec;
+  rlat = RAD_DEG*latitude;
+  hour = 15.0*st - ra;
+  cosz = sin (rdec) * sin (rlat) + cos (rdec) * cos (RAD_DEG*hour) * cos (rlat);
+  secz = 1.000 / cosz;
+  
+  if (!isfinite(secz)) {
+    Nbad_airmass ++;
+  }
+  if ((Nbad_airmass > 10) && ((Nbad_airmass % 1000) == 10)) {
+    fprintf (stderr, "*** WARNING *** %d NaN airmass values : what is the problem?\n", Nbad_airmass);
+  }
+  if (Nbad_airmass > 1) {
+    fprintf (stderr, "*** WARNING *** NaN airmass for detection\n");
+    fprintf (stderr, "*** ra, dec : %f, %f | st : %f | lat : %f | cosz : %f\n", ra, dec, st, latitude, cosz);
+  }
+
+  return (secz);
+}
+
+// ha/dec -> az
+float azimuth (double ha, double dec, double latitude) {
+
+  double rdec, rlat, rha;
+  double sinh, cosh;
+  float az;
+
+  rdec = RAD_DEG*dec;
+  rha = RAD_DEG*ha;
+  rlat = RAD_DEG*latitude;
+
+  sinh = - cos (rdec) * sin (rha);
+  cosh =   sin (rdec) * cos (rlat) - cos (rdec) * cos (rha) * sin (rlat);
+
+  az = DEG_RAD * atan2 (sinh, cosh);
+
+  return az;
+}
Index: /trunk/Ohana/src/fakeastro/src/args.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/args.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/args.c	(revision 37807)
@@ -0,0 +1,290 @@
+# include "fakeastro.h"
+void usage (void);
+void usage_client (void);
+
+int args (int *argc, char **argv) {
+
+  int N;
+
+  /* possible operations */
+  FAKEASTRO_OP = OP_NONE;
+
+  if ((N = get_argument (*argc, argv, "-images"))) {
+    remove_argument (N, argc, argv);
+    FAKEASTRO_OP = OP_IMAGES;
+  }
+
+  if ((N = get_argument (*argc, argv, "-galaxy"))) {
+    if (FAKEASTRO_OP != OP_NONE) usage();
+    remove_argument (N, argc, argv);
+    FAKEASTRO_OP = OP_GALAXY;
+  }
+
+  if ((N = get_argument (*argc, argv, "-2mass"))) {
+    remove_argument (N, argc, argv);
+    FAKEASTRO_OP = OP_2MASS;
+  }
+
+  TESTING = FALSE;
+  if ((N = get_argument (*argc, argv, "-testing"))) {
+    remove_argument (N, argc, argv);
+    TESTING = TRUE;
+  }
+  ONE_BIG_CHIP = FALSE;
+  if ((N = get_argument (*argc, argv, "-one-big-chip"))) {
+    remove_argument (N, argc, argv);
+    ONE_BIG_CHIP = TRUE;
+  }
+
+  if (FAKEASTRO_OP == OP_NONE) usage();
+
+  /* specify portion of the sky : allow default of all sky? */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax = 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+  if ((N = get_argument (*argc, argv, "-region"))) {
+    remove_argument (N, argc, argv);
+    UserPatch.Rmin = atof (argv[N]);
+    remove_argument (N, argc, argv);
+    UserPatch.Rmax = atof (argv[N]);
+    remove_argument (N, argc, argv);
+    UserPatch.Dmin = atof (argv[N]);
+    remove_argument (N, argc, argv);
+    UserPatch.Dmax = atof (argv[N]);
+    remove_argument (N, argc, argv);
+  } 
+  if ((N = get_argument (*argc, argv, "-catalog"))) {
+    remove_argument (N, argc, argv);
+    UserPatch.Rmin = atof (argv[N]);
+    UserPatch.Rmax = UserPatch.Rmin + 0.001;
+    remove_argument (N, argc, argv);
+    UserPatch.Dmin = atof (argv[N]);
+    UserPatch.Dmax = UserPatch.Dmin + 0.001;
+    remove_argument (N, argc, argv);
+  }
+
+  // XXX for the moment, make this selection manual.  it needs to be automatic 
+  // based on the state of the SkyTable
+  PARALLEL = FALSE;
+  if ((N = get_argument (*argc, argv, "-parallel"))) {
+    PARALLEL = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  // this is a test mode : rather than launching the remote jobs and waiting for completion,
+  // fakeastro will simply list the remote command and wait for the user to signal completion
+  PARALLEL_MANUAL = FALSE;
+  if ((N = get_argument (*argc, argv, "-parallel-manual"))) {
+    PARALLEL = TRUE; // -parallel-manual implies -parallel
+    PARALLEL_MANUAL = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  // this is a test mode : rather than launching the fakeastro_client jobs remotely, they are 
+  // run in serial via 'system'
+  PARALLEL_SERIAL = FALSE;
+  if ((N = get_argument (*argc, argv, "-parallel-serial"))) {
+    if (PARALLEL_MANUAL) {
+      fprintf (stderr, "ERROR: cannot mix -parallel-manual and -parallel-serial\n");
+      exit (1);
+    }
+    PARALLEL = TRUE; // -parallel-serial implies -parallel
+    PARALLEL_SERIAL = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  // MaxDensityUse = FALSE;
+  // if ((N = get_argument (*argc, argv, "-max-density"))) {
+  //   remove_argument (N, argc, argv);
+  //   MaxDensityValue = atof(argv[N]);
+  //   remove_argument (N, argc, argv);
+  //   MaxDensityUse = TRUE;
+  // }
+
+  UNIFORM_RADEC = FALSE;
+  if ((N = get_argument (*argc, argv, "-uniform-radec"))) {
+    UNIFORM_RADEC = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  FORCE = FALSE;
+  if ((N = get_argument (*argc, argv, "-force"))) {
+    FORCE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  VERBOSE = VERBOSE2 = FALSE;
+  if ((N = get_argument (*argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  if ((N = get_argument (*argc, argv, "-vv"))) {
+    VERBOSE = VERBOSE2 = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  if (FAKEASTRO_OP == OP_IMAGES) {
+    // mandatory arguments to fakeastro -images -input catdir -output -catdir -images images.fits
+    if ((N = get_argument (*argc, argv, "-input"))) {
+      remove_argument (N, argc, argv);
+      CATDIR_INPUT = strcreate (argv[N]);
+      remove_argument (N, argc, argv);
+    } else {
+      fprintf (stderr, "missing -input (catdir)\n");
+      exit (2);
+    }
+    if ((N = get_argument (*argc, argv, "-output"))) {
+      remove_argument (N, argc, argv);
+      CATDIR_OUTPUT = strcreate (argv[N]);
+      remove_argument (N, argc, argv);
+    } else {
+      fprintf (stderr, "missing -output (catdir)\n");
+      exit (2);
+    }
+    if ((N = get_argument (*argc, argv, "-input-images"))) {
+      remove_argument (N, argc, argv);
+      IMAGES_INPUT = strcreate (argv[N]);
+      remove_argument (N, argc, argv);
+    } else {
+      fprintf (stderr, "missing -images (images)\n");
+      exit (2);
+    }
+  }
+
+  return TRUE;
+}
+
+int args_client (int *argc, char **argv) {
+
+  int N;
+
+  /* possible operations */
+  FAKEASTRO_OP = OP_NONE;
+
+  // by definition, the client is not parallel 
+  PARALLEL = FALSE;
+  PARALLEL_MANUAL = FALSE;
+  PARALLEL_SERIAL = FALSE;
+
+  HOST_ID = 0;
+  if ((N = get_argument (*argc, argv, "-hostID"))) {
+    remove_argument (N, argc, argv);
+    HOST_ID = atoi (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!HOST_ID) usage_client();
+
+  HOSTDIR = NULL;
+  if ((N = get_argument (*argc, argv, "-hostdir"))) {
+    remove_argument (N, argc, argv);
+    HOSTDIR = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!HOSTDIR) usage_client();
+
+  CPT_FILE = NULL;
+  if ((N = get_argument (*argc, argv, "-cpt"))) {
+    remove_argument (N, argc, argv);
+    CPT_FILE = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!CPT_FILE) usage_client();
+  
+  INPUT = NULL;
+  if ((N = get_argument (*argc, argv, "-input"))) {
+    remove_argument (N, argc, argv);
+    INPUT = strcreate (argv[N]);
+    remove_argument (N, argc, argv);
+  }
+  if (!INPUT) usage_client();
+
+  if ((N = get_argument (*argc, argv, "-images"))) {
+    remove_argument (N, argc, argv);
+    FAKEASTRO_OP = OP_IMAGES;
+  }
+  if ((N = get_argument (*argc, argv, "-galaxy"))) {
+    if (FAKEASTRO_OP != OP_NONE) usage();
+    remove_argument (N, argc, argv);
+    FAKEASTRO_OP = OP_GALAXY;
+  }
+  if (FAKEASTRO_OP == OP_NONE) usage_client();
+
+  /* specify portion of the sky : allow default of all sky? */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax = 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+  if ((N = get_argument (*argc, argv, "-region"))) {
+    remove_argument (N, argc, argv);
+    UserPatch.Rmin = atof (argv[N]);
+    remove_argument (N, argc, argv);
+    UserPatch.Rmax = atof (argv[N]);
+    remove_argument (N, argc, argv);
+    UserPatch.Dmin = atof (argv[N]);
+    remove_argument (N, argc, argv);
+    UserPatch.Dmax = atof (argv[N]);
+    remove_argument (N, argc, argv);
+  } 
+  if ((N = get_argument (*argc, argv, "-catalog"))) {
+    remove_argument (N, argc, argv);
+    UserPatch.Rmin = atof (argv[N]);
+    UserPatch.Rmax = UserPatch.Rmin + 0.001;
+    remove_argument (N, argc, argv);
+    UserPatch.Dmin = atof (argv[N]);
+    UserPatch.Dmax = UserPatch.Dmin + 0.001;
+    remove_argument (N, argc, argv);
+  }
+
+  // MaxDensityUse = FALSE;
+  // if ((N = get_argument (*argc, argv, "-max-density"))) {
+  //   remove_argument (N, argc, argv);
+  //   MaxDensityValue = atof(argv[N]);
+  //   remove_argument (N, argc, argv);
+  //   MaxDensityUse = TRUE;
+  // }
+
+  VERBOSE = VERBOSE2 = FALSE;
+  if ((N = get_argument (*argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, argc, argv);
+  }
+  if ((N = get_argument (*argc, argv, "-vv"))) {
+    VERBOSE = VERBOSE2 = TRUE;
+    remove_argument (N, argc, argv);
+  }
+
+  return TRUE;
+}
+
+void usage () {
+  fprintf (stderr, "ERROR: USAGE: fakeastro -images [options]\n");
+  fprintf (stderr, "       OR:    fakeastro -galaxy [options]\n");
+
+  fprintf (stderr, "  specify one of the following modes: \n");
+  fprintf (stderr, "  -images\n");
+  fprintf (stderr, "  -galaxy\n");
+
+  fprintf (stderr, " additional options: \n");
+  fprintf (stderr, "  -region RA RA DEC DEC\n");
+  fprintf (stderr, "  -catalog (ra) (dec)\n\n");
+  fprintf (stderr, "  -v\n");
+  fprintf (stderr, "  -vv\n");
+  fprintf (stderr, "  \n");
+  exit (2);
+} 
+
+void usage_client () {
+  fprintf (stderr, "ERROR: USAGE: fakeastro_client -images\n");
+  fprintf (stderr, "       OR:    fakeastro_client -galaxy\n");
+
+  fprintf (stderr, "  specify one of the following modes: \n");
+  fprintf (stderr, "  -images\n");
+  fprintf (stderr, "  -galaxy\n");
+
+  fprintf (stderr, " additional options: \n");
+  fprintf (stderr, "  -region Rmin Rmax Dmin Dmax");
+  fprintf (stderr, "  -catalog RA DEC");
+  fprintf (stderr, "  -v\n");
+  fprintf (stderr, "  -vv\n");
+  fprintf (stderr, "  \n");
+  exit (2);
+} 
Index: /trunk/Ohana/src/fakeastro/src/fakeastro.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakeastro.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakeastro.c	(revision 37807)
@@ -0,0 +1,57 @@
+# include "fakeastro.h"
+
+int main (int argc, char **argv) {
+
+  gauss_init (50000);
+
+  /* get configuration info, args */
+  initialize (argc, argv);
+
+  switch (FAKEASTRO_OP) {
+    case OP_GALAXY:
+      /* the object analysis is a separate process iterating over catalogs */
+      fakeastro_galaxy ();  // generate fake stars following a galaxy model
+      /* make_fakestars() -- generates FakeAstro_Stars with density,distance and uR,uD following galaxy model
+	 make_fakeqsos()
+	 sortStars()
+	 make_subset()
+	 save_fakestars()
+	   fakestar_catalog()
+	     insert_fakestar() -- converts suppied FakeAstro_Stars and populates average/secfilt/starpar structures
+	   (fakestar_save_stars) [fakeastro_client / fakestar_load_stars]
+       */
+      exit (0);
+
+    case OP_IMAGES:
+      fakeastro_images ();
+      /* fakeastro_images_region()
+	   load_fake_stars()
+	   make_fake_images()
+	   make_fake_stars()
+	   fit_fake_stars()
+	   save_fake_stars()
+	     match_fake_stars()
+       */
+      exit (0);
+
+    case OP_2MASS:
+      fakeastro_2mass ();
+      /* make_2mass_measures()
+       */
+      exit (0);
+
+    default:
+      fprintf (stderr, "impossible!\n");
+      abort();
+  }
+  exit (1);
+}
+
+/* fakeastro can be used to generate a DVO database of fake observations.  
+
+   top-level modes:
+
+   -galaxy : generate a galaxy model. output is a DVO database with average.d and starpar.d tables populated
+   -images : generate a set of observations (or just images?)
+
+*/
Index: /trunk/Ohana/src/fakeastro/src/fakeastro_2mass.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakeastro_2mass.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakeastro_2mass.c	(revision 37807)
@@ -0,0 +1,56 @@
+# include "fakeastro.h"
+
+int fakeastro_2mass () {
+
+  INITTIME;
+
+  SkyTable *skyTable = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (skyTable, CATDIR, "cpt");
+
+  SkyList *skylist  = SkyListByPatch (skyTable, -1, &UserPatch);
+
+  Catalog catalog;
+
+  // load stars from database in these regions
+  int i;
+  for (i = 0; i < skylist->Nregions; i++) {
+    dvo_catalog_init (&catalog, TRUE);
+
+    // set the parameters which guide catalog open/load/create
+    catalog.filename  = skylist[0].filename[i];
+    catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+    catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+    catalog.catflags  = LOAD_AVES | LOAD_SECF | LOAD_MEAS | LOAD_STARPAR;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+    if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE, "w")) {
+      fprintf (stderr, "ERROR: failure reading catalog %s\n", catalog.filename);
+      exit (1);
+    }
+    if (!catalog.Naves_disk) {
+	if (VERBOSE2) { fprintf (stderr, "no data in %s, skipping\n", catalog.filename); }
+	dvo_catalog_unlock (&catalog);
+	dvo_catalog_free (&catalog);
+	continue;
+    }
+
+    make_2mass_measures (&catalog);
+
+    SetProtect (TRUE);
+    if (!dvo_catalog_update (&catalog, VERBOSE)) {
+      fprintf (stderr, "ERROR: failure to update %s\n", catalog.filename);
+      exit (3);
+    }
+    SetProtect (FALSE);
+
+    if (!dvo_catalog_unlock (&catalog)) {
+      fprintf (stderr, "ERROR: failure to unlock %s\n", catalog.filename);
+      exit (2);
+    }
+    dvo_catalog_free (&catalog);
+    if (VERBOSE) MARKTIME ("save cpt: %f sec\n", dtime); RESETTIME; 
+  }
+
+  exit (0);
+}
+
+
Index: /trunk/Ohana/src/fakeastro/src/fakeastro_client.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakeastro_client.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakeastro_client.c	(revision 37807)
@@ -0,0 +1,22 @@
+# include "fakeastro.h"
+
+int main (int argc, char **argv) {
+
+  initialize_client (argc, argv);
+
+  // client is called with a pointer to the file to be loaded
+
+  SkyTable *sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, FALSE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  
+  SkyList *skylist = SkyRegionByCPT (sky, CPT_FILE);
+
+  int Nstars;
+  FakeAstro_Stars *stars = fakestar_load_stars (INPUT, &Nstars);
+
+  fakestar_catalog (stars, Nstars, skylist->regions[0], CPT_FILE);
+
+  free (stars);
+
+  exit (0);
+}
Index: /trunk/Ohana/src/fakeastro/src/fakeastro_galaxy.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakeastro_galaxy.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakeastro_galaxy.c	(revision 37807)
@@ -0,0 +1,104 @@
+# include "fakeastro.h"
+
+int fakeastro_galaxy () {
+
+  int n, i;
+
+  SkyTable *sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  SkyList *skylistInput = SkyListByPatch (sky, -1, &UserPatch);
+
+  // load the list of hosts
+  HostTable *hosts = NULL;
+  if (PARALLEL) {
+    hosts = HostTableLoad (CATDIR, sky->hosts);
+    if (!hosts) {
+      fprintf (stderr, "ERROR: failure reading Host Table %s for database %s\n", sky->hosts, CATDIR);
+      exit (1);
+    }    
+
+    // ensure that the paths are absolute path names
+    for (i = 0; i < hosts->Nhosts; i++) {
+      char *tmppath = abspath (hosts->hosts[i].pathname, DVO_MAX_PATH);
+      free (hosts->hosts[i].pathname);
+      hosts->hosts[i].pathname = tmppath;
+    }
+
+    // set up the array of active hosts
+    init_remote_hosts ();
+  }
+
+  for (n = 0; n < FAKEASTRO_NLOOP; n++) {
+
+    INITTIME;
+
+    int Nstars = FAKEASTRO_NSTARS;
+    FakeAstro_Stars *stars = make_fakestars (Nstars);
+
+    // only generate the QSOs 1x:
+    if (n == 0) {
+      FakeAstro_Stars *qso_icrf = make_fakeqsos (FAKEASTRO_NQSO_ICRF, TRUE); // quasars with known positions
+      REALLOCATE (stars, FakeAstro_Stars, Nstars + FAKEASTRO_NQSO_ICRF);
+      for (i = 0; i < FAKEASTRO_NQSO_ICRF; i++) {
+	stars[Nstars+i] = qso_icrf[i];
+      }
+      Nstars += FAKEASTRO_NQSO_ICRF;
+      FakeAstro_Stars *qso_zero = make_fakeqsos (FAKEASTRO_NQSO_ZERO, FALSE); // quasars with unknown positions, but zero velocity
+      REALLOCATE (stars, FakeAstro_Stars, Nstars + FAKEASTRO_NQSO_ZERO);
+      for (i = 0; i < FAKEASTRO_NQSO_ZERO; i++) {
+	stars[Nstars+i] = qso_zero[i];
+      }
+      Nstars += FAKEASTRO_NQSO_ZERO;
+    }
+
+    MARKTIME ("generate %d fake stars in %f sec\n", Nstars, dtime);
+
+    // sort the stars by RA
+    sortStars (stars, Nstars);
+
+    MARKTIME ("sorted %d fake stars: %f sec\n", Nstars, dtime);
+
+    // scan through the stars, loading the containing catalogs
+    // skip through table for unsaved stars
+    for (i = 0; i < Nstars; i++) {
+      if (stars[i].flag) continue;
+
+      // identify the relevant catalog
+      SkyList *skylist = SkyRegionByPoint_List (skylistInput, -1, stars[i].R, stars[i].D);
+      if (skylist[0].Nregions == 0) {
+	SkyListFree (skylist);
+	continue;
+      }
+      SkyRegion *region = skylist[0].regions[0];
+
+      // select stars matching this region
+      int Nsubset;
+      FakeAstro_Stars *subset = make_subset (stars, Nstars, i, region, &Nsubset);
+      // MARKTIME ("subset %d fake stars: %f sec\n", Nsubset, dtime);
+
+      // In parallel mode, write out the subset to a disk file.  Block until a remote host
+      // is available.  In serial mode, just match against the appropriate region and save
+      save_fakestars (subset, Nsubset, hosts, region, skylist[0].filename[0]);
+      // MARKTIME ("saved %d fake stars: %f sec\n", Nsubset, dtime);
+
+      free (subset);
+      SkyListFree (skylist);
+    }
+    free (stars);
+
+    MARKTIME ("saved %d fake stars: %f sec\n", Nstars, dtime);
+
+    // wait for last remote clients to finish
+    harvest_all ();
+  }
+
+  return TRUE;
+}
+
+  /* outline
+
+   * generate N stars (random draw from galaxy mode)
+   * assign them to catalogs and distribute
+
+   */
+
Index: /trunk/Ohana/src/fakeastro/src/fakeastro_images.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakeastro_images.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakeastro_images.c	(revision 37807)
@@ -0,0 +1,94 @@
+# include "fakeastro.h"
+int open_image_db (FITS_DB *db, char *ImageName);
+int save_image_db (FITS_DB *db, Image *image, int Nimage);
+
+int fakeastro_images () {
+
+  INITTIME;
+
+  FITS_DB dbfake, dbtrue;
+
+  open_image_db (&dbfake, "Images.dat");
+  open_image_db (&dbtrue, "Images.true.dat");
+
+  SkyTable *skyTableInput = SkyTableLoadOptimal (CATDIR_INPUT, SKY_TABLE, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (skyTableInput, CATDIR_INPUT, "cpt");
+
+  SkyTable *skyTableOutput = SkyTableLoadOptimal (CATDIR_OUTPUT, SKY_TABLE, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  SkyTableSetFilenames (skyTableOutput, CATDIR_OUTPUT, "cpt");
+
+  IMAGE_ID = 1;
+
+  int NrefImage;
+  Image *refImage = load_template_images (&NrefImage); // subset of the DIS exposures
+  
+  ImageInfo imageInfo;
+
+  imageInfo.NfakeImage = 0;
+  imageInfo.NFAKEIMAGE = 1000;
+  imageInfo.fakeImage = NULL;
+  ALLOCATE (imageInfo.fakeImage, Image, imageInfo.NFAKEIMAGE);
+
+  imageInfo.NtrueImage = 0;
+  imageInfo.NTRUEIMAGE = 1000;
+  imageInfo.trueImage = NULL;
+  ALLOCATE (imageInfo.trueImage, Image, imageInfo.NTRUEIMAGE);
+
+  fakeastro_images_region (&imageInfo, refImage, NrefImage, skyTableInput, skyTableOutput, &UserPatch);
+
+  save_image_db (&dbfake, imageInfo.fakeImage, imageInfo.NfakeImage);
+  save_image_db (&dbtrue, imageInfo.trueImage, imageInfo.NtrueImage);
+
+  MARKTIME ("generate fake stars in %f sec\n", dtime)
+
+  exit (0);
+}
+
+/* outline:
+
+ * load reference image table
+ * generate a set of fake chips for each real exposure boresite
+ * select the real stars which land on the fake chips
+ * generate a set of measurements for the fake chip
+ * need to ingest the measurements into the new DVO as if they came from addstar
+
+ * I can probably load stars for a decent fraction of the sky at once
+ */
+
+
+// -input catdir -output catdir -images images.fits  
+
+int open_image_db (FITS_DB *db, char *ImageName) {
+
+  char ImageCat[DVO_MAX_PATH];
+
+  // we are creating a new image table (what about db ID?)
+  snprintf (ImageCat, DVO_MAX_PATH, "%s/%s", CATDIR_OUTPUT, ImageName);
+
+  /* setup image table format and lock */
+  db->mode    = dvo_catalog_catmode (CATMODE);
+  db->format  = dvo_catalog_catformat (CATFORMAT);
+  int status = dvo_image_lock (db, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", db->filename);
+
+  /* load or create the image table */
+  if (db->dbstate != LCK_EMPTY) {
+    Shutdown ("image table %s exists, exiting", db->filename);
+  }
+  dvo_image_create (db, GetZeroPoint());
+
+  return TRUE;
+}
+
+int save_image_db (FITS_DB *db, Image *image, int Nimage) {
+
+  /* add the new image and save */
+  dvo_image_addrows (db, image, Nimage);
+  SetProtect (TRUE);
+  dvo_image_update (db, VERBOSE);
+  SetProtect (FALSE);
+  dvo_image_unlock (db); /* unlock? */
+
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/fakeastro/src/fakeastro_images_region.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakeastro_images_region.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakeastro_images_region.c	(revision 37807)
@@ -0,0 +1,97 @@
+# include "fakeastro.h"
+
+int fakeastro_images_region (ImageInfo *imageInfo, Image *refImage, int NrefImage, SkyTable *skyTableInput, SkyTable *skyTableOutput, SkyRegion *innerRegion) {
+
+  // extend the outer region by 2.0 degrees
+  SkyRegion *outerRegion = SkyRegionExpand (innerRegion, 2.0);
+
+  SkyList *skyListInput  = SkyListByPatch (skyTableInput, -1, outerRegion);
+
+  // load all catalogs for this extended region (catalog and skyListInput are 1-to-1 in regions)
+  int Ncatalog;
+  Catalog *catalog = load_fake_stars (skyListInput, &Ncatalog);
+
+  int i, j;
+  for (i = 0; i < NrefImage; i++) {
+
+    // we only want to make fake images for the exposures.  NOTE : we should define
+    // template to only include DIS, but this is for safety.
+    if (strcmp(&refImage[i].coords.ctype[4], "-DIS")) continue;
+
+    // choose only images in this (inner) region
+    double Rc, Dc;
+    XY_to_RD (&Rc, &Dc, 0.0, 0.0, &refImage[i].coords);
+    Rc = ohana_normalize_angle (Rc);
+
+    // exposure center must be in innerRegion:
+    if (!SkyRegionHasPoint(innerRegion, Rc, Dc)) continue;
+
+    int NfakeImage;
+    Image *fakeImage = make_fake_images (&refImage[i], &NfakeImage);
+    
+    // save the new fake images with their true image parameters (not yet fitted to the data)
+    if (imageInfo->NtrueImage + NfakeImage >= imageInfo->NTRUEIMAGE) {
+      imageInfo->NTRUEIMAGE += 1000 + NfakeImage;
+      REALLOCATE (imageInfo->trueImage, Image, imageInfo->NTRUEIMAGE);
+    }
+    for (j = 0; j < NfakeImage; j++) {
+      memcpy (&imageInfo->trueImage[imageInfo->NtrueImage], &fakeImage[j], sizeof(Image));
+      imageInfo->NtrueImage ++;
+    }
+
+    int NfakeStars = 0;
+    Stars *fakeStars = NULL;
+
+    for (j = 0; j < NfakeImage; j++) {
+
+      // we only want to make fake stars for the fake chips (not the PHU entries)
+      if (strcmp(&fakeImage[j].coords.ctype[4], "-WRP")) continue;
+
+      int Nstart = NfakeStars;
+      fakeStars = make_fake_stars (catalog, Ncatalog, skyListInput, &fakeImage[j], fakeStars, &NfakeStars);
+      
+      // only fit the new stars to this image
+      fit_fake_stars (&fakeStars[Nstart], NfakeStars - Nstart, &fakeImage[j]);
+
+      fprintf (stderr, "%s : %d\n", fakeImage[j].name, NfakeStars - Nstart);
+    }
+    // send in just the PHU image for reference
+    save_fake_stars (skyTableOutput, &fakeImage[0], fakeStars, NfakeStars);
+    free (fakeStars);
+
+    // append the new fake images to the end of the full set:
+    if (imageInfo->NfakeImage + NfakeImage >= imageInfo->NFAKEIMAGE) {
+      imageInfo->NFAKEIMAGE += 1000 + NfakeImage;
+      REALLOCATE (imageInfo->fakeImage, Image, imageInfo->NFAKEIMAGE);
+    }
+    for (j = 0; j < NfakeImage; j++) {
+      memcpy (&imageInfo->fakeImage[imageInfo->NfakeImage], &fakeImage[j], sizeof(Image));
+      imageInfo->NfakeImage ++;
+    }
+    
+    free (fakeImage);
+  }
+
+  SkyListFree (skyListInput);
+
+  for (i = 0; i < Ncatalog; i++) {
+    dvo_catalog_free (&catalog[i]);
+  }
+
+  return TRUE;
+}
+
+/* 
+   
+   given a modest sized region (Rmin,Rmax,Dmin,Dmax):
+   define an exterior region 
+
+   * define an outer region with borders 2 linear deg wider 
+
+   * load all input catalogs for the outer region
+
+   * select all images with exposure centers in the inner region
+
+   * generate fake stars for the selected images
+
+   */
Index: /trunk/Ohana/src/fakeastro/src/fakestar_catalog.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakestar_catalog.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakestar_catalog.c	(revision 37807)
@@ -0,0 +1,27 @@
+# include "fakeastro.h"
+
+int fakestar_catalog (FakeAstro_Stars *stars, int Nstars, SkyRegion *region, char *filename) {
+
+  Catalog catalog;
+
+  // now we have all of the loaded stars in this catalog
+  catalog.filename = filename;
+  catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+  catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+  catalog.catflags = LOAD_AVES | LOAD_SECF | LOAD_STARPAR;
+  catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+    
+  // an error exit status here is a significant error
+  if (!dvo_catalog_open (&catalog, region, VERBOSE, "w")) {
+    fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+    exit (2);
+  }
+
+  insert_fakestar (region, stars, Nstars, &catalog);
+    
+  dvo_catalog_save (&catalog, VERBOSE);
+  dvo_catalog_unlock (&catalog);
+  dvo_catalog_free (&catalog);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/fakeastro/src/fakestar_io.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fakestar_io.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fakestar_io.c	(revision 37807)
@@ -0,0 +1,190 @@
+# include "fakeastro.h"
+
+int fakestar_save_stars (char *filename, FakeAstro_Stars *stars, int Nstars) {
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    myAbortF ("ERROR: cannot open file for output %s\n", filename);
+  }
+
+  gfits_fwrite_header  (f, &header);
+  gfits_fwrite_matrix  (f, &matrix);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  gfits_create_table_header (&theader, "BINTABLE", "STARPAR");
+
+  gfits_define_bintable_column (&theader, "D", "RA",       "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "DEC",      "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "GLON",     "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "GLAT",     "", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "Ebv",      "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dEbv",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "DistMag",  "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dDistMag", "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "M_r",      "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dM_r",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "FeH",      "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E", "dFeH",     "", "", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "averef",   "", "", 1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "objID",    "", "", 1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "catID",    "", "", 1.0, FT_BZERO_INT32);
+
+  int i;
+  double       *ra       = NULL; ALLOCATE (ra      , double	 , Nstars); for (i = 0; i < Nstars; i++) ra      [i] = stars[i].R               ;
+  double       *dec      = NULL; ALLOCATE (dec     , double	 , Nstars); for (i = 0; i < Nstars; i++) dec     [i] = stars[i].D               ;
+  float        *glon     = NULL; ALLOCATE (glon    , float 	 , Nstars); for (i = 0; i < Nstars; i++) glon    [i] = stars[i].starpar.galLon  ;
+  float        *glat     = NULL; ALLOCATE (glat    , float 	 , Nstars); for (i = 0; i < Nstars; i++) glat    [i] = stars[i].starpar.galLat  ;
+  float        *Ebv      = NULL; ALLOCATE (Ebv     , float	 , Nstars); for (i = 0; i < Nstars; i++) Ebv     [i] = stars[i].starpar.Ebv     ;
+  float        *dEbv     = NULL; ALLOCATE (dEbv    , float	 , Nstars); for (i = 0; i < Nstars; i++) dEbv    [i] = stars[i].starpar.dEbv    ;
+  float        *DistMag  = NULL; ALLOCATE (DistMag , float	 , Nstars); for (i = 0; i < Nstars; i++) DistMag [i] = stars[i].starpar.DistMag ;
+  float        *dDistMag = NULL; ALLOCATE (dDistMag, float	 , Nstars); for (i = 0; i < Nstars; i++) dDistMag[i] = stars[i].starpar.dDistMag;
+  float        *M_r      = NULL; ALLOCATE (M_r     , float	 , Nstars); for (i = 0; i < Nstars; i++) M_r     [i] = stars[i].starpar.M_r     ;
+  float        *dM_r     = NULL; ALLOCATE (dM_r    , float	 , Nstars); for (i = 0; i < Nstars; i++) dM_r    [i] = stars[i].starpar.dM_r    ;
+  float        *FeH      = NULL; ALLOCATE (FeH     , float	 , Nstars); for (i = 0; i < Nstars; i++) FeH     [i] = stars[i].starpar.FeH     ;
+  float        *dFeH     = NULL; ALLOCATE (dFeH    , float	 , Nstars); for (i = 0; i < Nstars; i++) dFeH    [i] = stars[i].starpar.dFeH    ;
+  unsigned int *averef   = NULL; ALLOCATE (averef  , unsigned int, Nstars); for (i = 0; i < Nstars; i++) averef  [i] = stars[i].starpar.averef  ;
+  unsigned int *objID    = NULL; ALLOCATE (objID   , unsigned int, Nstars); for (i = 0; i < Nstars; i++) objID   [i] = stars[i].starpar.objID   ;
+  unsigned int *catID    = NULL; ALLOCATE (catID   , unsigned int, Nstars); for (i = 0; i < Nstars; i++) catID   [i] = stars[i].starpar.catID   ;
+
+  // generate the output array that carries the data
+  gfits_create_table (&theader, &ftable);
+
+  // add the columns to the output array
+  gfits_set_bintable_column (&theader, &ftable, "RA",       ra      , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "DEC",      dec     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "GLON",     glon    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "GLAT",     glat    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "Ebv",      Ebv     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dEbv",     dEbv    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "DistMag",  DistMag , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dDistMag", dDistMag, Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "M_r",      M_r     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dM_r",     dM_r    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "FeH",      FeH     , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "dFeH",     dFeH    , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "averef",   averef  , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "objID",    objID   , Nstars);
+  gfits_set_bintable_column (&theader, &ftable, "catID",    catID   , Nstars);
+    
+  gfits_fwrite_Theader (f, &theader);
+  gfits_fwrite_table (f, &ftable);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  return TRUE;
+}
+
+
+# define GET_COLUMN(OUT,NAME,TYPE)					\
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+FakeAstro_Stars *fakestar_load_stars (char *filename, int *nstars) {
+
+  int i, Ncol;
+  off_t Nrow;
+  char type[16];
+
+  FakeAstro_Stars *stars = NULL;
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  header.buffer = NULL;
+  matrix.buffer = NULL;
+  ftable.buffer = NULL;
+  theader.buffer = NULL;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read header\n");
+    goto escape;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read matrix\n");
+    goto escape;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) goto escape;
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) goto escape;
+ 
+  GET_COLUMN(ra      , "RA"        ,   double);
+  GET_COLUMN(dec     , "DEC"       ,   double);
+  GET_COLUMN(glon    , "GLON"      ,   float);
+  GET_COLUMN(glat    , "GLAT"      ,   float);
+  GET_COLUMN(Ebv     , "Ebv"       ,   float);
+  GET_COLUMN(dEbv    , "dEbv"      ,   float);
+  GET_COLUMN(DistMag , "DistMag"   ,   float);
+  GET_COLUMN(dDistMag, "dDistMag"  ,   float);
+  GET_COLUMN(M_r     , "M_r"       ,   float);
+  GET_COLUMN(dM_r    , "dM_r"      ,   float);
+  GET_COLUMN(FeH     , "FeH"       ,   float);
+  GET_COLUMN(dFeH    , "dFeH"      ,   float);
+  GET_COLUMN(averef  , "averef"    ,   unsigned int);
+  GET_COLUMN(objID   , "objID"     ,   unsigned int);
+  GET_COLUMN(catID   , "catID"     ,   unsigned int);
+
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  ALLOCATE (stars, FakeAstro_Stars, Nrow);
+
+  for (i = 0; i < Nrow; i++) {
+    stars[i].R                = ra      [i];
+    stars[i].D                = dec     [i];
+    stars[i].starpar.R        = ra      [i];
+    stars[i].starpar.D        = dec     [i];
+    stars[i].starpar.galLon   = glon    [i];
+    stars[i].starpar.galLat   = glat    [i];
+    stars[i].starpar.Ebv      = Ebv     [i];
+    stars[i].starpar.dEbv     = dEbv    [i];
+    stars[i].starpar.DistMag  = DistMag [i];
+    stars[i].starpar.dDistMag = dDistMag[i];
+    stars[i].starpar.M_r      = M_r     [i];
+    stars[i].starpar.dM_r     = dM_r    [i];
+    stars[i].starpar.FeH      = FeH     [i];
+    stars[i].starpar.dFeH     = dFeH    [i];
+    stars[i].starpar.averef   = averef  [i];
+    stars[i].starpar.objID    = objID   [i];
+    stars[i].starpar.catID    = catID   [i];
+  }
+
+  fclose (f);
+  *nstars = Nrow;
+  return stars;
+
+escape:
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  fclose (f);
+  return NULL;
+}
Index: /trunk/Ohana/src/fakeastro/src/fit_fake_stars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fit_fake_stars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fit_fake_stars.c	(revision 37807)
@@ -0,0 +1,156 @@
+# include "fakeastro.h"
+
+int dump_fit_stars (Stars *stars, double *L, double *M, int Nstars, char *filename) {
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    fprintf (stderr, "failed to open %s\n", filename);
+    return FALSE;
+  }
+
+  int i;
+  for (i = 0; i < Nstars; i++) {
+    fprintf (f, "%d  %f %f : %f %f\n", i, stars[i].measure.Xccd, stars[i].measure.Yccd, L[i], M[i]);
+  }
+  fclose (f);
+  return TRUE;
+}
+
+int fit_fake_stars (Stars *stars, int Nstars, Image *image) {
+
+  // if we are doing imagemap or high-order polynomial fits, this is the order we should
+  // use
+  int order_use = 0;
+  if (Nstars >   5) order_use = 1; //  5 stars per cell
+  if (Nstars >  24) order_use = 2; //  6 stars per cell
+  if (Nstars >  63) order_use = 3; //  7 stars per cell
+  // if (Nstars > 128) order_use = 4; //  8 stars per cell
+  // if (Nstars > 225) order_use = 5; //  9 stars per cell
+  // if (Nstars > 360) order_use = 6; // 10 stars per cell
+  if (order_use < 1) {
+    fprintf (stderr, "insufficient measurements (%d) for linear fit\n", Nstars);
+    image[0].flags |= ID_IMAGE_ASTROM_FEW;
+    return FALSE;
+  }
+  order_use = 1; // hard-wire linear solutions (nothing higher is needed for now)
+
+  // I have Nstars with "reference" positions stars[].Rref,Dref and 
+  // "observed" chip coordinates stars[].measure.Xccd,Yccd.
+
+  // fit Xccd,Yccd to Rref,Dref
+
+  int CHIPMAP = (image[0].coords.Npolyterms == -1);
+
+  // XX1 image[0].coords.Npolyterms = CHIPMAP ? 1 : order_use;
+  CoordFit *fit = CHIPMAP ? fit_init (1) : fit_init (order_use);
+
+  double *L, *M;
+  ALLOCATE (L, double, Nstars);
+  ALLOCATE (M, double, Nstars);
+
+  // static int Ntest = 0;
+  // char name[64];
+  // snprintf (name, 64, "test.%03d.dat", Ntest); Ntest ++;
+  // FILE *f = fopen (name, "w");
+
+  // generate the fit matches
+  int i;
+  for (i = 0; i < Nstars; i++) {
+    RD_to_XY (&L[i], &M[i], stars[i].Rref, stars[i].Dref, image[0].coords.mosaic);
+    fit_add (fit, stars[i].measure.Xccd, stars[i].measure.Yccd, L[i], M[i], 0.05);
+
+    // double Lo, Mo, Xo, Yo;
+    // RD_to_XY (&Lo, &Mo, stars[i].average.R, stars[i].average.D, image[0].coords.mosaic);
+    // RD_to_XY (&Xo, &Yo, stars[i].average.R, stars[i].average.D, &image[0].coords);
+    // fprintf (f, "%d : %f %f : %f %f : %f %f : %f %f : %f %f : %f %f\n", i, stars[i].Rref, stars[i].Dref, stars[i].average.R, stars[i].average.D, stars[i].measure.Xccd, stars[i].measure.Yccd, L[i], M[i], Lo, Mo, Xo, Yo);
+  }
+  // fclose (f);
+
+  // measure the fit, update the coords & object coordinates
+  // XXX do something more drastic on failure?
+  if (!fit_eval (fit)) {
+    fprintf (stderr, "failed to fit new model\n");
+    image[0].flags |= ID_IMAGE_ASTROM_FAIL;
+    if (fit) fit_free (fit);
+    free (L);
+    free (M);
+    return FALSE;
+  }
+
+  image[0].coords.Npolyterms = CHIPMAP ? 1 : order_use;
+  if (!fit_apply_coords (fit, &image[0].coords, FALSE)) {
+    fprintf (stderr, "failed to fit new model\n");
+    image[0].flags |= ID_IMAGE_ASTROM_FAIL;
+    if (fit) fit_free (fit);
+    free (L);
+    free (M);
+    return FALSE;
+  }
+
+  // measure chip corners and find dX,dY
+  {
+    double Lo, Mo;
+    XY_to_LM (&Lo, &Mo, 0.0, 0.0, &image[0].coords);
+    
+    double Ls, Ms;
+    XY_to_LM (&Ls, &Ms, 4850.0, 0.0, &image[0].coords);
+
+    double Le, Me;
+    XY_to_LM (&Le, &Me, 0.0, 4850.0, &image[0].coords);
+
+    if (FALSE) fprintf (stderr, "%s : (dL,dM)_1 : %f : (dL,dM)_2 : %f\n", image->name, hypot(Le-Lo, Me-Mo), hypot(Ls-Lo, Ms-Mo));
+  }
+
+  if (CHIPMAP) {
+
+    // apply fit to get the fitted X,Y coordinates.  we need these to fit the residual map below
+    float *dX, *dY, *Xref, *Yref;
+    ALLOCATE (dX,   float, Nstars);
+    ALLOCATE (dY,   float, Nstars);
+    ALLOCATE (Xref, float, Nstars);
+    ALLOCATE (Yref, float, Nstars);
+
+    for (i = 0; i < Nstars; i++) {
+      // we have not yet fitted the map, so Npolyterms needs to be 1 here:
+      double tmpX, tmpY;
+      LM_to_XY (&tmpX, &tmpY, L[i], M[i], &image[0].coords);
+
+      // fit the linear terms as above
+      // calculate dX (raw.X - ref.X) and dY 
+      Xref[i] = tmpX;
+      Yref[i] = tmpY;
+      dX[i] = Xref[i] - stars[i].measure.Xccd;
+      dY[i] = Yref[i] - stars[i].measure.Yccd;
+    }
+
+    // in coordsops.c:XY_to_LM, the map is defined to carry dX,dY so that:
+    // (L,M) = f(X',Y') : (X',Y') = (X,Y) + (dX,dY)
+
+    if (image[0].coords.offsetMap == NULL) {
+      // allocate a new table and assign to this image
+      // need to allocate table?
+      AstromOffsetTable *table = get_astrom_table ();
+      AstromOffsetTableNewMap(table, order_use, order_use, image);
+    }
+    AstromOffsetMapFit (image[0].coords.offsetMap, Xref, Yref, dX, Nstars, TRUE);
+    AstromOffsetMapFit (image[0].coords.offsetMap, Xref, Yref, dY, Nstars, FALSE);
+
+    image[0].coords.Npolyterms = -1;
+
+    free (dX);
+    free (dY);
+    free (Xref);
+    free (Yref);
+  }
+
+  for (i = 0; i < Nstars; i++) {
+    // if we have fitted the map above, Npolyterms needs to be -1 here:
+    XY_to_RD (&stars[i].average.R, &stars[i].average.D, stars[i].measure.Xccd, stars[i].measure.Yccd, &image[0].coords);
+    stars[i].average.R = ohana_normalize_angle (stars[i].average.R);
+  }
+
+  free (L);
+  free (M);
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/fakeastro/src/fitpoly.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/fitpoly.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/fitpoly.c	(revision 37807)
@@ -0,0 +1,362 @@
+# include "fakeastro.h"
+
+/* these functions support simultaneous 2D fits to
+   x2 = \sum a_i,j x1^i y1^j 
+   y2 = \sum b_i,j x1^i y1^j 
+
+   the order of the fit (largest coefficient) is fixed to a single
+   value for both x1,y1 terms and for x2,y2 fits
+
+   the code is currently confusing because we limit to i+j <= order.
+   this could be cleaner if we used masks and allowed i <= order, j <= order
+*/
+
+double **array_init (int Nx, int Ny) {
+
+  int i;
+  double **array;
+
+  ALLOCATE (array, double *, Nx);
+  for (i = 0; i < Nx; i++) {
+    ALLOCATE (array[i], double, Ny);
+    memset (array[i], 0, Ny*sizeof(double));
+  }    
+  return (array);
+}
+
+void array_free (double **array, int Nx) {
+
+  int i;
+
+  for (i = 0; i < Nx; i++) {
+    free (array[i]);
+  }    
+  free (array);
+}
+
+// XXX define a fit structure and drop the file static variables?
+CoordFit *fit_init (int order) {
+
+  CoordFit *fit;
+
+  ALLOCATE (fit, CoordFit, 1);
+
+  fit[0].Npts   = 0;
+  fit[0].Norder = order;
+  fit[0].Nterms = order + 1;
+  fit[0].Nsums  = 2*order + 1;
+  fit[0].Nelems = SQ(order + 1);
+
+  /* summing arrays for fit solution */
+
+  // xsum[i][j] holds \sum (x2 wt x1^i y1^j)
+  // ysum[i][j] holds \sum (y2 wt x1^i y1^j)
+  fit[0].xsum = array_init (fit[0].Nterms, fit[0].Nterms);
+  fit[0].ysum = array_init (fit[0].Nterms, fit[0].Nterms);
+
+  // xfit[i][j] holds x2 coeff for x1^i y1^j
+  // yfit[i][j] holds y2 coeff for x1^i y1^j
+  fit[0].xfit = array_init (fit[0].Nterms, fit[0].Nterms);
+  fit[0].yfit = array_init (fit[0].Nterms, fit[0].Nterms);
+
+  // sum[i][j] holds \sum (wt x1^i y1^j)
+  fit[0].sum = array_init (fit[0].Nsums, fit[0].Nsums);
+
+  return (fit);
+}
+
+void fit_free (CoordFit *fit) {
+
+  array_free (fit[0].xfit, fit[0].Nterms);
+  array_free (fit[0].yfit, fit[0].Nterms);
+
+  array_free (fit[0].sum, fit[0].Nsums);
+  array_free (fit[0].xsum, fit[0].Nterms);
+  array_free (fit[0].ysum, fit[0].Nterms);
+  
+  free (fit);
+}
+  
+// XXX use implicit masks as below or explicit masks (with function to set?)
+// XXX eg, add a global mask to this file and 
+void fit_add (CoordFit *fit, double x1, double y1, double x2, double y2, double wt) {
+
+  int ix, iy;
+  double xterm, yterm, term;
+
+  xterm = 1;
+  for (ix = 0; ix < fit[0].Nsums; ix++) {
+    yterm = 1;
+    for (iy = 0; iy < fit[0].Nsums; iy++) {
+      term = xterm*yterm*wt;
+      fit[0].sum[ix][iy] += term;
+      if ((iy < fit[0].Nterms) && (ix < fit[0].Nterms)) {
+	fit[0].xsum[ix][iy] += x2*term;
+	fit[0].ysum[ix][iy] += y2*term;
+      }
+      yterm *= y1;
+    }
+    xterm *= x1;
+  }
+  fit[0].Npts ++;
+}
+
+/* convert the xsum,ysum,sum terms into vector,matrix and solve */
+int fit_eval (CoordFit *fit) {
+
+  int i, j, ix, iy, jx, jy;
+  double **matrix, **vector;
+
+  if (fit[0].Npts == 0) {
+    fprintf (stderr, "warning: no valid pts\n");
+    return (FALSE);
+  }
+
+  // matrix, vector hold the final linear system
+  matrix = array_init (fit[0].Nelems, fit[0].Nelems);
+  vector = array_init (fit[0].Nelems, 2);
+
+  /* remap the xsum,ysum terms into the vector */
+  for (i = 0; i < fit[0].Nelems; i++) {
+    ix = i % fit[0].Nterms;
+    iy = i / fit[0].Nterms;
+    vector[i][0] = fit[0].xsum[ix][iy];
+    vector[i][1] = fit[0].ysum[ix][iy];
+
+    for (j = 0; j < fit[0].Nelems; j++) {
+      jx = j % fit[0].Nterms;
+      jy = j / fit[0].Nterms;
+      matrix[i][j] = fit[0].sum[ix+jx][iy+jy];
+    }
+
+    // mask the terms not represented by the Coords terms
+    if (ix + iy > fit[0].Norder) {
+      for (j = 0; j < fit[0].Nelems; j++) {
+	matrix[i][j] = 0.0;
+	matrix[j][i] = 0.0;
+      }
+      vector[i][0] = 0.0;
+      vector[i][1] = 0.0;
+      matrix[i][i] = 1.0;
+    }      
+  }
+
+# define DEBUG FALSE
+  for (i = 0; DEBUG && (i < fit[0].Nelems); i++) {
+    ix = i % fit[0].Nterms;
+    iy = i / fit[0].Nterms;
+    fprintf (stderr, "x2 : x^%dy^%d: %10.4g    y2 : x^%dy^%d: %10.4g \n", 
+    ix, iy, vector[i][0], ix, iy, vector[i][1]);
+  }	
+  
+  if (!dgaussjordan (matrix, vector, fit[0].Nelems, 2)) {
+    array_free (matrix, fit[0].Nelems);
+    array_free (vector, fit[0].Nelems);
+    return (FALSE);
+  }
+
+  for (i = 0; DEBUG && i < fit[0].Nelems; i++) {
+    ix = i % fit[0].Nterms;
+    iy = i / fit[0].Nterms;
+    fprintf (stderr, "x2 : x^%dy^%d: %10.4g    y2 : x^%dy^%d: %10.4g \n", 
+    ix, iy, vector[i][0], ix, iy, vector[i][1]);
+  }	
+
+  /* remap the vector terms into xfit,yfit */
+  for (i = 0; i < fit[0].Nelems; i++) {
+    ix = i % fit[0].Nterms;
+    iy = i / fit[0].Nterms;
+    fit[0].xfit[ix][iy] = vector[i][0];
+    fit[0].yfit[ix][iy] = vector[i][1];
+  }
+
+  array_free (matrix, fit[0].Nelems);
+  array_free (vector, fit[0].Nelems);
+  return (TRUE);
+}
+
+void fit_apply (CoordFit *fit, double *x2, double *y2, double x1, double y1) {
+  
+  int ix, iy;
+  double xterm, yterm, term;
+  double x, y;
+
+  x = 0.0;
+  y = 0.0;
+
+  xterm = 1;
+  for (ix = 0; ix < fit[0].Nterms; ix++) {
+    yterm = 1;
+    for (iy = 0; iy < fit[0].Nterms; iy++) {
+      term = xterm*yterm;
+      x += fit[0].xfit[ix][iy]*term;
+      y += fit[0].yfit[ix][iy]*term;
+      yterm *= y1;
+    }
+    xterm *= x1;
+  }
+  *x2 = x;
+  *y2 = y;
+}
+
+// Nx, Ny is the number of terms in x and in y
+double **poly2d_dx (double **poly, int Nx, int Ny) {
+
+  int i, j, Nxout, Nyout;
+  double **dpoly;
+
+  Nxout = Nx - 1;
+  Nyout = Ny;
+
+  // poly[i][j] holds coeff for x1^i y1^j
+  dpoly = array_init (Nxout, Nyout);
+
+  for (i = 0; i < Nxout; i++) {
+    for (j = 0; j < Nyout; j++) {
+      dpoly[i][j] = poly[i+1][j] * (i+1);
+    }
+  }
+  return dpoly;
+}
+
+// Nx, Ny is the number of terms in x and in y
+double **poly2d_dy (double **poly, int Nx, int Ny) {
+
+  int i, j, Nxout, Nyout;
+  double **dpoly;
+
+  Nxout = Nx;
+  Nyout = Ny - 1;
+
+  // poly[i][j] holds coeff for x1^i y1^j
+  dpoly = array_init (Nxout, Nyout);
+
+  for (i = 0; i < Nxout; i++) {
+    for (j = 0; j < Nyout; j++) {
+      dpoly[i][j] = poly[i][j+1] * (j+1);
+    }
+  }
+  return dpoly;
+}
+
+// Nx, Ny is the number of terms in x and in y
+double **poly2d_copy (double **poly, int Nx, int Ny) {
+
+  int i, j;
+  double **out;
+
+  // poly[i][j] holds coeff for x1^i y1^j
+  out = array_init (Nx, Ny);
+
+  for (i = 0; i < Nx; i++) {
+    for (j = 0; j < Ny; j++) {
+      out[i][j] = poly[i][j];
+    }
+  }
+  return out;
+}
+
+double poly2d_eval (double **poly, int Nx, int Ny, double x, double y) {
+
+  int i, j;
+  double xsum, ysum, sum;
+
+  sum = 0;
+  xsum = ysum = 1.0;
+
+  for (i = 0; i < Nx; i++) {
+    ysum = xsum;
+    for (j = 0; j < Ny; j++) {
+      sum += poly[i][j] * ysum;
+      ysum *= y;
+    }
+    xsum *= x;
+  }
+  return (sum);
+}
+
+/* linear portion of fit : NORDER is 1 */
+/* this should only apply to the polynomial, not the projection terms */
+/* compare with psastro supporting code */
+// this code will work for linear (Npolyterm == 0 or 1) and linear + map fits
+int fit_apply_coords (CoordFit *fit, Coords *coords, int keepRef) {
+
+  double R1, R2;
+  CoordFit *modfit;
+
+# if (0)
+  if (keepRef) {
+    // adjust crpix1,2 as needed:
+    // L = a[0][0] + a[1][0]x^1 y^0 + a[0][1] x^0 y^1 + ...
+    // L = pc1_1*cd1*(x - cp1) + pc1_2*cd2*(y - cp2) + ...
+
+    double Xo, Yo;
+    if (!CoordsGetCenter (fit, 0.001, &Xo, &Yo)) {
+      fprintf (stderr, "failed to modify model\n");
+      return (FALSE);
+    }
+    coords[0].crpix1 = Xo;
+    coords[0].crpix2 = Yo;
+    /* we do not modify crval1,2: these are kept at the default values */
+  
+    // resulting fit should have zero constant terms
+    modfit = CoordsSetCenter (fit, Xo, Yo);
+  } else {
+# endif
+    // L = a[0][0] + a[1][0]x^1 y^0 + a[0][1] x^0 y^1 + ...
+    // P = cd1*x, Q = cd2*y
+    // L = pc1_1*P + pc1_2*Q + ...
+
+    /* modify crval1,2: these are kept at the default values */
+
+    coords[0].crpix1 = 0.0;
+    coords[0].crpix2 = 0.0;
+    modfit = fit;
+// }
+
+  // set cdelt1, cdelt2
+  coords[0].cdelt1 = hypot (modfit[0].xfit[1][0], modfit[0].xfit[0][1]);
+  coords[0].cdelt2 = hypot (modfit[0].yfit[1][0], modfit[0].yfit[0][1]);
+  R1 = 1 / coords[0].cdelt1;
+  R2 = 1 / coords[0].cdelt2;
+
+  // set pc1_1, pc1_2, pc2_1, pc2_2 (cd1,cd2 = 1.0)
+  coords[0].pc1_1 = modfit[0].xfit[1][0] * R1;
+  coords[0].pc1_2 = modfit[0].xfit[0][1] * R2;
+  coords[0].pc2_1 = modfit[0].yfit[1][0] * R1;
+  coords[0].pc2_2 = modfit[0].yfit[0][1] * R2;
+
+  // set the polyterm elements 
+  if (coords->Npolyterms > 1) {
+    coords[0].polyterms[0][0] = modfit[0].xfit[2][0]*R1*R1;
+    coords[0].polyterms[1][0] = modfit[0].xfit[1][1]*R1*R2;
+    coords[0].polyterms[2][0] = modfit[0].xfit[0][2]*R2*R2;
+
+    coords[0].polyterms[0][1] = modfit[0].yfit[2][0]*R1*R1;
+    coords[0].polyterms[1][1] = modfit[0].yfit[1][1]*R1*R2;
+    coords[0].polyterms[2][1] = modfit[0].yfit[0][2]*R2*R2;
+  }
+
+  // I need to validate Norder
+  if (coords->Npolyterms > 2) {
+    coords[0].polyterms[3][0] = modfit[0].xfit[3][0]*R1*R1*R1;
+    coords[0].polyterms[4][0] = modfit[0].xfit[2][1]*R1*R1*R2;
+    coords[0].polyterms[5][0] = modfit[0].xfit[1][2]*R1*R2*R2;
+    coords[0].polyterms[6][0] = modfit[0].xfit[0][3]*R2*R2*R2;
+
+    coords[0].polyterms[3][1] = modfit[0].yfit[3][0]*R1*R1*R1;
+    coords[0].polyterms[4][1] = modfit[0].yfit[2][1]*R1*R1*R2;
+    coords[0].polyterms[5][1] = modfit[0].yfit[1][2]*R1*R2*R2;
+    coords[0].polyterms[6][1] = modfit[0].yfit[0][3]*R2*R2*R2;
+  }
+  
+  if (keepRef) {
+    fit_free (modfit);
+  } else {
+    fit_apply (fit, &coords[0].crval1, &coords[0].crval2, coords[0].crpix1, coords[0].crpix2); 
+  }
+  /* keep the order and type from initial values */
+  
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/fakeastro/src/gaussian.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/gaussian.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/gaussian.c	(revision 37807)
@@ -0,0 +1,75 @@
+# include "fakeastro.h"
+
+static int Ngaussint = 0;
+static double *gaussint;
+
+extern double drand48();
+
+double gaussian (double x, double mean, double sigma) {
+
+  double f;
+
+  f = exp (-0.5 * SQ(x - mean) / SQ(sigma)) / sqrt(2 * M_PI * SQ(sigma));
+
+  return (f);
+
+}
+
+/* integrate a gaussian from -5 sigma to +5 sigma */
+void gauss_init (int Nbin) {
+ 
+  int i;
+  long A, B;
+  double val, x, dx, dx1, dx2, dx3, df;
+  double mean, sigma;
+ 
+  /* no need to generate this if it already exists */
+  if (Ngaussint == Nbin) return;
+
+  A = time(NULL);
+  for (B = 0; A == time(NULL); B++);
+  srand48(B);
+ 
+  Ngaussint = Nbin;
+  ALLOCATE (gaussint, double, Ngaussint + 1);
+
+  val = 0;
+  dx = 1.0 / Ngaussint;
+  dx1 = dx / 3.0;
+  dx2 = 2.0*dx/3.0;
+  dx3 = dx;
+  mean = 0.0;
+  sigma = 1.0;
+ 
+  for (i = 0, x = -7.0; (i < Ngaussint) && (x < 7.0); x += dx)  {
+    df = (3.0*gaussian(x    , mean, sigma) + 
+          9.0*gaussian(x+dx1, mean, sigma) +
+          9.0*gaussian(x+dx2, mean, sigma) + 
+          3.0*gaussian(x+dx3, mean, sigma)) * (dx1/8.0);
+    val += df;
+    if (val > (i + 0.5) / (double) Ngaussint) {
+      gaussint[i] = x + dx / 2.0;
+      i++;
+    }
+  }
+}
+
+double rnd_gauss (double mean, double sigma) {
+ 
+  int i;
+  double y;
+ 
+  y = drand48();
+  i = Ngaussint*y;
+  y = gaussint[i]*sigma + mean;
+ 
+  return (y);
+ 
+}
+ 
+double int_gauss (int i) {
+  double y;
+  y = gaussint[i];
+  return (y);
+}
+ 
Index: /trunk/Ohana/src/fakeastro/src/get_image_patch.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/get_image_patch.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/get_image_patch.c	(revision 37807)
@@ -0,0 +1,145 @@
+# include "fakeastro.h"
+
+SkyRegion *get_image_patch (Image *image) {
+
+  SkyRegion *region;
+
+  // find the R,D coords of the 4 corners and 4 edge midpoints
+
+  static double Xpt[] = {0.0, 0.5, 1.0, 0.0, 1.0, 0.0, 0.5, 1.0};
+  static double Ypt[] = {0.0, 0.0, 0.0, 0.5, 0.5, 1.0, 1.0, 1.0};
+
+  double Rmin = +480.0;
+  double Rmax = -360.0;
+  double Dmin =  +90.0;
+  double Dmax =  -90.0;
+
+  int i;
+  for (i = 0; i < 8; i++) {
+    double R, D;
+    XY_to_RD (&R, &D, Xpt[i]*image->NX, Ypt[i]*image->NY, &image->coords);
+
+    // fprintf (stderr, "%f %f -> %f %f\n", Xpt[i]*image->NX, Ypt[i]*image->NY, R, D); 
+    Rmin = MIN(R,Rmin);
+    Rmax = MAX(R,Rmax);
+    Dmin = MIN(D,Dmin);
+    Dmax = MAX(D,Dmax);
+  }
+
+  ALLOCATE (region, SkyRegion, 1);
+
+  region->Rmin = Rmin;
+  region->Rmax = Rmax;
+  region->Dmin = Dmin;
+  region->Dmax = Dmax;
+
+  // fprintf (stderr, "%f %f , %f %f\n", Rmin, Rmax, Dmin, Dmax);
+
+  return region;
+}
+
+SkyRegion *get_mosaic_patch (Image *image) {
+
+  SkyRegion *region;
+
+  // find the R,D coords of the 4 corners and 4 edge midpoints
+
+  static double Xpt[] = {-0.5,  0.0,  0.5, -0.5, 0.5, -0.5, 0.0, 0.5};
+  static double Ypt[] = {-0.5, -0.5, -0.5,  0.0, 0.0,  0.5, 0.5, 0.5};
+
+  double Rmin = +480.0;
+  double Rmax = -360.0;
+  double Dmin =  +90.0;
+  double Dmax =  -90.0;
+
+  int i;
+  for (i = 0; i < 8; i++) {
+    double R, D;
+    XY_to_RD (&R, &D, Xpt[i]*image->NX, Ypt[i]*image->NY, &image->coords);
+
+    // fprintf (stderr, "%f %f -> %f %f\n", Xpt[i]*image->NX, Ypt[i]*image->NY, R, D); 
+    Rmin = MIN(R,Rmin);
+    Rmax = MAX(R,Rmax);
+    Dmin = MIN(D,Dmin);
+    Dmax = MAX(D,Dmax);
+  }
+
+  ALLOCATE (region, SkyRegion, 1);
+
+  region->Rmin = Rmin;
+  region->Rmax = Rmax;
+  region->Dmin = Dmin;
+  region->Dmax = Dmax;
+
+  // fprintf (stderr, "%f %f , %f %f\n", Rmin, Rmax, Dmin, Dmax);
+
+  return region;
+}
+
+// region has region of Rmin <= R < Rmax, Dmin <= D < Dmax
+int SkyRegionHasPoint (SkyRegion *region, double R, double D) {
+
+  if (D <  region->Dmin) return FALSE;
+  if (D >= region->Dmax) return FALSE;
+  if (R <  region->Rmin) return FALSE;
+  if (R >= region->Rmax) return FALSE;
+  return TRUE;
+}
+
+int SkyRegionsOverlap (SkyRegion *region, SkyRegion *patch) {
+
+  // what are the assumptions?  
+  // * patch->Rmin,Rmax are on the same side of the boundary 
+  //   * [-2,-1], [-2,+1], [358,361] : all OK
+  //   * [358, 2] : NOT OK
+
+  myAssert (patch->Rmin <= patch->Rmax, "impossible!");
+
+  if (region->Dmin >= patch->Dmax) return FALSE;
+  if (region->Dmax <= patch->Dmin) return FALSE;
+
+  int keepR0 = FALSE;
+  int keepR1 = FALSE;
+  int keepR2 = FALSE;
+  if (patch->Rmin < 0.0) {
+    float Rmin = patch->Rmin + 360.0;
+    float Rmax = patch->Rmax + 360.0;
+    keepR0 = (Rmin <= region->Rmax) && (Rmax >= region->Rmin);
+  }
+  if (patch->Rmax > 360.0) {
+    float Rmin = patch->Rmin - 360.0;
+    float Rmax = patch->Rmax - 360.0;
+    keepR1 = (Rmin <= region->Rmax) && (Rmax >= region->Rmin);
+  }
+  keepR2 = (patch->Rmin <= region->Rmax) && (patch->Rmax >= region->Rmin);
+
+  int keep = keepR0 || keepR1 || keepR2;
+
+  if (!keep) return FALSE;
+  return TRUE;
+}
+
+SkyRegion *SkyRegionExpand (SkyRegion *region, float boundary) {
+
+  SkyRegion *output = NULL;
+  ALLOCATE (output, SkyRegion, 1);
+
+  output->Dmin = MAX (-90.0, region->Dmin - boundary);
+  output->Dmax = MIN (+90.0, region->Dmax + boundary);
+
+  if ((region->Rmax == 360.0) && (region->Rmin == 0.0)) {
+    output->Rmin = region->Rmin;
+    output->Rmax = region->Rmax;
+    return output;
+  }
+
+  float dRmin = boundary / cos (RAD_DEG*region->Dmin);
+  float dRmax = boundary / cos (RAD_DEG*region->Dmin);
+
+  float dR = MAX (dRmin, dRmax);
+
+  output->Rmin = region->Rmin - dR;
+  output->Rmax = region->Rmax + dR;
+
+  return output;
+}
Index: /trunk/Ohana/src/fakeastro/src/initialize.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/initialize.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/initialize.c	(revision 37807)
@@ -0,0 +1,37 @@
+# include "fakeastro.h"
+
+void usage (void);
+void usage_client (void);
+
+void initialize (int argc, char **argv) {
+
+  if (argc == 1) usage();
+  if (get_argument (argc, argv, "-h")) usage();
+  if (get_argument (argc, argv, "--h")) usage();
+  if (get_argument (argc, argv, "-help")) usage();
+  if (get_argument (argc, argv, "--help")) usage();
+
+  args (&argc, argv);
+  ConfigInit (&argc, argv);
+  if (argc != 1) usage ();
+
+  // XXX add to config?
+  if (!InitGalaxyModel ("ROESER")) {
+    fprintf (stderr, "failed to init galaxy model\n");
+    exit (2);
+  }
+}
+
+void initialize_client (int argc, char **argv) {
+
+  args_client (&argc, argv);
+  ConfigInit (&argc, argv);
+  if (argc != 1) usage_client ();
+
+  // XXX add to config?
+  if (!InitGalaxyModel ("ROESER")) {
+    fprintf (stderr, "failed to init galaxy model\n");
+    exit (2);
+  }
+}
+
Index: /trunk/Ohana/src/fakeastro/src/insert_fakestar.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/insert_fakestar.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/insert_fakestar.c	(revision 37807)
@@ -0,0 +1,120 @@
+# include "fakeastro.h"
+
+// assume we are working with an empty database?  allow for matches?
+int insert_fakestar (SkyRegion *region, FakeAstro_Stars *stars, int Nstars, Catalog *catalog) {
+  
+  off_t i, j;
+  off_t Nave, NAVE, Nstarpar, NSTARPAR, Nmatch;
+
+  /* internal counters */
+  Nmatch = 0;
+  NSTARPAR = Nstarpar = catalog[0].Nstarpar;
+
+  // current max obj ID for this catalog
+  unsigned int objID = catalog[0].objID;
+  unsigned int catID = catalog[0].catID;
+
+  int Nsecfilt = catalog[0].Nsecfilt;
+  NAVE = Nave = catalog[0].Naverage;
+  NSTARPAR = Nstarpar = catalog[0].Nstarpar;
+  
+  /** incorporate unmatched image stars? **/
+  for (i = 0; i < Nstars; i++) {
+
+    // skip already matched stars
+    if (stars[i].found) continue;
+    if (!IN_REGION (stars[i].R, stars[i].D)) continue;
+
+    /* make sure there is space for next entry */
+    if (Nstarpar >= NSTARPAR) {
+      NSTARPAR = Nstarpar + 1000;
+      REALLOCATE (catalog[0].starpar, StarPar, NSTARPAR);
+    }
+    if (Nave >= NAVE) {
+      NAVE = Nave + 1000;
+      REALLOCATE (catalog[0].average, Average, NAVE);
+      REALLOCATE (catalog[0].secfilt, SecFilt, NAVE*Nsecfilt);
+    }
+
+    dvo_average_init (&catalog[0].average[Nave]);
+    catalog[0].average[Nave].R         	   = stars[i].R;
+    catalog[0].average[Nave].D         	   = stars[i].D;
+
+    catalog[0].average[Nave].Nstarpar  	   = 1;
+    catalog[0].average[Nave].starparOffset = Nstarpar;
+    catalog[0].average[Nave].objID     	   = objID;
+    catalog[0].average[Nave].catID     	   = catID;
+
+    objID ++;
+
+    for (j = 0; j < Nsecfilt; j++) {
+      dvo_secfilt_init (&catalog[0].secfilt[Nave*Nsecfilt+j]);
+    }
+
+    double m_r, gr, ri, rz, zy;
+
+    // valid stars do not have crazy FeH values
+    if (fabs(stars[i].starpar.FeH) < 50.0) {
+      // Mr vs r-i, Mr vs r-z: linear fit from Bochanski et al 2010 Fig 7 & Fig 9 
+      // http://iopscience.iop.org/1538-3881/139/6/2679/pdf/aj_139_6_2679.pdf
+      // gr, zy relations from Magnier et al 2012 Fig 3
+      double M_r = stars[i].starpar.M_r;
+      m_r = M_r + stars[i].starpar.DistMag;
+      ri = 0.215*M_r - 1.05;
+      rz = 0.350*M_r - 1.82;
+      gr = 2.27*ri + 0.09;
+      zy = 0.37*(rz - ri) + 0.03; // zy vs iz
+    }
+    // ICRF QSOs have FeH = -100
+    if (stars[i].starpar.FeH < -50.0) {
+      m_r = stars[i].starpar.M_r;
+      ri = 0.0;
+      rz = 0.0;
+      gr = 0.0;
+      zy = 0.0;
+    }
+    // ICRF QSOs have FeH = -100
+    if (stars[i].starpar.FeH > +50.0) {
+      m_r = stars[i].starpar.M_r;
+      ri = 0.0;
+      rz = 0.0;
+      gr = 0.0;
+      zy = 0.0;
+    }
+
+    double m_g = m_r + gr;
+    double m_i = m_r - ri;
+    double m_z = m_r - rz;
+    double m_y = m_z - zy;
+
+    // I need a photcode for r-band
+    catalog[0].secfilt[Nave*Nsecfilt+0].M = m_g;
+    catalog[0].secfilt[Nave*Nsecfilt+1].M = m_r;
+    catalog[0].secfilt[Nave*Nsecfilt+2].M = m_i;
+    catalog[0].secfilt[Nave*Nsecfilt+3].M = m_z;
+    catalog[0].secfilt[Nave*Nsecfilt+4].M = m_y;
+
+    catalog[0].starpar[Nstarpar]        = stars[i].starpar;
+    catalog[0].starpar[Nstarpar].averef = Nave;
+    catalog[0].starpar[Nstarpar].objID  = catalog[0].average[Nave].objID;
+    catalog[0].starpar[Nstarpar].catID  = catID;
+
+    stars[i].found = TRUE;
+    Nstarpar ++;
+    Nave ++;
+  }
+
+  REALLOCATE (catalog[0].average, Average, Nave);
+  REALLOCATE (catalog[0].starpar, StarPar, Nstarpar);
+ 
+  catalog[0].sorted = FALSE;
+
+  /* check if the catalog has changed?  if no change, no need to write */
+  catalog[0].objID     = objID; // new max value, save on catalog close
+  catalog[0].Naverage  = Nave;
+  catalog[0].Nstarpar  = Nstarpar;
+  catalog[0].Nsecf_mem = Nave*Nsecfilt;
+  if (VERBOSE) fprintf (stderr, "Nstars, Nave, Nstarpar: %d "OFF_T_FMT" "OFF_T_FMT" ("OFF_T_FMT" matches)\n",  Nstars, Nave, Nstarpar, Nmatch);
+
+  return (Nmatch);
+}
Index: /trunk/Ohana/src/fakeastro/src/load_fake_stars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/load_fake_stars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/load_fake_stars.c	(revision 37807)
@@ -0,0 +1,34 @@
+# include "fakeastro.h"
+
+Catalog *load_fake_stars (SkyList *skylist, int *ncatalog) {
+
+  Catalog *catalog = NULL;
+  ALLOCATE (catalog, Catalog, skylist->Nregions);
+
+  // load stars from database in these regions
+  int i;
+  for (i = 0; i < skylist->Nregions; i++) {
+    dvo_catalog_init (&catalog[i], TRUE);
+
+    // set the parameters which guide catalog open/load/create
+    catalog[i].filename  = skylist[0].filename[i];
+    catalog[i].catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+    catalog[i].catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+    catalog[i].catflags  = LOAD_AVES | LOAD_SECF | LOAD_STARPAR;
+    catalog[i].Nsecfilt  = GetPhotcodeNsecfilt ();
+    if (!dvo_catalog_open (&catalog[i], skylist[0].regions[i], VERBOSE, "r")) {
+      fprintf (stderr, "ERROR: failure reading catalog %s\n", catalog[i].filename);
+      exit (1);
+    }
+    dvo_catalog_unlock (&catalog[i]);
+
+    if (!catalog[i].Naves_disk) {
+	if (VERBOSE2) { fprintf (stderr, "no data in %s, skipping\n", catalog[i].filename); }
+	dvo_catalog_free (&catalog[i]);
+	continue;
+    }
+  }
+
+  *ncatalog = skylist->Nregions;
+  return catalog;
+}
Index: /trunk/Ohana/src/fakeastro/src/load_template_images.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/load_template_images.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/load_template_images.c	(revision 37807)
@@ -0,0 +1,82 @@
+# include "fakeastro.h"
+
+Image *load_template_images (int *nrefimage) {
+
+  FITS_DB db;
+
+  db.mode    = dvo_catalog_catmode (CATMODE);
+  db.format  = dvo_catalog_catformat (CATFORMAT);
+  int status = dvo_image_lock (&db, IMAGES_INPUT, 60.0, LCK_SOFT);  // shorter timeout?
+  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", db.filename);
+
+  // load the image table 
+  if (db.dbstate == LCK_EMPTY) {
+    dvo_image_unlock (&db); // unlock input
+    fprintf (stderr, "ERROR: no template images in %s\n", IMAGES_INPUT);
+    exit (3);
+  }
+
+  // this operation reads the PHU header (db.header)
+  if (!dvo_image_load (&db, VERBOSE, TRUE)) {
+    Shutdown ("can't read input image catalog %s", db.filename);
+  }
+  dvo_image_unlock (&db); // unlock input
+
+  off_t Nimage;
+  Image *image = gfits_table_get_Image (&db.ftable, &Nimage, &db.swapped);
+  if (!image) {
+    fprintf (stderr, "ERROR: no template images in %s (though db exists)\n", IMAGES_INPUT);
+    exit (3);
+  }
+
+  BuildChipMatch (image, Nimage);
+
+  int NREFIMAGE = 1000;
+  int Nrefimage = 0;
+  Image *refimage;
+  ALLOCATE (refimage, Image, NREFIMAGE);
+
+  int i, j;
+  for (i = 0; i < Nimage; i++) {
+
+    // we only want to make fake images for the exposures
+    if (strcmp(&image[i].coords.ctype[4], "-DIS")) continue;
+
+    refimage[Nrefimage] = image[i];
+
+    // find a corresponding chip image:
+    Image *childImage = NULL;
+    for (j = i + 1; j < i + 65; j++) {
+      if (image[j].parent == &image[i]) {
+	childImage = &image[j];
+	break;
+      }
+    }
+    if (!childImage) continue;
+
+    // XXX hard=wire the photcode range for now (just i-band)
+    if (childImage->photcode < 10200) continue;
+    if (childImage->photcode > 10277) continue;
+
+    refimage[Nrefimage].secz     = childImage->secz;
+    refimage[Nrefimage].Mcal     = childImage->Mcal;
+    refimage[Nrefimage].dMcal    = childImage->dMcal;
+    refimage[Nrefimage].exptime  = childImage->exptime;
+    refimage[Nrefimage].sidtime  = childImage->sidtime;
+    refimage[Nrefimage].latitude = childImage->latitude;
+    refimage[Nrefimage].fwhm_x   = childImage->fwhm_x;
+    refimage[Nrefimage].fwhm_y   = childImage->fwhm_y;
+
+    // this is a bit of a hack, but not too bad (very gpc1 specific):
+    refimage[Nrefimage].photcode = 100 * (int) (childImage->photcode / 100);
+
+    Nrefimage ++;
+    CHECK_REALLOCATE (refimage, Image, NREFIMAGE, Nrefimage, 1000);
+  }
+
+  // free the input image database
+  gfits_db_free (&db);
+
+  *nrefimage = Nrefimage;
+  return refimage;
+}
Index: /trunk/Ohana/src/fakeastro/src/make_2mass_measures.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_2mass_measures.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_2mass_measures.c	(revision 37807)
@@ -0,0 +1,125 @@
+# include "fakeastro.h"
+
+// region corresponds to the catalog
+int make_2mass_measures (Catalog *catalog) {
+
+  int Nsecfilt = GetPhotcodeNsecfilt ();
+
+  Average *average = catalog->average;
+  SecFilt *secfilt = catalog->secfilt;
+  StarPar *starpar = catalog->starpar;
+  Measure *measure = catalog->measure;
+  
+  // we have starpar.R,D, which represent the true positions at the reference epoch, FAKEASTRO_REF_EPOCH
+  // the 2MASS stars are generated at locations for the 2MASS EPOCH
+  time_t timeRef = ohana_date_to_sec(FAKEASTRO_REF_EPOCH);
+  time_t tzero_2mass = ohana_date_to_sec(FAKEASTRO_2MASS_EPOCH);
+
+  // tzero, timeRef are in UNIX seconds, Toffset should be in years
+  double Toffset = (tzero_2mass - timeRef) / 365.25 / 86400.0;
+
+  // use photcode to get zero point
+  PhotCode *codeJ = GetPhotcodebyName ("2MASS_J");
+  // PhotCode *codeH = GetPhotcodebyName ("2MASS_H");
+  // PhotCode *codeK = GetPhotcodebyName ("2MASS_K");
+
+  float ZP = 21.0; // this is chosen to give 16.0 mag stars a flux of 100.0 counts (crude, yes)
+  float SkyCts = 300.0; // this is chosen to make SN @ 16.0 = 5.0 (
+
+  int Nmeasure = catalog->Nmeasure;
+  int NMEASURE = catalog->Nmeasure;
+
+  int Nsec = 4; // y-band
+
+  int i;
+  for (i = 0; i < catalog->Naverage; i++) {
+
+    if (Nmeasure >= NMEASURE) {
+      NMEASURE = Nmeasure + 1000;
+      REALLOCATE (catalog[0].measure, Measure, NMEASURE);
+      measure = catalog[0].measure;
+    }
+
+    int Nstarpar = average[i].starparOffset;
+
+    // make a crude JHK color for now:
+    double J_PS1 = secfilt[i*Nsecfilt + Nsec].M - 1.0; // make all stars have y-J = 1.0
+    if (J_PS1 > 16.0) continue; // only generate 2MASS detections for objects with J_PS1 < 16.0
+
+    if (isnan(secfilt[i*Nsecfilt + Nsec].M)) {
+      // look for a non-NAN secfilt mag and just use that (it is not super important)
+      int ns;
+      for (ns = 0; ns < Nsecfilt; ns++) {
+	if (!isnan(secfilt[i*Nsecfilt + ns].M)) break;
+      }
+      if (ns == Nsecfilt) continue; // no non-nan
+
+      J_PS1 = secfilt[i*Nsecfilt + ns].M; // pretend secfilt.M = J
+      if (J_PS1 > 16.0) continue; // only generate 2MASS detections for objects with J_PS1 < 16.0
+    }
+
+    double Minst = J_PS1 - ZP; 
+
+    double Counts = pow(10.0, -0.4*Minst);
+
+    double SN = Counts / sqrt(SkyCts + Counts);
+
+    // true position from src catalog
+    double Rtru = starpar[Nstarpar].R;
+    double Dtru = starpar[Nstarpar].D;
+
+    // observed position is scattered from true position by:
+    // * proper motion
+    // * gaussian scatter (~ seeing) 
+    double uR = starpar[Nstarpar].uRA; // starpar are in arcsec / year
+    double uD = starpar[Nstarpar].uDEC;
+    
+    // uR,uD in linear (arcsec / yr)
+    double dRpm = uR*Toffset;
+    double dDpm = uD*Toffset;
+
+    // uR,uD in linear arcsec
+    double dRsee = rnd_gauss (0.0, 1.0 / SN);
+    double dDsee = rnd_gauss (0.0, 1.0 / SN);
+
+    double dRoff = (dRpm + dRsee) / 3600.0;
+    double dDoff = (dDpm + dDsee) / 3600.0;
+
+    double Robs = Rtru + dRoff / cos(Dtru*RAD_DEG);
+    double Dobs = Dtru + dDoff;
+
+    dvo_measure_init (&measure[Nmeasure]);
+
+    measure[Nmeasure].R = Robs;
+    measure[Nmeasure].D = Dobs;
+
+    measure[Nmeasure].M      = J_PS1;
+    measure[Nmeasure].dM     = 1.0 / SN;
+
+    measure[Nmeasure].Sky        = SkyCts;
+    measure[Nmeasure].dSky       = sqrt(SkyCts);
+    measure[Nmeasure].photFlags  = 0;
+    measure[Nmeasure].photFlags2 = 0;
+    measure[Nmeasure].airmass = 1.0;
+    measure[Nmeasure].az      = 0.0; // irrelevant
+    measure[Nmeasure].Mcal    = 0.0;
+    measure[Nmeasure].t       = tzero_2mass;
+    measure[Nmeasure].dt      = 0.0;
+    measure[Nmeasure].photcode = codeJ->code;
+
+    measure[Nmeasure].averef   = i;
+    measure[Nmeasure].objID    = average[i].objID;
+    measure[Nmeasure].catID    = average[i].catID;
+
+    measure[Nmeasure].imageID = 0;
+
+    // This is may optionally be replaced by the internal sequence (see FilterStars.c)
+    measure[Nmeasure].detID      = 0;
+    Nmeasure ++;
+  }
+
+  if (VERBOSE) fprintf (stderr, "added %d 2mass entries to %d stars\n",  (int) Nmeasure, (int) catalog->Naverage);
+
+  catalog->Nmeasure = Nmeasure;
+  return TRUE;
+}
Index: /trunk/Ohana/src/fakeastro/src/make_fake_images.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_fake_images.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_fake_images.c	(revision 37807)
@@ -0,0 +1,127 @@
+# include "fakeastro.h"
+
+// fields I need to inherit from (one of) the WRP images:
+// secz, Mcal (ensure measure.M is set right), exptime (if not hardwired), sidtime (or calculate from jd, longitude),
+// extern_id, source_id?, name (is this used in relastro?, NO: stack names are used in relphot to set the tess_id)
+
+Image *make_fake_images (Image *image, int *nfakeImage) {
+
+  // given an image (really, the WCS) which defines an exposure, generate the image
+  // parameters for a full set of chips in an exposure
+  
+  int NfakeImage = 65;
+  Image *fakeImage = NULL;
+
+  ALLOCATE (fakeImage, Image, NfakeImage);
+
+  // first image is the PHU, copy basic data:
+
+  fakeImage[0] = image[0];
+  fakeImage[0].imageID = IMAGE_ID;
+  IMAGE_ID ++;
+  
+  int N = 1;
+
+  if (ONE_BIG_CHIP) {
+    fakeImage[N] = fakeImage[0];
+
+    // coords map relative to image center:
+    fakeImage[N].coords.crval1 = 0.0;
+    fakeImage[N].coords.crval2 = 0.0;
+
+    fakeImage[N].coords.crpix1 = 0.5*8*5000;
+    fakeImage[N].coords.crpix2 = 0.5*8*5000;
+    fakeImage[N].coords.pc1_1 = 1.0;
+    fakeImage[N].coords.pc2_2 = 1.0;
+
+    fakeImage[N].coords.cdelt1 = 1.0;
+    fakeImage[N].coords.cdelt2 = 1.0;
+
+    fakeImage[N].coords.pc1_2 = 0.0;
+    fakeImage[N].coords.pc2_1 = 0.0;
+
+    strcpy (fakeImage[N].coords.ctype, "DEC--WRP");
+    fakeImage[N].coords.Npolyterms = 1;
+
+    fakeImage[N].coords.mosaic = &fakeImage[0].coords;
+
+    fakeImage[N].NX = 8*5000;
+    fakeImage[N].NY = 8*5000;
+
+    fakeImage[N].imageID = IMAGE_ID;
+    IMAGE_ID ++;
+
+    // gpc1-specific (assumes grizy map to 10101, etc)
+    fakeImage[N].photcode = fakeImage[0].photcode + 01;
+
+    char *ptr = strstr (fakeImage[N].name, "[PHU]");
+    if (ptr) {
+      snprintf (ptr, 7, "[XY01]");
+    }
+    N++;
+  } else {
+    int ix, iy;
+    for (ix = 0; ix < 8; ix++) {
+      for (iy = 0; iy < 8; iy++) {
+      
+	if ((ix == 0) && (iy == 0)) continue;
+	if ((ix == 7) && (iy == 0)) continue;
+	if ((ix == 0) && (iy == 7)) continue;
+	if ((ix == 7) && (iy == 7)) continue;
+
+	fakeImage[N] = fakeImage[0];
+
+	// coords map relative to image center:
+	fakeImage[N].coords.crval1 = 0.0;
+	fakeImage[N].coords.crval2 = 0.0;
+
+	if (ix < 4) {
+	  fakeImage[N].coords.crpix1 = (ix - 3)*5000;
+	  fakeImage[N].coords.crpix2 = (iy - 3)*5100;
+	  fakeImage[N].coords.pc1_1 = 1.0;
+	  fakeImage[N].coords.pc2_2 = 1.0;
+	} else {
+	  fakeImage[N].coords.crpix1 = (4 - ix)*5000;
+	  fakeImage[N].coords.crpix2 = (4 - iy)*5100;
+	  fakeImage[N].coords.pc1_1 = -1.0;
+	  fakeImage[N].coords.pc2_2 = -1.0;
+	} 
+
+	fakeImage[N].coords.cdelt1 = 1.0;
+	fakeImage[N].coords.cdelt2 = 1.0;
+
+	fakeImage[N].coords.pc1_2 = 0.0;
+	fakeImage[N].coords.pc2_1 = 0.0;
+
+	strcpy (fakeImage[N].coords.ctype, "DEC--WRP");
+	fakeImage[N].coords.Npolyterms = 1;
+
+	// XXX add higher order polynomials or imagemaps here
+
+	fakeImage[N].coords.mosaic = &fakeImage[0].coords;
+
+	fakeImage[N].NX = 4850;
+	fakeImage[N].NY = 4850;
+
+	fakeImage[N].imageID = IMAGE_ID;
+	IMAGE_ID ++;
+
+	// gpc1-specific (assumes grizy map to 10101, etc)
+	fakeImage[N].photcode = fakeImage[0].photcode + iy*10 + ix;
+
+	char *ptr = strstr (fakeImage[N].name, "[PHU]");
+	if (ptr) {
+	  snprintf (ptr, 7, "[XY%d%d]", ix, iy);
+	}
+	N++;
+      }
+    }
+  }
+
+  // the PHU needs to have photcode of 0 for relphot
+  fakeImage[0].photcode = 0;
+
+  *nfakeImage = N;
+  return fakeImage;
+}
+
Index: /trunk/Ohana/src/fakeastro/src/make_fake_stars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_fake_stars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_fake_stars.c	(revision 37807)
@@ -0,0 +1,32 @@
+# include "fakeastro.h"
+
+Stars *make_fake_stars (Catalog *catalog, int Ncatalog, SkyList *skylist, Image *image, Stars *stars, int *nstars) {
+
+  // patch is generous region around image, but limited ot this image
+  SkyRegion *imagePatch = get_image_patch (image);
+
+  // fprintf (stderr, "image patch: %f %f , %f %f\n", imagePatch->Rmin, imagePatch->Rmax, imagePatch->Dmin, imagePatch->Dmax);
+
+  int Nstars = *nstars;
+
+  // load stars from database in these regions
+  int i;
+  for (i = 0; i < Ncatalog; i++) {
+    // skylist matches catalog
+
+    // fprintf (stderr, "try catalog: %f %f , %f %f : ", skylist[0].regions[i][0].Rmin, skylist[0].regions[i][0].Rmax, skylist[0].regions[i][0].Dmin, skylist[0].regions[i][0].Dmax);
+
+    // XXX check that catalog overlaps patch
+    if (!SkyRegionsOverlap(skylist[0].regions[i], imagePatch)) { continue; }
+    // fprintf (stderr, "skip\n"); 
+    // fprintf (stderr, "keep\n");
+
+    // generate fake measurements for this image
+    stars = make_fake_stars_catalog (stars, &Nstars, imagePatch, &catalog[i], image);
+  }
+
+  free (imagePatch);
+
+  *nstars = Nstars;
+  return stars;
+}
Index: /trunk/Ohana/src/fakeastro/src/make_fake_stars_catalog.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_fake_stars_catalog.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_fake_stars_catalog.c	(revision 37807)
@@ -0,0 +1,187 @@
+# include "fakeastro.h"
+# define SCALE 0.001
+
+// sky[Nsecfilt], crude and hard-wired for now
+float sky[] = {100.0, 150.0, 200.0, 300.0, 400.0};
+
+// things to figure out:
+// * which filter am I (image.photcode)
+// * lookup table of sky counts / pixel or seeing disk
+// * what about QSOs?
+
+/* used in find_matches, find_matches_refstars */
+# define IN_PATCH(REGION,R,D) (					\
+    ((D) >= REGION[0].Dmin) && ((D) < REGION[0].Dmax) &&	\
+    ((R) >= REGION[0].Rmin) && ((R) < REGION[0].Rmax))
+
+// region corresponds to the catalog
+Stars *make_fake_stars_catalog (Stars *stars, int *nstars, SkyRegion *imagePatch, Catalog *catalog, Image *image) {
+
+  int Nstars = *nstars;
+  
+  // we have starpar.R,D, which represent the true positions at the reference epoch, FAKEASTRO_REF_EPOCH
+  time_t timeRef = ohana_date_to_sec(FAKEASTRO_REF_EPOCH);
+  
+  if (!stars) {
+    ALLOCATE (stars, Stars, catalog->Naverage);
+  } else {
+    REALLOCATE (stars, Stars, Nstars + catalog->Naverage);
+  }
+  
+  int Nsecfilt = GetPhotcodeNsecfilt ();
+  
+  // use photcode to get zero point
+  PhotCode *code = GetPhotcodebyCode (image->photcode);
+  int Nsec = GetPhotcodeNsec (code->equiv);
+  myAssert (Nsec >= 0, "undefined Nsec?");
+  
+  float Mtime = 2.5*log10(image->exptime);
+  
+  // XXX hard-wired plateScale for now?
+  double plateScale = 0.257;
+
+  // XXX put in airmass?
+  float ZP  = SCALE*code->C - image->Mcal + Mtime;
+  float ZPo = 25.0 - image->Mcal + Mtime;
+  // float ZP = code[0].K*(measure[0].airmass - 1.000) + SCALE*code[0].C - measure[0].Mcal;
+  
+  // generate a set of measurements for each star entry
+
+  Average *average = catalog->average;
+  SecFilt *secfilt = catalog->secfilt;
+  StarPar *starpar = catalog->starpar;
+  
+  double Rmid = 0.5*(imagePatch->Rmin + imagePatch->Rmax);
+
+  int i;
+  for (i = 0; i < catalog->Naverage; i++) {
+
+    // true position from src catalog
+    double ra = ohana_normalize_angle_to_midpoint (average[i].R, Rmid);
+
+    // int show = !strcmp (image->name, "o6600g0127o.672655.cm.955522.smf[XY01]") && ((average[i].R < 1.0) || (average[i].R > 359.0));
+    int show = FALSE;
+
+    if (show) fprintf (stderr, "try: %f (%f) %f : %f %f , %f %f", average[i].R, ra, average[i].D, imagePatch->Rmin, imagePatch->Rmax, imagePatch->Dmin, imagePatch->Dmax);
+
+    if (!IN_PATCH(imagePatch, ra, average[i].D)) {
+      if (show) fprintf (stderr, "\n");
+      continue;
+    }
+    if (show) fprintf (stderr, " * \n");
+
+    int nStar = average[i].starparOffset;
+
+    InitStar (&stars[Nstars]);
+
+    // which filter?
+    // float Mraw = Minst + measure[0].dt + 25.0;
+    // float Minst = Msky - SCALE*code->C - measure[0].dt
+
+    double Minst = secfilt[i*Nsecfilt + Nsec].M  - ZP;
+    double Counts = pow(10.0, -0.4*Minst);
+    double SkyCts = sky[Nsec];
+
+    double SN = Counts / sqrt(SkyCts + Counts);
+
+    // XXX skip stars with low S/N
+    if ((SN < 5.0) || isnan (SN)) continue;
+
+    // true position from src catalog
+    double Rtru = average[i].R;
+    double Dtru = average[i].D;
+    
+    stars[Nstars].starpar = starpar[nStar]; // keep this so we can save it on the generated stars
+
+    // XXX what is the epoch for the src catalog? J2000?
+    stars[Nstars].Rref = Rtru; // add in any deviations we want here 
+    stars[Nstars].Dref = Dtru;
+
+    // observed position is scattered from true position by:
+    // * proper motion
+    // * gaussian scatter (~ seeing) 
+    double uR = starpar[nStar].uRA; // starpar are in arcsec / year
+    double uD = starpar[nStar].uDEC;
+    
+    // tzero, timeRef are in UNIX seconds, Toffset should be in years
+    double Toffset = (image->tzero - timeRef) / 365.25 / 86400.0;
+    // Toffset = 0.0; // XXX TEST
+
+    // uR,uD in linear (arcsec / yr)
+    double dRpm = uR*Toffset;
+    double dDpm = uD*Toffset;
+
+    // uR,uD in linear arcsec
+    double dRsee = rnd_gauss (0.0, 1.0 / SN);
+    double dDsee = rnd_gauss (0.0, 1.0 / SN);
+
+    double dRoff = (dRpm + dRsee) / 3600.0;
+    double dDoff = (dDpm + dDsee) / 3600.0;
+
+    double Robs = Rtru + dRoff / cos(Dtru*RAD_DEG);
+    double Dobs = Dtru + dDoff;
+
+    Robs = ohana_normalize_angle (Robs);
+
+    if (isnan(Robs)) continue;
+    if (isnan(Dobs)) continue;
+
+    double X, Y;
+    RD_to_XY (&X, &Y, Robs, Dobs, &image->coords);
+
+    if ((fabs(Robs) < 0.1) || (fabs(Robs - 360.0) < 0.1)) {
+      // fprintf (stderr, "%f %f : %f %f : %f %f\n", Rtru, Dtru, Robs, Dobs, X, Y);
+    }
+
+    if (X < 0) continue;
+    if (Y < 0) continue;
+    if (X > image->NX) continue;
+    if (Y > image->NY) continue;
+    if (isnan(X)) continue;
+    if (isnan(Y)) continue;
+
+    if (!strcmp (image->name, "o6600g0127o.672655.cm.955522.smf[XY01]")) {
+      // fprintf (stderr, "keep: %f %f\n", average[i].R, average[i].D);
+    }
+
+    stars[Nstars].measure.Xccd       = X;
+    stars[Nstars].measure.Yccd       = Y;
+    stars[Nstars].measure.dXccd      = 1.0 / SN / plateScale;
+    stars[Nstars].measure.dYccd      = 1.0 / SN / plateScale;
+
+    // stars[Nstars].measure.posangle   = ToShortDegrees(ps1data[i].posangle);
+    // stars[Nstars].measure.pltscale   = ps1data[i].pltscale;
+
+    stars[Nstars].measure.M      = Minst + ZPo; // XX I need to compensate for the internal zero point of 25.0
+    stars[Nstars].measure.dM     = 1.0 / SN;
+
+    // stars[Nstars].measure.dMcal      = ps1data[i].dMcal;
+    stars[Nstars].measure.Sky        = sky[Nsec];
+    stars[Nstars].measure.dSky       = sqrt(sky[Nsec]);
+                        
+    stars[Nstars].measure.photFlags  = 0;
+    stars[Nstars].measure.photFlags2 = 0;
+
+    stars[Nstars].average.R = Robs;
+    stars[Nstars].average.D = Dobs;
+
+    stars[Nstars].measure.photcode = image->photcode;
+
+    stars[Nstars].measure.airmass = airmass (image->secz, stars[Nstars].average.R, stars[Nstars].average.D, image->sidtime, image->latitude);
+    if (!isfinite(stars[Nstars].measure.airmass)) continue;
+
+    stars[Nstars].measure.az      = azimuth (15.0*image->sidtime - stars[Nstars].average.R, stars[Nstars].average.D, image->latitude);
+    stars[Nstars].measure.Mcal    = image->Mcal;
+    stars[Nstars].measure.t       = image->tzero + 1e-4*stars[Nstars].measure.Yccd*image->trate;  // trate is in 0.1 msec / row 
+    stars[Nstars].measure.dt      = Mtime;
+
+    stars[Nstars].measure.imageID = image->imageID;
+
+    // This is may optionally be replaced by the internal sequence (see FilterStars.c)
+    stars[Nstars].measure.detID      = Nstars + 1;
+    Nstars ++;
+  }
+
+  *nstars = Nstars;
+  return stars;
+}
Index: /trunk/Ohana/src/fakeastro/src/make_fakeqsos.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_fakeqsos.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_fakeqsos.c	(revision 37807)
@@ -0,0 +1,123 @@
+# include "fakeastro.h"
+
+FakeAstro_Stars *make_fakeqsos (int Nstars, int ICRF) {
+
+  FakeAstro_Stars *stars;
+  ALLOCATE (stars, FakeAstro_Stars, Nstars);
+
+  // do I want to add in a galaxy obscuration (P(b) ?)
+
+  float dR = UserPatch.Rmax - UserPatch.Rmin;
+  float dPmin = sin(RAD_DEG*UserPatch.Dmin);
+  float dPmax = sin(RAD_DEG*UserPatch.Dmax);
+  float dP = dPmax - dPmin;
+
+  CoordTransform *transback = InitTransform (COORD_CELESTIAL, COORD_GALACTIC);
+
+  FILE *f = NULL;
+  if (ICRF) {
+    f = fopen ("fake.icrf.dat", "w");
+    if (!f) {
+      fprintf (stderr, "unable to open fake.icrf.dat\n");
+    }
+  } else {
+    f = fopen ("fake.zero.dat", "w");
+    if (!f) {
+      fprintf (stderr, "unable to open fake.zero.dat\n");
+    }
+  } 
+
+  int i;
+  for (i = 0; i < Nstars; i++) {
+
+    // ra, dec, distance, (absolute mag, colors) or (mass) or (MK type)
+
+    if (i % 100000 == 0) fprintf (stderr, ".");
+    double R,D;
+
+    int inPatch = FALSE;
+    while (!inPatch) {
+      double phi = dP * drand48() + dPmin;
+      D = DEG_RAD * asin(phi); // random in degrees
+      R = drand48() * dR + UserPatch.Rmin;   // random in degrees
+      if (R < UserPatch.Rmin) continue;
+      if (R > UserPatch.Rmax) continue;
+      if (D < UserPatch.Dmin) continue;
+      if (D > UserPatch.Dmax) continue;
+      break;
+    }
+    double L, B;
+    ApplyTransform (&L, &B, R, D, transback);
+
+    // Mr will be interpretted as m_r
+    double Mr = rnd_gauss (18.0, 1.5);
+    
+    stars[i].R = R;
+    stars[i].D = D;
+    stars[i].flag  = FALSE;
+    stars[i].found = FALSE;
+
+    stars[i].starpar.R      = R;
+    stars[i].starpar.D      = D;
+    stars[i].starpar.galLon = L;
+    stars[i].starpar.galLat = B;
+
+    // how shall I distinguish ICRF and ZERO quasars
+    stars[i].starpar.Ebv      = 0.0;
+    stars[i].starpar.dEbv     = 0.0;
+    stars[i].starpar.DistMag  = 0.0;
+    stars[i].starpar.dDistMag = 0.0;
+    stars[i].starpar.M_r      = Mr;
+    stars[i].starpar.dM_r     = 0.0;
+
+    // overload FeH to identify the ICRF vs ZERO QSOs
+    if (ICRF) {
+      stars[i].starpar.FeH      = -100.0;
+    } else {
+      stars[i].starpar.FeH      = +100.0;
+    }
+    stars[i].starpar.dFeH     = 0.0;
+
+    stars[i].starpar.uRA  = 0.0;
+    stars[i].starpar.uDEC = 0.0;
+
+/*
+#                       hr mn seconds   deg mn seconds      mas    mas                   Jy     Jy      Jy     Jy      Jy     Jy     Jy     Jy      Jy     Jy
+C  2357+080 J0000+0816  00 00 07.031141 +08 16 45.05175    0.46   0.85   0.758     41  -1.00  -1.00   -1.00  -1.00    0.020 <0.014  -1.00  -1.00   -1.00  -1.00   X    rfc_2014c
+01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0         1         2         3         4         5         6         7         8         9         0         1         2         3         4         5         6         
+*/
+
+    if (f) {
+      char Rline[32], Dline[32];
+      {
+	int flag = SIGN(R);
+	double hours = fabs(R / 15.0);  /* convert from degrees to hours */
+	int h = hours;
+	int m = 60.000001*(hours - h);
+	float s = 3600*(hours - h - m / 60.0);
+	if (flag > 0) {
+	  snprintf (Rline, 32, " %02d %02d %09.6f  ", h, m, s);
+	} else {
+	  snprintf (Rline, 32, "-%02d %02d %09.6f  ", h, m, s);
+	}	
+      }
+      {
+	int flag = SIGN(D);
+	double dec = fabs(D);
+	int d = dec;
+	int m = 60.000001*(dec - d);
+	float s = 3600*(dec - d - m / 60.0);
+	if (flag > 0) {
+	  snprintf (Dline, 32, " %02d %02d %09.6f  ", d, m, s);
+	} else {
+	  snprintf (Dline, 32, "-%02d %02d %09.6f  ", d, m, s);
+	}	
+      }
+
+      fprintf (f, "C  2357+080 J0000+0816 %16s %15s    0.46   0.85   0.758     41  -1.00  -1.00   -1.00  -1.00    0.020 <0.014  -1.00  -1.00   -1.00  -1.00   X    rfc_2014c\n", Rline, Dline);
+    }
+  }
+  fprintf (stderr, "\n");
+  return stars;
+}
Index: /trunk/Ohana/src/fakeastro/src/make_fakestars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_fakestars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_fakestars.c	(revision 37807)
@@ -0,0 +1,143 @@
+# include "fakeastro.h"
+
+FakeAstro_Stars *make_fakestars (int Nstars) {
+
+  FakeAstro_Stars *stars;
+  ALLOCATE (stars, FakeAstro_Stars, Nstars);
+
+  // libdvo uses Liu et al 2011 (A&A 526, A16) for default galactic coords,
+  // but Greg Green / LSD use the older Reid et al 2004 (ApJ 616, 872) definition
+  CoordTransform *transform = InitTransform (COORD_GALACTIC, COORD_CELESTIAL);
+  CoordTransform *transback = InitTransform (COORD_CELESTIAL, COORD_GALACTIC);
+
+  float dR = UserPatch.Rmax - UserPatch.Rmin;
+  float dPmin = sin(RAD_DEG*UserPatch.Dmin);
+  float dPmax = sin(RAD_DEG*UserPatch.Dmax);
+  float dP = dPmax - dPmin;
+
+  int i;
+  for (i = 0; i < Nstars; i++) {
+
+    // each star has the following value drawn from an appropriate random distribution:
+
+    // ra, dec, distance, (absolute mag, colors) or (mass) or (MK type)
+
+    /* very simple galaxy model:
+
+     * rho(x,y,z) = exp(-0.5*(z/Zpc)^2) (r < 2000)
+
+     */
+
+    if (i % 100000 == 0) fprintf (stderr, ".");
+    double z,r,L,B,R,D,Lrad,Brad;
+
+    if (UNIFORM_RADEC) {
+      // we can generate a distribution which is uniform on the sky, in which case
+      // we can limit to the selected patch analyically
+      r = pow(drand48(), 0.33333) * FAKEASTRO_RGAL;
+      z = 0.0;
+      double phi = dP * drand48() + dPmin;
+      D = DEG_RAD * asin(phi); // random in degrees
+      R = drand48() * dR + UserPatch.Rmin;   // random in degrees
+      R = ohana_normalize_angle (R);
+
+      ApplyTransform (&L, &B, R, D, transback);
+      Lrad = L*RAD_DEG;
+      Brad = B*RAD_DEG;
+    } else {
+      int inPatch = FALSE;
+      while (!inPatch) {
+	z = rnd_gauss (0.0, FAKEASTRO_ZGAL);
+	r = sqrt(drand48()) * FAKEASTRO_RGAL;
+	Lrad = drand48() * 2 * M_PI;
+	Brad = atan2(z,r);
+      
+	L = Lrad*DEG_RAD;
+	B = Brad*DEG_RAD;
+      
+	ApplyTransform (&R, &D, L, B, transform);
+	if (R < UserPatch.Rmin) continue;
+	if (R > UserPatch.Rmax) continue;
+	if (D < UserPatch.Dmin) continue;
+	if (D > UserPatch.Dmax) continue;
+	break;
+      }
+    }
+
+    // double x = r*cos(L);
+    // double y = r*sin(L);
+
+    double distance = sqrt (SQ(r) + SQ(z));
+
+    double uL_gal, uB_gal;
+    GalaxyMotionModel_radians(&uL_gal, &uB_gal, Lrad, Brad);
+
+    double uL_sol, uB_sol;
+    SolarMotionModel_radians(&uL_sol, &uB_sol, Lrad, Brad, distance);
+
+    double uL = uL_gal + uL_sol;
+    double uB = uB_gal + uB_sol;
+    
+    // XXX: amplify motion to make tests easier:
+    if (TESTING) {
+      uL *= 100.0;
+      uB *= 100.0;
+    }
+    
+    double uR, uD;
+    TransformProperMotion (&uR, &uD, uL, uB, R, D, transback);
+
+    // crude Mr distribution from Bochanski et al 2010
+    // http://iopscience.iop.org/1538-3881/139/6/2679/pdf/aj_139_6_2679.pdf
+    
+    // two gaussian distributions:
+    // 75% in narrow gauss, with Mr = 11.25, sigma = 1.0
+    // 25% is wide gauss, with Mr = 10.0, sigma = 3.0
+    // first choose which gauss:
+    int bigPeak = (drand48() > 0.60);
+    double Mr;
+    if (bigPeak) {
+      Mr = rnd_gauss (11.25, 1.0);
+    } else {
+      Mr = rnd_gauss (10.00, 2.5);
+    }
+    
+    stars[i].R = R;
+    stars[i].D = D;
+    stars[i].flag  = FALSE;
+    stars[i].found = FALSE;
+
+    stars[i].starpar.R      = R;
+    stars[i].starpar.D 	    = D;
+    stars[i].starpar.galLon = L;
+    stars[i].starpar.galLat = B;
+
+    stars[i].starpar.Ebv      = 0.0;
+    stars[i].starpar.dEbv     = 0.0;
+    stars[i].starpar.DistMag  = 5.0*log10(distance) - 5.0;
+    stars[i].starpar.dDistMag = 0.0;
+    stars[i].starpar.M_r      = Mr;
+    stars[i].starpar.dM_r     = 0.0;
+    stars[i].starpar.FeH      = 0.0;
+    stars[i].starpar.dFeH     = 0.0;
+
+    stars[i].starpar.uRA  = uR;
+    stars[i].starpar.uDEC = uD;
+  }
+  fprintf (stderr, "\n");
+  return stars;
+}
+
+int sortStars (FakeAstro_Stars *stars, int Nstars) {
+
+# define SWAPFUNC(A,B){ FakeAstro_Stars temp = stars[A]; stars[A] = stars[B]; stars[B] = temp; }
+# define COMPARE(A,B)(stars[A].R < stars[B].R)
+
+  OHANA_SORT (Nstars, COMPARE, SWAPFUNC);
+
+# undef SWAPFUNC
+# undef COMPARE
+  
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/fakeastro/src/make_subset.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/make_subset.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/make_subset.c	(revision 37807)
@@ -0,0 +1,43 @@
+# include "fakeastro.h"
+
+// assign stars in the given region to the subset (NOTE: stars are sorted by RA, start is
+// first entry in stars array in this region)
+
+FakeAstro_Stars *make_subset (FakeAstro_Stars *stars, int Nstars, int start, SkyRegion *region, int *nsubset) {
+
+  int i;
+
+  FakeAstro_Stars *subset = NULL;
+
+  // collect array of (Stars *) stars in a new output catalog
+  int Nsubset = 0;
+  int NSUBSET = 3000;
+  ALLOCATE (subset, FakeAstro_Stars, NSUBSET);
+
+  // find the rest of the stars in this output region
+  for (i = start; i < Nstars; i++) {
+    if (stars[i].flag) continue;
+
+    // check if in skyregion
+    if (stars[i].R < region[0].Rmin) continue;
+    if (stars[i].R > region[0].Rmax) break;
+    if (stars[i].D < region[0].Dmin) continue;
+    if (stars[i].D > region[0].Dmax) continue;
+	  
+    // check if in UserPatch (a GLOBAL)
+    if (stars[i].R < UserPatch.Rmin) continue;
+    if (stars[i].R > UserPatch.Rmax) break;
+    if (stars[i].D < UserPatch.Dmin) continue;
+    if (stars[i].D > UserPatch.Dmax) continue;
+	  
+    subset[Nsubset] = stars[i];
+    Nsubset ++;
+
+    stars[i].flag = TRUE;
+
+    CHECK_REALLOCATE (subset, FakeAstro_Stars, NSUBSET, Nsubset, 10000);
+  }
+
+  *nsubset = Nsubset;
+  return subset;
+}
Index: /trunk/Ohana/src/fakeastro/src/match_fake_stars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/match_fake_stars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/match_fake_stars.c	(revision 37807)
@@ -0,0 +1,295 @@
+# include "fakeastro.h"
+
+int match_fake_stars (Stars *stars, unsigned int NstarsIn, SkyRegion *region, Catalog *catalog) {
+  
+  off_t i, j, n, N, J, Jmin, status, Nstars;
+  double RADIUS2, Rmin;
+  double *X1, *Y1, *X2, *Y2;
+  double dX, dY, dR;
+  off_t *N1, *N2;
+  off_t Nave, NAVE, Nmeas, NMEAS, Nstarpar, NSTARPAR, Nmatch;
+  unsigned int objID, catID;
+  Coords tcoords;
+
+  int Nsecfilt = GetPhotcodeNsecfilt ();
+
+  /** allocate local arrays (stars) **/
+  ALLOCATE (X1, double, NstarsIn);
+  ALLOCATE (Y1, double, NstarsIn);
+  ALLOCATE (N1, off_t,  NstarsIn);
+
+  /** allocate local arrays (catalog) **/
+  NAVE = Nave = catalog[0].Naverage;
+  ALLOCATE (X2, double, NAVE);
+  ALLOCATE (Y2, double, NAVE);
+  ALLOCATE (N2, off_t,  NAVE);
+  ALLOCATE (catalog[0].found_t, off_t, NAVE);
+  /* for secfilt j and star i, secfilt[i*Nsecfilt+j] */
+
+  /* internal counters */
+  Nmatch = 0;
+  NMEAS = Nmeas = catalog[0].Nmeasure;
+  NSTARPAR = Nstarpar = catalog[0].Nstarpar;
+
+  // current max obj ID for this catalog
+  objID = catalog[0].objID;
+  catID = catalog[0].catID;
+
+  /* project onto rectilinear grid with 1 arcsec pixels. the choice of ARC projection has
+   * the advantage that every point in R,D has a mapping to a unique X,Y.  However, note
+   * that not all possible X,Y points map back to R,D and the local plate scale changes
+   * far from the projection pole. We use the center of the region (catalog) for crval1,2.
+   */
+  InitCoords (&tcoords, "DEC--ARC");
+  tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
+  if (region[0].Dmax < 90) {
+    tcoords.crval2 = 0.5*(region[0].Dmin + region[0].Dmax);
+  } else {
+    tcoords.crval2 = 90.0;
+  }
+  tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
+
+  /* build spatial index (RA sort) referencing input array sequence */
+  Nstars = 0;
+  for (i = 0; i < NstarsIn; i++) {
+    status = RD_to_XY (&X1[Nstars], &Y1[Nstars], stars[i].average.R, stars[i].average.D, &tcoords);
+    if (!status) continue;
+    N1[Nstars] = i;
+    Nstars ++;
+  }
+  if (Nstars < 1) {
+    if (VERBOSE) fprintf (stderr, "skipping %s, no overlapping stars\n", catalog[0].filename);
+    free (X1);
+    free (Y1);
+    free (N1);
+    free (X2);
+    free (Y2);
+    free (N2);
+    return (0);
+  }
+  if (Nstars > 1) sort_coords_index (X1, Y1, N1, Nstars);
+
+  /* build spatial index (RA sort) */
+  for (i = 0; i < Nave; i++) {
+    RD_to_XY (&X2[i], &Y2[i], catalog[0].average[i].R, catalog[0].average[i].D, &tcoords);
+    N2[i] = i;
+    catalog[0].found_t[N2[i]] = -1;
+  }
+  if (Nave > 1) sort_coords_index (X2, Y2, N2, Nave);
+
+  /* RADIUS is global from ConfigInit.c */
+  RADIUS2 = RADIUS*RADIUS;
+
+  /** find matched stars **/
+  for (i = j = 0; (i < Nstars) && (j < Nave); ) {
+    if (!finite(X1[i]) || !finite(Y1[i])) { 
+      i++; 
+      continue;
+    }
+    if (!finite(X2[j]) || !finite(Y2[j])) { 
+      j++; 
+      continue;
+    }
+    
+    /* negative dX: j is too large */
+    dX = X1[i] - X2[j];
+    if (dX <= -1.02*RADIUS) {
+      i++;
+      continue;
+    }
+    /* positive dX, i is too large */
+    if (dX >= 1.02*RADIUS) {
+      j++;
+      continue;
+    }
+
+    if (stars[N1[i]].found != -1) {
+        /* this star has already been assigned to an object in this or another catalog */
+        i++;
+        continue;
+    }
+
+    /* this block will match a given detection to the closest object within range of that detection.
+       XXX note that this matches ALL detections within range of the single object to that same object 
+       this is bad, but I cannot just go in linear order (ie, mark off each object as they are
+       used).  I should make a list of all Nobj * Ndet pairs in range and choose the matches
+       based on their separations.  UGH
+     */
+    
+    /* within match range; look for matches */
+    Jmin = -1;
+    Rmin = RADIUS2;
+    for (J = j; (dX > -1.02*RADIUS) && (J < Nave); J++) {
+      /* find closest match for this detection */
+      dX = X1[i] - X2[J];
+      dY = Y1[i] - Y2[J];
+      dR = dX*dX + dY*dY;
+      if (dR > RADIUS2) continue;
+      if (dR < Rmin) {
+	Rmin = dR;
+	Jmin  = J;
+      }
+    }
+
+    /* no match, try next detection */ 
+    if (Jmin == -1) {
+      i++;
+      continue;
+    }
+
+    /*** a match is found, add to average, measure ***/
+    Nmatch ++;
+    n = N2[Jmin];
+    N = N1[i];
+
+    /* make sure there is space for next entry */
+    if (Nmeas >= NMEAS) {
+      NMEAS = Nmeas + 1000;
+      REALLOCATE (catalog[0].measure, Measure, NMEAS);
+    }
+
+    /** add measurements for this star **/
+
+    // set the new measurements
+    catalog[0].measure[Nmeas]          = stars[N].measure;
+
+    // measure now carries R,D (not dR,dD) 
+    // note that ReadStarsFITS does not set measure.R,D and average.R,D
+    catalog[0].measure[Nmeas].R        = stars[N].average.R;
+    catalog[0].measure[Nmeas].D        = stars[N].average.D;
+    catalog[0].measure[Nmeas].dbFlags  = 0;
+    catalog[0].measure[Nmeas].averef   = n;
+    catalog[0].measure[Nmeas].objID    = catalog[0].average[n].objID;
+    catalog[0].measure[Nmeas].catID    = catalog[0].catID;
+
+    float dRoff = dvoOffsetR(&catalog[0].measure[Nmeas], &catalog[0].average[n]);
+
+    // rationalize dR
+    if (dRoff > +180.0*3600.0) {
+      // average on high end of boundary, move star up
+      catalog[0].measure[Nmeas].R += 360.0;
+      dRoff -= 360.0*3600.0;
+    }
+    if (dRoff < -180.0*3600.0) {
+      // average on low end of boundary, move star down
+      catalog[0].measure[Nmeas].R -= 360.0;
+      dRoff += 360.0*3600.0;
+    }
+    if (fabs(dRoff) > 10*RADIUS) {
+	// take declination into account and check again.
+	double cosD = cos(RAD_DEG*catalog[0].average[n].D);
+	if (fabs(dRoff*cosD) > 10*RADIUS) {
+	    fprintf (stderr, "error: %10.6f,%10.6f vs %10.6f,%10.6f (%f,%f vs %f,%f)\n", 
+		     catalog[0].average[n].R, catalog[0].average[n].D, 
+		     stars[N].average.R, stars[N].average.D,
+		     X1[i], X2[Jmin], 
+		     Y1[i], Y2[Jmin]);
+	    // XXX abort on this? -- this is a bad failure...
+	}
+    }
+
+    /* Nm is updated, but not written out in -update mode (for existing entries)
+       Nm is recalculated in build_meas_links if loaded table is not sorted */
+    stars[N].found = Nmeas;
+    catalog[0].found_t[n] = Nmeas;
+    catalog[0].average[n].Nmeasure ++;
+    Nmeas ++;
+    i++;
+  }
+
+  /** incorporate unmatched image stars, if this star is in field of this catalog **/
+  /* these new entries are all written out in UPDATE mode */ 
+  for (i = 0; i < Nstars; i ++) {
+    if (stars[i].found != -1) continue;
+    if (!IN_REGION (stars[i].average.R, stars[i].average.D)) continue;
+
+    /* make sure there is space for next entry */
+    if (Nmeas >= NMEAS - 1) {
+      NMEAS = Nmeas + 1000;
+      REALLOCATE (catalog[0].measure, Measure, NMEAS);
+    }
+    /* make sure there is space for next entry */
+    if (Nstarpar >= NSTARPAR - 1) {
+      NSTARPAR = Nstarpar + 1000;
+      REALLOCATE (catalog[0].starpar, StarPar, NSTARPAR);
+    }
+    if (Nave >= NAVE) {
+      NAVE = Nave + 1000;
+      REALLOCATE (catalog[0].average, Average, NAVE);
+      REALLOCATE (catalog[0].secfilt, SecFilt, NAVE*catalog[0].Nsecfilt);
+    }
+
+    dvo_average_init (&catalog[0].average[Nave]);
+    catalog[0].average[Nave].R         	   = stars[i].average.R;
+    catalog[0].average[Nave].D         	   = stars[i].average.D;
+
+    catalog[0].average[Nave].Nstarpar  	   = 1;
+    catalog[0].average[Nave].starparOffset = Nstarpar;
+    catalog[0].average[Nave].Nmeasure  	   = 1;
+    catalog[0].average[Nave].measureOffset = Nmeas;
+    catalog[0].average[Nave].objID     	   = objID;
+    catalog[0].average[Nave].catID     	   = catID;
+
+    objID ++;
+
+    for (j = 0; j < Nsecfilt; j++) {
+      dvo_secfilt_init (&catalog[0].secfilt[Nave*Nsecfilt+j]);
+    }
+
+    // supply the measurments from this detection
+    dvo_measure_init (&catalog[0].measure[Nmeas]);
+    catalog[0].measure[Nmeas]           = stars[i].measure;
+
+    // the following measure elements cannot be set until here:
+    catalog[0].measure[Nmeas].R        = stars[i].average.R;
+    catalog[0].measure[Nmeas].D        = stars[i].average.D;
+    catalog[0].measure[Nmeas].dbFlags  = 0;
+    catalog[0].measure[Nmeas].averef   = Nave;
+    catalog[0].measure[Nmeas].objID    = catalog[0].average[Nave].objID;
+    catalog[0].measure[Nmeas].catID    = catalog[0].catID;
+
+    // supply the starpar values from this detection
+    dvo_starpar_init (&catalog[0].starpar[Nstarpar]);
+    catalog[0].starpar[Nstarpar] = stars[i].starpar;
+    catalog[0].starpar[Nstarpar].averef   = Nave;
+    catalog[0].starpar[Nstarpar].objID    = catalog[0].average[Nave].objID;
+    catalog[0].starpar[Nstarpar].catID    = catalog[0].catID;
+    Nstarpar ++;
+
+    /* next[Nmeas] should always be -1 in this context (it is always the only
+       measurement for the star) */
+    stars[i].found = Nmeas;
+    Nmeas ++;
+    Nave ++;
+  }
+      
+  REALLOCATE (catalog[0].average, Average, Nave);
+  REALLOCATE (catalog[0].measure, Measure, Nmeas);
+  REALLOCATE (catalog[0].starpar, StarPar, Nstarpar);
+ 
+  catalog[0].sorted = FALSE;
+
+  /* note stars which have been found in this catalog */
+  for (i = 0; i < NstarsIn; i++) {
+    if (stars[i].found > -1) {
+      stars[i].found = -2;
+    }
+  }
+
+  /* check if the catalog has changed?  if no change, no need to write */
+  catalog[0].objID    = objID; // new max value, save on catalog close
+  catalog[0].Naverage = Nave;
+  catalog[0].Nmeasure = Nmeas;
+  catalog[0].Nstarpar = Nstarpar;
+  catalog[0].Nsecf_mem = Nave*Nsecfilt;
+  if (VERBOSE) fprintf (stderr, "Nstars, Nave, Nmeas: "OFF_T_FMT" "OFF_T_FMT" "OFF_T_FMT", ("OFF_T_FMT" matches)\n",  Nstars,  Nave,  Nmeas,  Nmatch);
+
+  free (X1);
+  free (Y1);
+  free (N1);
+  free (N2);
+  free (X2);
+  free (Y2);
+
+  return (Nmatch);
+}
Index: /trunk/Ohana/src/fakeastro/src/remote_hosts.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/remote_hosts.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/remote_hosts.c	(revision 37807)
@@ -0,0 +1,141 @@
+# include "fakeastro.h"
+# include <sys/types.h>
+# include <sys/wait.h>
+
+# define DEBUG 1
+
+// we are running N parallel remote jobs on the remote hosts.  In the usual remote
+// processing (eg, relphot or relastro), we launch one job per remote host.  In this case,
+// we are launching one job foreach table (catalog.cpt) being touched.  individual hosts
+// may have more than one job active at a time.  
+
+int       Nhosts = 0;
+HostInfo **hosts = NULL;
+
+int init_remote_hosts () {
+
+  Nhosts = 10;
+  ALLOCATE (hosts, HostInfo *, Nhosts);
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    hosts[i] = NULL;
+  }
+  return TRUE;
+}
+
+int find_empty_slot () {
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    if (hosts[i] == NULL) return i;
+  }
+  return -1;
+}
+
+int harvest_all () {
+
+  int slot = -1;
+
+  while (slot != -2) {
+    slot = harvest_host ();
+    usleep (50000);
+  }
+  return TRUE;
+}
+ 
+// put the host in the list in a free slot. NOTE: this should never be called if we do not
+// already have a free slot.
+int save_remote_host (HostInfo *host) {
+
+  int i;
+  for (i = 0; i < Nhosts; i++) {
+    if (hosts[i] == NULL) {
+      hosts[i] = host;
+      return TRUE;
+    }
+  }
+  myAbort ("failed to find an empty slot: this is a programming error");
+  return FALSE;
+}
+
+// wait for all children to complete, report output to stdout
+// possible states when function is finished:
+// * no child was harvested, but children still exist
+// * no outstanding children
+// * a child was harvested
+int harvest_host () {
+
+  // check if any children have finished...
+  int status = 0;
+  int pid = waitpid (-1, &status, WNOHANG);
+
+  if (!pid) return -1; // there are outstanding children, but none have exited, no slot was opened
+  if ((pid == -1) && (errno == ECHILD)) return -2; // there are no outstanding children
+  if ((pid == -1) && (errno != ECHILD)) myAbort ("programming error?");
+
+  // find the host which has finished
+  int i;
+  int found = FALSE;
+  for (i = 0; (i < Nhosts) && !found; i++) {
+    if (!hosts[i]) continue; // unassigned slot
+    if (hosts[i][0].pid != pid) continue;
+    found = TRUE;
+    break;
+  }
+  myAssert (found, "Programming error: failed to matched finished job to known host!");
+  int slot = i;
+
+  HostInfo *host = hosts[slot];
+
+  // check on the status of this job and report any output
+  if (DEBUG) fprintf (stdout, "job finished for %s (%d)\n", host->hostname, pid);
+
+  int Nout, printHead;
+
+  // read stdout
+  EmptyIOBuffer (&host->stdout, 100, host->stdio[HOST_STDOUT]);
+  printHead = VERBOSE || (host->stdout.Nbuffer > 0);
+  if (printHead) fprintf (stdout, "--- stdout from %s --- (%d bytes, v2)\n", host->hostname, host->stdout.Nbuffer);
+  Nout = write (STDOUT_FILENO, host->stdout.buffer, host->stdout.Nbuffer);
+  if (Nout != host->stdout.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+  FlushIOBuffer (&host->stdout);
+  if (printHead) fprintf (stdout, "\n");
+	    
+  // read stderr
+  EmptyIOBuffer (&host->stderr, 100, host->stdio[HOST_STDERR]);
+  printHead = VERBOSE || (host->stderr.Nbuffer > 0);
+  if (printHead) fprintf (stdout, "--- stderr from %s --- (%d bytes, v2)\n", host->hostname, host->stderr.Nbuffer);
+  Nout = write (STDOUT_FILENO, host->stderr.buffer, host->stderr.Nbuffer);
+  if (Nout != host->stderr.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+  FlushIOBuffer (&host->stderr);
+  if (printHead) fprintf (stdout, "\n");
+
+  if (WIFEXITED(status)) {
+    if (DEBUG) fprintf (stdout, "normal completion, exit status is %d\n", WEXITSTATUS(status));
+    host->status = WEXITSTATUS(status);
+    if (host->status) {
+      fprintf (stdout, "job failed on %s\n", host->hostname);
+    }
+  } else {
+    host->status = -1;
+    fprintf (stdout, "job exited abnormally on %s\n", host->hostname);
+  }
+
+  // close opened connections
+  close (host->stdio[HOST_STDIN]);
+  close (host->stdio[HOST_STDOUT]);
+  close (host->stdio[HOST_STDERR]);
+
+  // free data associated with the host
+  free (host->hostname);
+  free (host->pathname);
+  FreeIOBuffer (&host->stdout);
+  FreeIOBuffer (&host->stderr);
+  free (host);
+
+  // free the slot
+  hosts[slot] = NULL;
+  return slot; 
+}
+
Index: /trunk/Ohana/src/fakeastro/src/save_fake_stars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/save_fake_stars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/save_fake_stars.c	(revision 37807)
@@ -0,0 +1,55 @@
+# include "fakeastro.h"
+
+int save_fake_stars (SkyTable *sky, Image *mosaic, Stars *stars, int Nstars) {
+
+  Catalog catalog;
+
+  // patch is generous region around image, but limited to this image
+  SkyRegion *mosaicPatch = get_mosaic_patch (mosaic);
+
+  // list of regions which cover this image
+  SkyList *skylist = SkyListByPatch (sky, -1, mosaicPatch);
+
+  int Naverage = 0;
+  int Nmeasure = 0;
+
+  INITTIME;
+
+  int i;
+  for (i = 0; i < skylist->Nregions; i++) {
+
+    // set the parameters which guide catalog open/load/create
+    catalog.filename  = skylist[0].filename[i];
+    catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+    catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+    // catalog.catflags  = LOAD_AVES | LOAD_SECF | LOAD_MEAS | LOAD_STARPAR; // use this for non-update mode
+    catalog.catflags  = LOAD_AVES | LOAD_SECF | LOAD_STARPAR;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+
+    // I want to do an update here
+    if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE, "w")) {
+      fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+      exit (2);
+    }
+
+    match_fake_stars (stars, Nstars, skylist[0].regions[i], &catalog);
+
+    /* report total updated values */
+    Naverage += catalog.Naverage;
+    Nmeasure += catalog.Nmeasure;
+
+    SetProtect (TRUE);
+    dvo_catalog_update (&catalog, VERBOSE);
+    SetProtect (FALSE);
+
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+    if (VERBOSE) MARKTIME ("save cpt: %f sec\n", dtime); RESETTIME; 
+  }
+
+  free (mosaicPatch);
+  SkyListFree (skylist);
+  return TRUE;
+}
+
+// XXX what about starpar values?
Index: /trunk/Ohana/src/fakeastro/src/save_fakestars.c
===================================================================
--- /trunk/Ohana/src/fakeastro/src/save_fakestars.c	(revision 37807)
+++ /trunk/Ohana/src/fakeastro/src/save_fakestars.c	(revision 37807)
@@ -0,0 +1,84 @@
+# include "fakeastro.h"
+# define DEBUG 1
+
+int save_fakestars (FakeAstro_Stars *stars, int Nstars, HostTable *hosts, SkyRegion *region, char *fullname) {
+
+  char uniquer[12];
+  int TIME = time(NULL);
+  int PID = getpid();
+  snprintf (uniquer, 12, "%05d.%05d", PID, TIME % 100000);
+
+  // if this region is a parallel thing, save and launch remote
+  if (!PARALLEL) { 
+    fakestar_catalog (stars, Nstars, region, fullname);
+  } else {
+    int N = hosts->index[region->hostID];
+    HostInfo *hostMach = &hosts->hosts[N];
+
+    // save to a unique filename
+    char filename[1024]; // CATDIR/tmpdir/starpar.PID.index.fits
+    snprintf (filename, 1024, "%s/tmpdir/starpar.%s.%05d.fits", CATDIR, uniquer, region->index);
+
+    // write the data to the given FITS file
+    fakestar_save_stars (filename, stars, Nstars);
+
+    int slot = -1;
+    while (slot == -1) {
+      slot = find_empty_slot ();
+      if (slot == -1) {
+	usleep (50000);
+	slot = harvest_host();
+	myAssert (slot != -2, "we should not call harvest_host here if we have open slots");
+      }
+    }
+
+    // allocate a host for this job
+    HostInfo *host = NULL;
+    ALLOCATE (host, HostInfo, 1);
+
+    // we want to run this job on the host described by hostMach.  copy
+    // immutable data from hostMach to a locally allocated host:
+    host->hostID = hostMach->hostID;
+    host->hostname = strcreate (hostMach->hostname);
+    host->pathname = strcreate (hostMach->pathname);
+    InitIOBuffer (&host->stdout, 1000);
+    InitIOBuffer (&host->stderr, 1000);
+
+    // got a valid slot, so launch a new host
+
+    // need to generate the remote command
+    char command[1024];
+    snprintf (command, 1024, "fakestar_client");
+    strextend (command, "-galaxy");
+    strextend (command, "-hostID %d", host->hostID);
+    strextend (command, "-D CATDIR %s", CATDIR);
+    strextend (command, "-hostdir %s", host->pathname);
+    strextend (command, "-cpt %s", region->name);
+    strextend (command, "-input %s", filename);
+
+    // launch the job on the remote machine (no handshake)
+    int errorInfo = 0;
+    int pid = rconnect ("ssh", host->hostname, command, host->stdio, &errorInfo, FALSE);
+    if (!pid) {
+      if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", host->hostname, errorInfo);
+      exit (1);
+    }
+    host->pid = pid; // save for future reference
+    
+    save_remote_host (host);
+  }
+  return TRUE;
+}
+
+int strextend (char *input, char *format,...) {
+
+  char tmpextra[1024], tmpline[1024];
+  va_list argp;
+
+  va_start (argp, format);
+  vsnprintf (tmpextra, 1024, format, argp);
+  snprintf (tmpline, 1024, "%s %s", input, tmpextra);
+  strcpy (input, tmpline);
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/fixcat/src/gcatstats.c
===================================================================
--- /trunk/Ohana/src/fixcat/src/gcatstats.c	(revision 37806)
+++ /trunk/Ohana/src/fixcat/src/gcatstats.c	(revision 37807)
@@ -46,11 +46,8 @@
   /* project onto rectilinear grid with 1 arcsec pixels, sort by X */
   /* reference for coords is center of field  */
+  InitCoords (&catstats[0].coords, "DEC--TAN");
   catstats[0].coords.crval1 = RaCenter;
   catstats[0].coords.crval2 = DecCenter;
-  catstats[0].coords.crpix1 = catstats[0].coords.crpix2 = 0.0;
   catstats[0].coords.cdelt1 = catstats[0].coords.cdelt2 = 1.0 / 3600.0;
-  catstats[0].coords.pc1_1 = catstats[0].coords.pc2_2 = 1.0;
-  catstats[0].coords.pc1_2 = catstats[0].coords.pc2_1 = 0.0;
-  strcpy (catstats[0].coords.ctype, "DEC--TAN");
 
   X1 = catstats[0].X;
Index: /trunk/Ohana/src/gastro/src/gfitpoly.c
===================================================================
--- /trunk/Ohana/src/gastro/src/gfitpoly.c	(revision 37806)
+++ /trunk/Ohana/src/gastro/src/gfitpoly.c	(revision 37807)
@@ -261,4 +261,6 @@
     det = 1.0 / (a1*b2 - a2*b1);
 
+    InitCoords (coords, "DEC--PLY");
+
     coords[0].pc1_1 = p11*a1 + p12*b1*(S2/S1);
     coords[0].pc2_1 = p21*a1 + p22*b1*(S2/S1);
@@ -273,5 +275,4 @@
 
     coords[0].Npolyterms = NORDER;
-    strcpy (coords[0].ctype, "DEC--PLY");
 
     /* generate higher order terms from vector */
Index: /trunk/Ohana/src/gastro/src/gstars.c
===================================================================
--- /trunk/Ohana/src/gastro/src/gstars.c	(revision 37806)
+++ /trunk/Ohana/src/gastro/src/gstars.c	(revision 37807)
@@ -79,9 +79,8 @@
   if (!strcasecmp (ROUGH_ASTROMETRY, "config")) {
     /* default values for coords */
-    strcpy (coords[0].ctype, "DEC--TAN");
+    InitCoords (coords, "DEC--TAN");
     coords[0].pc1_1 = CCD_PC1_1; coords[0].pc1_2 = CCD_PC1_2;
     coords[0].pc2_1 = CCD_PC2_1; coords[0].pc2_2 = CCD_PC2_2;
     coords[0].cdelt1 = coords[0].cdelt2 = ASEC_PIX / 3600.0;
-    coords[0].Npolyterms = 0;
     coords[0].crpix1 = 0.5*(*NX);
     coords[0].crpix2 = 0.5*(*NY);
Index: /trunk/Ohana/src/gastro2/src/gproject2.c
===================================================================
--- /trunk/Ohana/src/gastro2/src/gproject2.c	(revision 37806)
+++ /trunk/Ohana/src/gastro2/src/gproject2.c	(revision 37807)
@@ -19,22 +19,10 @@
 
   /* create Tangent Plane to Sky transformation from input coords */
-  strcpy (TPtoSky.ctype, coords[0].ctype);
+  InitCoords (&TPtoSky, coords[0].ctype);
   TPtoSky.crval1 = coords[0].crval1;
   TPtoSky.crval2 = coords[0].crval2;
 
-  TPtoSky.cdelt1 = TPtoSky.cdelt2 = 1;
-  TPtoSky.crpix1 = TPtoSky.crpix2 = 0;
-  TPtoSky.pc1_1 = TPtoSky.pc2_2 = 1;
-  TPtoSky.pc1_2 = TPtoSky.pc2_1 = 0;
-  TPtoSky.Npolyterms = 0;
-  for (i = 0; i < 7; i++) {
-    TPtoSky.polyterms[i][0] = 0;
-    TPtoSky.polyterms[i][1] = 0;
-  }
-
   /* create Focal Plane to Tangent Plane transformation from input coords */
-  strcpy (FPtoTP.ctype, "FP---PLY");
-  FPtoTP.crval1 = FPtoTP.crval2 = 0;
-
+  InitCoords (&FPtoTP, "FP---PLY");
   FPtoTP.cdelt1 = coords[0].cdelt1;
   FPtoTP.cdelt2 = coords[0].cdelt2;
Index: /trunk/Ohana/src/gastro2/src/gstars2.c
===================================================================
--- /trunk/Ohana/src/gastro2/src/gstars2.c	(revision 37806)
+++ /trunk/Ohana/src/gastro2/src/gstars2.c	(revision 37807)
@@ -28,9 +28,8 @@
 
   /* default values for coords */
-  strcpy (Target[0].coords.ctype, "DEC--TAN");
+  InitCoords (&Target[0].coords, "DEC--TAN");
   Target[0].coords.pc1_1 = CCD_PC1_1; Target[0].coords.pc1_2 = CCD_PC1_2;
   Target[0].coords.pc2_1 = CCD_PC2_1; Target[0].coords.pc2_2 = CCD_PC2_2;
   Target[0].coords.cdelt1 = Target[0].coords.cdelt2 = ASEC_PIX / 3600.0;
-  Target[0].coords.Npolyterms = 0;
   Target[0].coords.crpix1 = 0.5*NX;
   Target[0].coords.crpix2 = 0.5*NY;
Index: /trunk/Ohana/src/getstar/include/dvoImageOverlaps.h
===================================================================
--- /trunk/Ohana/src/getstar/include/dvoImageOverlaps.h	(revision 37806)
+++ /trunk/Ohana/src/getstar/include/dvoImageOverlaps.h	(revision 37807)
@@ -37,2 +37,5 @@
 int edge_check (double *x1, double *y1, double *x2, double *y2);
 double opening_angle (double x1, double y1, double x2, double y2, double x3, double y3);
+
+void initMosaicCoords ();
+void saveMosaicCoords (Coords *input);
Index: /trunk/Ohana/src/getstar/src/MatchCoords.c
===================================================================
--- /trunk/Ohana/src/getstar/src/MatchCoords.c	(revision 37806)
+++ /trunk/Ohana/src/getstar/src/MatchCoords.c	(revision 37807)
@@ -29,7 +29,4 @@
     if (!WITH_PHU && !strcmp (&dbImages[i].coords.ctype[4], "-DIS")) continue;
     if ( SOLO_PHU &&  strcmp (&dbImages[i].coords.ctype[4], "-DIS")) continue;
-
-    /* if any of these images are WRP images, need to find matching DIS */
-    if (!FindMosaicForImage (dbImages, NdbImages, i)) continue;
 
     /* define image corners */
Index: /trunk/Ohana/src/getstar/src/MatchImages.c
===================================================================
--- /trunk/Ohana/src/getstar/src/MatchImages.c	(revision 37806)
+++ /trunk/Ohana/src/getstar/src/MatchImages.c	(revision 37807)
@@ -23,26 +23,13 @@
   /* we keep the original crpix1,2 and crref1,2 */
   /* for mosaic astrometry, the grid should be w.r.t. the tangent-plane, not chip coords */
+  InitCoords (&tcoords, "DEC--TAN");
   if (!strcmp (&image[0].coords.ctype[4], "-WRP")) {
-    // if the input image is a mosaic, we need to register that mosaic for the calculations below
-    if (MOSAIC == NULL) {
-      fprintf (stderr, "no mosaic for WRP image (use -mosaic)\n");
-      return NULL;
-    }
-    RegisterMosaic (MOSAIC);
-
-    tcoords = MOSAIC[0];
+    myAssert (image[0].coords.mosaic, "should already have tested this");
+    tcoords = image[0].coords.mosaic[0];
     tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-    tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-    tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-    tcoords.Npolyterms = 1;
-    strcpy (tcoords.ctype, "DEC--TAN");
     /* register so image->sky conversions below have correct mosaic */
   } else {
     tcoords = image[0].coords;
     tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-    tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-    tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-    tcoords.Npolyterms = 1;
-    strcpy (tcoords.ctype, "DEC--TAN");
   }
 
@@ -77,7 +64,4 @@
     if (!WITH_PHU && !strcmp (&dbImages[i].coords.ctype[4], "-DIS")) continue;
     if ( SOLO_PHU &&  strcmp (&dbImages[i].coords.ctype[4], "-DIS")) continue;
-
-    /* if any of these images are WRP images, need to find matching DIS */
-    if (!FindMosaicForImage (dbImages, NdbImages, i)) continue;
 
     /* define image corners */
Index: /trunk/Ohana/src/getstar/src/ReadImageFiles.c
===================================================================
--- /trunk/Ohana/src/getstar/src/ReadImageFiles.c	(revision 37806)
+++ /trunk/Ohana/src/getstar/src/ReadImageFiles.c	(revision 37807)
@@ -42,4 +42,6 @@
 
   mode = GetFileMode (header);
+
+  initMosaicCoords ();
 
   /*** load data from a single PHU or a collection of PHU files ***/
Index: /trunk/Ohana/src/getstar/src/ReadImageHeader.c
===================================================================
--- /trunk/Ohana/src/getstar/src/ReadImageHeader.c	(revision 37806)
+++ /trunk/Ohana/src/getstar/src/ReadImageHeader.c	(revision 37807)
@@ -1,3 +1,14 @@
 # include "dvoImageOverlaps.h"
+
+// for DIS/WRP sets, we need to save the DIS set and supply it to the WRP entries
+static Coords *mosaic = NULL;
+
+void initMosaicCoords () {
+  mosaic = NULL;
+}
+
+void saveMosaicCoords (Coords *input) {
+  mosaic = input;
+}
 
 /* read an image header corresponding to a CMF / CMP data block */
@@ -44,5 +55,5 @@
   /* only load astrometry, NAXIS1,2, and time if this is a MOSAIC_PHU (ctype is ....-DIS) */
   if (!strcmp (&image[0].coords.ctype[4], "-DIS")) {
-    MOSAIC = &image[0].coords;
+    saveMosaicCoords (&image[0].coords);
     return (TRUE);
   }
@@ -56,9 +67,9 @@
   }
   if (!strcmp (&image[0].coords.ctype[4], "-WRP")) {
-    if (MOSAIC == NULL) {
+    if (mosaic == NULL) {
       fprintf (stderr, "no mosaic for WRP image (use -mosaic)\n");
       return (FALSE);
     }
-    RegisterMosaic (MOSAIC);
+    image[0].coords.mosaic = mosaic;
   } else {
     /* force image to lie in 0-360 range */
Index: /trunk/Ohana/src/getstar/src/WriteImages.c
===================================================================
--- /trunk/Ohana/src/getstar/src/WriteImages.c	(revision 37806)
+++ /trunk/Ohana/src/getstar/src/WriteImages.c	(revision 37807)
@@ -5,5 +5,5 @@
   
   int i, N;
-  int nWRP, newWRP, isWRP;
+  int isWRP;
   FILE *f;
 
@@ -16,21 +16,16 @@
   }
 
-  nWRP = -1;
+  Image *mosaic = NULL;
   isWRP = FALSE;
   for (i = 0; i < Nmatches; i++) {
-    if (!strcmp (&images[0].coords.ctype[4], "-WRP")) {
-      if (!FindMosaicForImage (images, Nimages, i)) {
-	fprintf (stderr, "failed to find matching mosaic\n");
-	exit (1);
-      }
+    N = matches[i];
+    if (!strcmp (&images[N].coords.ctype[4], "-WRP")) {
       if (isWRP) {
-	newWRP = GetRegisteredMosaic();
-	if (newWRP != nWRP) {
+	if (mosaic != images[N].parent) {
 	  fprintf (stderr, "only one mosaic allowed in an output file\n");
 	  exit (1);
 	}
       } else {
-	nWRP = GetRegisteredMosaic();
-	isWRP = TRUE;
+	mosaic = images[N].parent;
       }
     }      
@@ -38,5 +33,5 @@
 
   if (isWRP) {
-    WriteImageFITS (f, &images[nWRP]);
+    WriteImageFITS (f, mosaic);
   } else {
     // write a blank
Index: /trunk/Ohana/src/imregister/imphot/dumpfits.c
===================================================================
--- /trunk/Ohana/src/imregister/imphot/dumpfits.c	(revision 37806)
+++ /trunk/Ohana/src/imregister/imphot/dumpfits.c	(revision 37807)
@@ -34,5 +34,5 @@
 
   ftable.header = &theader;
-  gfits_table_set_Image (&ftable, subset, Nmatch);
+  // gfits_table_set_Image (&ftable, subset, Nmatch);
 
   gfits_fwrite_header  (f, &header);
Index: /trunk/Ohana/src/imregister/imphot/modify.c
===================================================================
--- /trunk/Ohana/src/imregister/imphot/modify.c	(revision 37806)
+++ /trunk/Ohana/src/imregister/imphot/modify.c	(revision 37807)
@@ -30,5 +30,7 @@
   gfits_vtable_from_ftable (&db[0].ftable, &db[0].vtable, match, Nmatch);
   for (i = 0; i < Nmatch; i++) {
-    gfits_convert_Image ((Image *) db[0].vtable.buffer[i], sizeof (Image), 1);
+    fprintf (stderr, "need to work with real output format\n");
+    exit (2);
+    // gfits_convert_Image ((Image *) db[0].vtable.buffer[i], sizeof (Image), 1);
   }
   gfits_db_update (db);
Index: /trunk/Ohana/src/kapa2/include/constants.h
===================================================================
--- /trunk/Ohana/src/kapa2/include/constants.h	(revision 37806)
+++ /trunk/Ohana/src/kapa2/include/constants.h	(revision 37807)
@@ -10,5 +10,5 @@
  | PointerMotionMask)
 
-# define NCHANNELS 3
+# define NCHANNELS 10
 # define NPIXELS_DYNAMIC 128
 
Index: /trunk/Ohana/src/kapa2/include/structures.h
===================================================================
--- /trunk/Ohana/src/kapa2/include/structures.h	(revision 37806)
+++ /trunk/Ohana/src/kapa2/include/structures.h	(revision 37807)
@@ -217,4 +217,5 @@
   KapaImageChannel *image;
   KapaImageChannel channel[NCHANNELS];
+  int currentChannel;
 } KapaImageWidget;
 
Index: /trunk/Ohana/src/kapa2/src/CreatePicture.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/CreatePicture.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/CreatePicture.c	(revision 37807)
@@ -40,7 +40,12 @@
 
   case 24:
+    
+    extra = 4 - (image[0].wide.dx * 3) % 4;
+    REALLOCATE (image[0].wide.data, char, image[0].wide.dy*(3*image[0].wide.dx+extra));
+
+    extra = 4 - (image[0].zoom.dx * 3) % 4;
+    REALLOCATE (image[0].zoom.data, char, image[0].zoom.dy*(3*image[0].zoom.dx+extra));
+
     extra = 4 - (image[0].picture.dx * 3) % 4;
-    REALLOCATE (image[0].wide.data, char, image[0].wide.dy*(3*image[0].wide.dx+extra));
-    REALLOCATE (image[0].zoom.data, char, image[0].zoom.dy*(3*image[0].zoom.dx+extra));
     REALLOCATE (image[0].picture.data, char, image[0].picture.dy*(3*image[0].picture.dx+extra));
     c = (unsigned char *) image[0].picture.data;
Index: /trunk/Ohana/src/kapa2/src/CursorOps.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/CursorOps.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/CursorOps.c	(revision 37807)
@@ -100,8 +100,12 @@
   // Sx, Sy are the screen coordinates of the Ix,Iy pixel
   Image_to_Picture (&Sx, &Sy, Ix, Iy, picture);
+  
+  // Sx,Sy should be forced into the pixel
+  int Sxi = Sx + 0.5;
+  int Syi = Sy + 0.5;
 
   // i_start, j_start are now limited to valid screen coordinates
-  *i_start = MIN (MAX (Sx, 0), picture[0].dx);
-  *j_start = MIN (MAX (Sy, 0), picture[0].dy);
+  *i_start = MIN (MAX (Sxi, 0), picture[0].dx);
+  *j_start = MIN (MAX (Syi, 0), picture[0].dy);
 }
   
@@ -127,14 +131,19 @@
   Iy = MIN (MAX (Iy, 0), matrix[0].Naxis[1]);
 
-  // I_end, J_end are the last displayed image pixel 
-  // *I_end = Ix;
-  // *J_end = Iy;
-
   // Sx, Sy are the screen coordinates of the Ix,Iy pixel
   Image_to_Picture (&Sx, &Sy, Ix, Iy, picture);
 
+  // Sx,Sy should be forced into the pixel
+  int Sxi = Sx + 0.5;
+  int Syi = Sy + 0.5;
+
+  // double IxTest, IyTest;
+  // Picture_to_Image (&IxTest, &IyTest, Sx, Sy, picture);
+  // fprintf (stderr, "dx,dy: %d,%d : %f,%f : %f,%f : %f:%f\n", picture[0].dx, picture[0].dy, Ix, Iy, Sx, Sy, IxTest, IyTest);
+
   // i_start, j_start are now limited to valid screen coordinates
-  *i_end = MIN (MAX (Sx, 0), picture[0].dx);
-  *j_end = MIN (MAX (Sy, 0), picture[0].dy);
+  // XXX: i_end, j_end *should* be the last valid screen pixel plus 1
+  *i_end = MIN (MAX (Sxi, 0), picture[0].dx);
+  *j_end = MIN (MAX (Syi, 0), picture[0].dy);
 
   // round off error can leave us with a small number of extra pixels here.
Index: /trunk/Ohana/src/kapa2/src/Graphs.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/Graphs.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/Graphs.c	(revision 37807)
@@ -57,12 +57,5 @@
   graph[0].data.flipnorth = FALSE;	// +North = +Y by default
 
-  graph[0].data.coords.pc1_1 = graph[0].data.coords.pc2_2 = 1.0;
-  graph[0].data.coords.pc1_2 = graph[0].data.coords.pc2_1 = 0.0;
-  graph[0].data.coords.crval1 = 0.0;
-  graph[0].data.coords.crval2 = 0.0;
-  graph[0].data.coords.crpix1 = 0.0;
-  graph[0].data.coords.crpix2 = 0.0;
-  graph[0].data.coords.cdelt1 = graph[0].data.coords.cdelt2 = 1.0;
-  strcpy (graph[0].data.coords.ctype, "DEC--LIN");
+  InitCoords (&graph[0].data.coords, "DEC--LIN");
   strcpy (graph[0].data.axis, "2222");
   strcpy (graph[0].data.ticks, "2222");
Index: /trunk/Ohana/src/kapa2/src/Image.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/Image.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/Image.c	(revision 37807)
@@ -11,13 +11,5 @@
   channel->slope = 1;
 
-  channel->coords.pc1_1 = channel->coords.pc2_2 = 1.0;
-  channel->coords.pc1_2 = channel->coords.pc2_1 = 0.0;
-  channel->coords.crval1 = 0.0;
-  channel->coords.crval2 = 0.0;
-  channel->coords.crpix1 = 0.0;
-  channel->coords.crpix2 = 0.0;
-  channel->coords.cdelt1 = channel->coords.cdelt2 = 1.0;
-  strcpy (channel->coords.ctype, "DEC--LIN");
-  channel->coords.Npolyterms = 0;
+  InitCoords (&channel->coords, "DEC--LIN");
 
   channel->matrix.datasize = 0; /* a flag to show there is no data in the matrix */
@@ -42,5 +34,7 @@
     InitImageChannel (&image[0].channel[i]);
   }
-  image[0].image = &image[0].channel[0];
+
+  image[0].currentChannel = 0;
+  image[0].image = &image[0].channel[image[0].currentChannel];
 
   image[0].nPixels = 0;
Index: /trunk/Ohana/src/kapa2/src/InterpretKeys.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/InterpretKeys.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/InterpretKeys.c	(revision 37807)
@@ -90,24 +90,27 @@
   switch (keysym) {
 
-    case XK_F1:
-      image[0].image = &image[0].channel[0];
-      SetColorScale (graphic, image);
-      Remap (graphic, image);
-      Reorient (graphic, image, image[0].picture.Xc, image[0].picture.Yc, 0);
+# define SET_CHANNEL_CASE(NCHAN) \
+    case XK_F##NCHAN: \
+      image[0].currentChannel = NCHAN-1; \
+      image[0].image = &image[0].channel[NCHAN-1]; \
+      SetColorScale (graphic, image); \
+      Reorient (graphic, image, image[0].picture.Xc, image[0].picture.Yc, 0); \
+      Screen_to_Image (&X, &Y, (double)(event[0].x + 0.5), (double)(event[0].y + 0.5), &image[0].picture); \
+      UpdateStatusBox (graphic, image, X, Y, 0.0, 1); \
       break;
 
-    case XK_F2:
-      image[0].image = &image[0].channel[1];
-      SetColorScale (graphic, image);
-      Remap (graphic, image);
-      Reorient (graphic, image, image[0].picture.Xc, image[0].picture.Yc, 0);
-      break;
+//    Remap (graphic, image); 
 
-    case XK_F3:
-      image[0].image = &image[0].channel[2];
-      SetColorScale (graphic, image);
-      Remap (graphic, image);
-      Reorient (graphic, image, image[0].picture.Xc, image[0].picture.Yc, 0);
-      break;
+    // the number of entries here must match the value of NCHANNELS in contants.h
+    SET_CHANNEL_CASE(1);
+    SET_CHANNEL_CASE(2);
+    SET_CHANNEL_CASE(3);
+    SET_CHANNEL_CASE(4);
+    SET_CHANNEL_CASE(5);
+    SET_CHANNEL_CASE(6);
+    SET_CHANNEL_CASE(7);
+    SET_CHANNEL_CASE(8);
+    SET_CHANNEL_CASE(9);
+    SET_CHANNEL_CASE(10);
 
     case XK_KP_Home:
Index: /trunk/Ohana/src/kapa2/src/LoadPicture.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/LoadPicture.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/LoadPicture.c	(revision 37807)
@@ -55,9 +55,11 @@
   // choose expand for wide to guarantee we fit:
   wx = MAX ((header.Naxis[0] / (float) image[0].wide.dx), (header.Naxis[1] / (float) image[0].wide.dy));
-  if (wx > 1.0) {
-    image[0].wide.expand = -wx;
-  } else {
-    image[0].wide.expand = 1.0 / wx;
-  }    
+
+  // -4.002 -> -5
+  // image[0].wide.expand = (wx > 1.0) ? ceil (-wx) : floor (1.0 / wx);
+  image[0].wide.expand = (wx > 1.0) ? floor (-wx) : ceil (1.0 / wx);
+  // fprintf (stderr, "%d : %f\n", image[0].wide.expand, wx);
+  image[0].wide.expand = (wx > 1.0) ? ceil (-wx) : floor (1.0 / wx);
+
   image[0].wide.Xc = 0.5*header.Naxis[0];
   image[0].wide.Yc = 0.5*header.Naxis[1];
Index: /trunk/Ohana/src/kapa2/src/Reconfig.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/Reconfig.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/Reconfig.c	(revision 37807)
@@ -15,5 +15,9 @@
 
   // if the new size is the same as the old size, do nothing.
-  if ((graphic->dx == NX) && (graphic->dy == NY)) return (TRUE);
+  if ((graphic->dx == NX) && (graphic->dy == NY)) {
+    if (USE_XWINDOW) XClearWindow (graphic->display, graphic->window);
+    Refresh ();
+    return (TRUE);
+  }
 
   // set the new window size
Index: /trunk/Ohana/src/kapa2/src/Remap32.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/Remap32.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/Remap32.c	(revision 37807)
@@ -37,5 +37,4 @@
   swap_client = 0;
 # endif  
-
   swap_server = ImageByteOrder (graphic[0].display);
   swap_bytes = !(swap_client ^ swap_server);
@@ -91,8 +90,15 @@
   in_pix  = &image[0].pixmap[DX*(int)MAX(Iy,0) + (int)MAX(Ix,0)];
 
+  unsigned short *in_base = image[0].pixmap;
+  OUT_TYPE *out_base = (OUT_TYPE *) picture[0].data;
+
+  int Ninmax = matrix[0].Naxis[0] * matrix[0].Naxis[1];
+  int Noutmax = picture[0].dx * picture[0].dy;
+
   /********** below we do the mapping from buffer pixels (in) to picture pixels (out) **********/
 
   /**** fill in bottom area ****/
   for (j = 0; j < dx*j_start; j++, out_pix++) {
+    myAssert (out_pix - out_base < Noutmax, "too far out");
     *out_pix = back;
   }
@@ -105,4 +111,5 @@
       out_pix2 = out_pix + jj*dx;
       for (i = 0; i < i_start; i++, out_pix2++) {
+	myAssert (out_pix2 - out_base < Noutmax, "too far out");
 	*out_pix2 = back;
       }
@@ -114,12 +121,16 @@
     if (expand_out == 1) {
       for (i = i_start; i < i_end; i++, in_pix2 += inDX*expand_in, out_pix++) {
+	myAssert (out_pix - out_base < Noutmax, "too far out");
+	myAssert (in_pix2 - in_base < Ninmax, "too far in");
 	*out_pix = pixel[*in_pix2];
       }
     } else {
       for (i = i_start; i < i_end; i+= expand_out, in_pix2 += inDX, out_pix+= expand_out) { 
+	myAssert (in_pix2 - in_base < Ninmax, "too far in");
 	pixvalue = pixel[*in_pix2];
 	out_pix2 = out_pix;
 	for (jj = 0; (jj < expand_out) && (j + jj < dy); jj++, out_pix2+=(dx-expand_out)) {
 	  for (ii = 0; ii < expand_out; ii++, out_pix2++) {
+	    myAssert (out_pix2 - out_base < Noutmax, "too far out");
 	    *out_pix2 = pixvalue;
 	  }
@@ -133,4 +144,5 @@
       out_pix2 = out_pix + jj*dx;
       for (i = i_end; i < dx; i++, out_pix2++) {
+	myAssert (out_pix2 - out_base < Noutmax, "too far out");
 	*out_pix2 = back;
       }
@@ -142,4 +154,5 @@
   for (j = 0; j < dy - j_end; j++) {
     for (i = 0; i < dx; i++, out_pix ++) {
+      myAssert (out_pix - out_base < Noutmax, "too far out");
       *out_pix = back;
     }
Index: /trunk/Ohana/src/kapa2/src/SetChannel.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/SetChannel.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/SetChannel.c	(revision 37807)
@@ -21,5 +21,6 @@
   if (Nchannel >= NCHANNELS) return (TRUE);
   
-  image[0].image = &image[0].channel[Nchannel];
+  image[0].currentChannel = Nchannel;
+  image[0].image = &image[0].channel[image[0].currentChannel];
   SetColorScale (graphic, image);
 
@@ -61,5 +62,5 @@
 
   Refresh ();
-  XFlush (graphic[0].display);
+  if (USE_XWINDOW) XFlush (graphic[0].display);
 
   return (TRUE);
Index: /trunk/Ohana/src/kapa2/src/UpdateStatusBox.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/UpdateStatusBox.c	(revision 37806)
+++ /trunk/Ohana/src/kapa2/src/UpdateStatusBox.c	(revision 37807)
@@ -20,6 +20,6 @@
   
     bzero (line, 100);
-    snprintf (line, 100, "(%d x %d) @ %d                                         ", 
-	     image[0].picture.dx, image[0].picture.dy, image[0].picture.expand); 
+    snprintf (line, 100, "(%d x %d) @ %d   ch: %d                                     ", 
+	      image[0].picture.dx, image[0].picture.dy, image[0].picture.expand, image[0].currentChannel+1); 
     XDrawString (graphic[0].display, graphic[0].window, graphic[0].gc, 
 		 image[0].text_x + PAD1, image[0].text_y + 4*textpad + 4*PAD1, line, 25);
Index: /trunk/Ohana/src/libautocode/Makefile.Targets
===================================================================
--- /trunk/Ohana/src/libautocode/Makefile.Targets	(revision 37806)
+++ /trunk/Ohana/src/libautocode/Makefile.Targets	(revision 37807)
@@ -1,4 +1,5 @@
 AOBJS = \
-$(ASRC)/coords.$(ARCH).o \
+$(ASRC)/coords-disk.$(ARCH).o \
+$(ASRC)/AstromOffsetMap_Disk.$(ARCH).o \
 $(ASRC)/average.$(ARCH).o \
 $(ASRC)/average-loneos.$(ARCH).o \
@@ -13,4 +14,5 @@
 $(ASRC)/average-ps1-v4.$(ARCH).o \
 $(ASRC)/average-ps1-v5.$(ARCH).o \
+$(ASRC)/average-ps1-sim.$(ARCH).o \
 $(ASRC)/average-ps1-ref.$(ARCH).o \
 $(ASRC)/secfilt.$(ARCH).o \
@@ -26,4 +28,5 @@
 $(ASRC)/secfilt-ps1-v4.$(ARCH).o \
 $(ASRC)/secfilt-ps1-v5.$(ARCH).o \
+$(ASRC)/secfilt-ps1-sim.$(ARCH).o \
 $(ASRC)/secfilt-ps1-ref.$(ARCH).o \
 $(ASRC)/measure.$(ARCH).o \
@@ -39,9 +42,14 @@
 $(ASRC)/measure-ps1-v4.$(ARCH).o \
 $(ASRC)/measure-ps1-v5.$(ARCH).o \
+$(ASRC)/measure-ps1-sim.$(ARCH).o \
 $(ASRC)/measure-ps1-ref.$(ARCH).o \
 $(ASRC)/lensing.$(ARCH).o \
 $(ASRC)/lensing-ps1-v5.$(ARCH).o \
+$(ASRC)/lensing-ps1-v5-alt.$(ARCH).o \
 $(ASRC)/lensobj.$(ARCH).o \
 $(ASRC)/lensobj-ps1-v5.$(ARCH).o \
+$(ASRC)/starpar.$(ARCH).o \
+$(ASRC)/starpar-ps1-v5.$(ARCH).o \
+$(ASRC)/starpar-ps1-sim.$(ARCH).o \
 $(ASRC)/missing.$(ARCH).o \
 $(ASRC)/photcode.$(ARCH).o \
@@ -56,5 +64,4 @@
 $(ASRC)/photcode-ps1-v5.$(ARCH).o \
 $(ASRC)/photcode-ps1-ref.$(ARCH).o \
-$(ASRC)/image.$(ARCH).o \
 $(ASRC)/image-loneos.$(ARCH).o \
 $(ASRC)/image-elixir.$(ARCH).o \
@@ -69,4 +76,5 @@
 $(ASRC)/image-ps1-v4.$(ARCH).o \
 $(ASRC)/image-ps1-v5.$(ARCH).o \
+$(ASRC)/image-ps1-sim.$(ARCH).o \
 $(ASRC)/image-ps1-ref.$(ARCH).o \
 $(ASRC)/regimage.$(ARCH).o \
@@ -98,5 +106,6 @@
 
 AINCS = \
-$(AINC)/coords.h \
+$(AINC)/coords-disk.h \
+$(AINC)/AstromOffsetMap_Disk.h \
 $(AINC)/average.h \
 $(AINC)/average-loneos.h \
@@ -111,4 +120,5 @@
 $(AINC)/average-ps1-v4.h \
 $(AINC)/average-ps1-v5.h \
+$(AINC)/average-ps1-sim.h \
 $(AINC)/average-ps1-ref.h \
 $(AINC)/secfilt.h \
@@ -124,4 +134,5 @@
 $(AINC)/secfilt-ps1-v4.h \
 $(AINC)/secfilt-ps1-v5.h \
+$(AINC)/secfilt-ps1-sim.h \
 $(AINC)/secfilt-ps1-ref.h \
 $(AINC)/measure.h \
@@ -137,9 +148,14 @@
 $(AINC)/measure-ps1-v4.h \
 $(AINC)/measure-ps1-v5.h \
+$(AINC)/measure-ps1-sim.h \
 $(AINC)/measure-ps1-ref.h \
 $(AINC)/lensing.h \
 $(AINC)/lensing-ps1-v5.h \
+$(AINC)/lensing-ps1-v5-alt.h \
 $(AINC)/lensobj.h \
 $(AINC)/lensobj-ps1-v5.h \
+$(AINC)/starpar.h \
+$(AINC)/starpar-ps1-v5.h \
+$(AINC)/starpar-ps1-sim.h \
 $(AINC)/missing.h \
 $(AINC)/photcode.h \
@@ -154,5 +170,4 @@
 $(AINC)/photcode-ps1-v5.h \
 $(AINC)/photcode-ps1-ref.h \
-$(AINC)/image.h \
 $(AINC)/image-loneos.h \
 $(AINC)/image-elixir.h \
@@ -167,4 +182,5 @@
 $(AINC)/image-ps1-v4.h \
 $(AINC)/image-ps1-v5.h \
+$(AINC)/image-ps1-sim.h \
 $(AINC)/image-ps1-ref.h \
 $(AINC)/regimage.h \
Index: /trunk/Ohana/src/libautocode/README
===================================================================
--- /trunk/Ohana/src/libautocode/README	(revision 37807)
+++ /trunk/Ohana/src/libautocode/README	(revision 37807)
@@ -0,0 +1,2 @@
+
+doc/autocode.txt gives a description of the autocode process
Index: /trunk/Ohana/src/libautocode/def/AstromOffsetMap_Disk.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/AstromOffsetMap_Disk.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/AstromOffsetMap_Disk.d	(revision 37807)
@@ -0,0 +1,26 @@
+STRUCT  AstromOffsetMap_Disk_6x6
+EXTNAME ASTROM_OFFSET_MAP_DISK_6x6
+TYPE    BINTABLE
+SIZE    312
+
+# we have one row per correction image, with the max correction dimensions hard-wired in the structure
+
+# ID is the ID of this correction image, image ID is the image being corrected
+FIELD   tableID,   TABLE_ID,       unsigned int
+FIELD   imageID,   IMAGE_ID,       unsigned int
+
+# 6x6 is the max size; Nx,Ny give the actual size of the array
+FIELD   Nx,	   NX,             int
+FIELD   Ny,	   NY,             int
+
+# for an image of size (NxBig,NyBig) and a map of size (Nx,Ny), the relationship between
+# real and map pixels is given by
+
+# Ix = ix * (Nx / NxBig)
+# dX = (Nx / NxBig)
+FIELD   dX,	   DX_SCALE,       float
+FIELD   dY,	   DY_SCALE,       float
+
+FIELD   dXv,       DX_VALUE,       float[6][6]
+FIELD   dYv,       DY_VALUE,       float[6][6]
+
Index: /trunk/Ohana/src/libautocode/def/average-ps1-sim.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/average-ps1-sim.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/average-ps1-sim.d	(revision 37807)
@@ -0,0 +1,46 @@
+STRUCT       Average_PS1_SIM
+EXTNAME      DVO_AVERAGE_PS1_SIM
+TYPE         BINTABLE
+SIZE         104
+DESCRIPTION  DVO Average Object Table
+
+# elements of data structure / FITS table
+
+FIELD R,              RA,          double,          RA,                           decimal degrees 
+FIELD D,              DEC,         double,          DEC,                          decimal degrees 
+FIELD dR,             RA_ERR,      float,           RA error,                     arcsec
+FIELD dD,             DEC_ERR,     float,           DEC error,                    arcsec
+				   
+FIELD uR,             U_RA,        float,           RA*cos(D) proper-motion,      arcsec/year
+FIELD uD,             U_DEC,       float,           DEC proper-motion,            arcsec/year
+FIELD duR,            V_RA_ERR,    float,           RA*cos(D) p-m error,          arcsec/year
+FIELD duD,            V_DEC_ERR,   float,           DEC p-m error,                arcsec/year
+FIELD P,              PAR,         float,           parallax,                     arcsec
+FIELD dP,             PAR_ERR,     float,           parallax error,               arcsec
+
+FIELD ChiSqAve,       CHISQ_POS,   float,           astrometry analysis chisq
+FIELD ChiSqPM,        CHISQ_PM,    float,           astrometry analysis chisq
+FIELD ChiSqPar,       CHISQ_PAP,   float,           astrometry analysis chisq
+FIELD Tmean,          MEAN_EPOCH,  int,   	    mean epoch (PM,PAR ref),       unix time seconds
+FIELD Trange,         TIME_RANGE,  int,   	    mean epoch (PM,PAR ref),       unix time seconds
+
+FIELD Npos,           NUMBER_POS,  unsigned short,  number of detections used for astrometry
+FIELD dummy,          DUMMY,       short,           padding
+
+# this limits us to a max of 64k measurements per object
+FIELD Nmeasure,       NMEASURE,    unsigned short,  number of psf measurements
+FIELD Nstarpar,       NSTARPAR,    short,           number of stellar parameter entries
+
+FIELD measureOffset,  OFF_MEASURE, int,             offset to first psf measurement
+FIELD starparOffset,  OFF_STARPAR, int,   	    offset to stellar parameter data
+
+FIELD refColorBlue,   REF_COLOR_BLUE, float,   	    color of astrometry ref stars
+FIELD refColorRed,    REF_COLOR_RED,  float,   	    color of astrometry ref stars
+
+# 'flags' was called 'code' prior to 2009.02.07
+FIELD flags,          FLAGS,       uint32_t,        average object flags (star; ghost; etc)
+
+# objID + catID gives a unique ID for all objects in the database
+FIELD objID,          OBJ_ID,      unsigned int,    unique ID for object in table
+FIELD catID,          CAT_ID,      unsigned int,    unique ID for table in which object was first realized
+
Index: /trunk/Ohana/src/libautocode/def/average-ps1-v5.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/average-ps1-v5.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/average-ps1-v5.d	(revision 37807)
@@ -47,6 +47,6 @@
 FIELD lensingOffset,  OFF_LENSING, int,   	    offset to first lensing obs
 FIELD lensobjOffset,  OFF_LENSOBJ, int,   	    offset to mean lensing data
+FIELD starparOffset,  OFF_STARPAR, int,   	    offset to stellar parameter data
 FIELD extendOffset,   OFF_EXTEND,  int,   	    offset to extended object entry
-FIELD paramsOffset,   OFF_PARAMS,  int,   	    offset to stellar parameter data
 
 FIELD refColorBlue,   REF_COLOR_BLUE, float,   	    color of astrometry ref stars
@@ -57,5 +57,6 @@
 FIELD projectionID,   PROJECTION_ID, short,	    ID of projection for primary skycell
 
-FIELD dummy,          DUMMY,         int,           unassigned bytes
+FIELD Nstarpar,       DUMMY,         short,         number of stellar parameter entries
+FIELD dummy,          DUMMY,         short,         unassigned bytes
 
 # 'flags' was called 'code' prior to 2009.02.07
Index: /trunk/Ohana/src/libautocode/def/average.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/average.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/average.d	(revision 37807)
@@ -47,6 +47,6 @@
 FIELD lensingOffset,  OFF_LENSING, int,   	    offset to first lensing obs
 FIELD lensobjOffset,  OFF_LENSOBJ, int,   	    offset to mean lensing data
+FIELD starparOffset,  OFF_STARPAR, int,   	    offset to stellar parameter data
 FIELD extendOffset,   OFF_EXTEND,  int,   	    offset to extended object entry
-FIELD paramsOffset,   OFF_PARAMS,  int,   	    offset to stellar parameter data
 
 FIELD refColorBlue,   REF_COLOR_BLUE, float,   	    color of astrometry ref stars
@@ -57,5 +57,6 @@
 FIELD projectionID,   PROJECTION_ID, short,	    ID of projection for primary skycell
 
-FIELD dummy,          DUMMY,         int,           unassigned bytes
+FIELD Nstarpar,       DUMMY,         short,         number of stellar parameter entries
+FIELD dummy,          DUMMY,         short,         unassigned bytes
 
 # 'flags' was called 'code' prior to 2009.02.07
Index: /trunk/Ohana/src/libautocode/def/coords-disk.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/coords-disk.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/coords-disk.d	(revision 37807)
@@ -0,0 +1,20 @@
+STRUCT       CoordsDisk
+EXTNAME      COORDS_DISK
+TYPE         BINTABLE
+SIZE         120
+DESCRIPTION  DVO Coordinate Transformation Table as saved to disk
+
+# elements of the Coords structure
+FIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
+FIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
+FIELD  crpix1,           CRPIX1,               float,   	 coordinate of reference pixel
+FIELD  crpix2,           CRPIX2,               float,   	 coordinate of reference pixel
+FIELD  cdelt1,           CDELT1,               float,   	 degrees per pixel
+FIELD  cdelt2,           CDELT2,               float,    	 degrees per pixel
+FIELD  pc1_1,            PC1_1,                float,    	 rotation matrix
+FIELD  pc1_2,            PC1_2,                float,    	 rotation matrix
+FIELD  pc2_1,            PC2_1,                float,    	 rotation matrix
+FIELD  pc2_2,            PC2_2,                float,    	 rotation matrix
+FIELD  polyterms,        POLYTERMS,            float[7][2],	 higher order warping terms
+FIELD  ctype,            CTYPE,                char[15],         coordinate type
+FIELD  Npolyterms,       NPOLYTERMS,           char,     	 order of polynomial
Index: /trunk/Ohana/src/libautocode/def/coords.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/coords.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/coords.d	(revision 37807)
@@ -17,4 +17,5 @@
 FIELD  pc2_2,            PC2_2,                float,    	 rotation matrix
 FIELD  polyterms,        POLYTERMS,            float[7][2],	 higher order warping terms
-FIELD  ctype,            CTYPE,                char[15],      coordinate type
+FIELD  ctype,            CTYPE,                char[15],         coordinate type
 FIELD  Npolyterms,       NPOLYTERMS,           char,     	 order of polynomial
+FIELD  astrom_map_id,    ASTROM_MAP_ID,        unsigned int,   reference to 2D astrometry map
Index: /trunk/Ohana/src/libautocode/def/image-elixir.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-elixir.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-elixir.d	(revision 37807)
@@ -6,5 +6,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-loneos.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-loneos.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-loneos.d	(revision 37807)
@@ -6,5 +6,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-panstarrs-dev-0.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-panstarrs-dev-0.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-panstarrs-dev-0.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-panstarrs-dev-1.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-panstarrs-dev-1.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-panstarrs-dev-1.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-dev-1.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-dev-1.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-dev-1.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-dev-2.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-dev-2.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-dev-2.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-dev-3.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-dev-3.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-dev-3.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-ref.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-ref.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-ref.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-sim.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-sim.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-sim.d	(revision 37807)
@@ -0,0 +1,78 @@
+STRUCT       Image_PS1_SIM
+EXTNAME      DVO_IMAGE_PS1_SIM
+TYPE         BINTABLE
+SIZE         360
+DESCRIPTION  DVO Image Table 
+
+# elements of the image structure
+# careful of 8-byte boundaries
+
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
+SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
+SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
+SUBFIELD  crpix1,           CRPIX1,               float,   	 coordinate of reference pixel
+SUBFIELD  crpix2,           CRPIX2,               float,   	 coordinate of reference pixel
+SUBFIELD  cdelt1,           CDELT1,               float,   	 degrees per pixel
+SUBFIELD  cdelt2,           CDELT2,               float,    	 degrees per pixel
+SUBFIELD  pc1_1,            PC1_1,                float,    	 rotation matrix
+SUBFIELD  pc1_2,            PC1_2,                float,    	 rotation matrix
+SUBFIELD  pc2_1,            PC2_1,                float,    	 rotation matrix
+SUBFIELD  pc2_2,            PC2_2,                float,    	 rotation matrix
+SUBFIELD  polyterms,        POLYTERMS,            float[7][2],	 higher order warping terms
+SUBFIELD  ctype,            CTYPE,                char[15],      coordinate type
+SUBFIELD  Npolyterms,       NPOLYTERMS,           char,     	 order of polynomial
+# 120 bytes
+
+# change this to a double mjd?
+FIELD 	  tzero,            TZERO,                e_time,         readout time (row 0)
+FIELD 	  nstar,            NSTAR,                unsigned int,   number of stars on image
+FIELD 	  secz,             SECZ,                 float,      	  airmass,                   mag
+FIELD 	  NX,               NX,                   unsigned short, image width
+FIELD 	  NY,               NY,                   unsigned short, image height
+FIELD 	  apmifit,          APMIFIT,              float,      	  aperture correction,       mag
+FIELD 	  dapmifit,         DAPMIFIT,             float,      	  apmifit error,             mag
+FIELD 	  Mcal,             MCAL,                 float,      	  calibration mag,           mag
+FIELD 	  dMcal,            DMCAL,                float,      	  error on Mcal,             mag
+FIELD 	  Xm,               XM,                   short,      	  image chisq,               10*log(value)
+FIELD 	  photcode,         PHOTCODE,             short,      	  identifier for CCD,
+FIELD 	  exptime,          EXPTIME,              float,          exposure time,             seconds
+FIELD     sidtime,          ST,			  float,          sidereal time of exposure
+FIELD     latitude,         LAT,		  float,          observatory latitude,      degrees
+# 40 bytes
+
+FIELD     RAo,              RA_CENTER,            float,          image center,              degrees
+FIELD     DECo,             DEC_CENTER,           float,          image center,              degrees
+FIELD     Radius,           RADIUS,               float,          image radius,              degrees
+FIELD     refColorBlue,     REF_COLOR_BLUE,       float,          median astrometry ref color
+FIELD     refColorRed,      REF_COLOR_RED,        float,          median astrometry ref color
+
+# should we define the max length of name as a macro?
+FIELD 	  name,             NAME,                 char[117],      name of original image 
+FIELD 	  detection_limit,  DETECTION_LIMIT,      unsigned char,  detection limit,           10*mag
+FIELD 	  saturation_limit, SATURATION_LIMIT,     unsigned char,  saturation limit,          10*mag
+FIELD 	  cerror,           CERROR,               unsigned char,  astrometric error,         50*arcsec
+FIELD 	  fwhm_x,           FWHM_X,               unsigned char,  PSF x width,               25*arcsec
+FIELD 	  fwhm_y,           FWHM_Y,               unsigned char,  PSF y width,               25*arcsec
+FIELD 	  trate,            TRATE,                unsigned char,  scan rate,                 100 usec/pixel
+FIELD 	  ccdnum,           CCDNUM,               unsigned char,  CCD ID number
+FIELD 	  flags,            FLAGS,                unsigned int,   image quality flags
+FIELD 	  imageID,          IMAGE_ID,             unsigned int,   internal image ID
+FIELD 	  parentID,         PARENT_ID,            unsigned int,   associated ref image
+FIELD 	  externID,         EXTERN_ID,            unsigned int,   external image ID
+FIELD 	  sourceID,         SOURCE_ID,            unsigned short, analysis source ID
+# 48 bytes 
+
+FIELD 	  nLinkAstrom,      NLINK_ASTROM,         short,      	  mean number of matched measurements for astrometry
+FIELD 	  nLinkPhotom,      NLINK_PHOTOM,         short,      	  mean number of matched measurements for astrometry
+FIELD 	  ubercalDist,      UBERCAL_DIST,         short,      	  distance to nearest ubercal image
+
+FIELD 	  dXpixSys,         XPIX_SYS_ERR,         float,      	  systematic astrometry error in X
+FIELD 	  dYpixSys,         YPIX_SYS_ERR,         float,      	  systematic astrometry error in Y
+
+FIELD 	  dMagSys,          MAG_SYS_ERR,          float,      	  systematic photometry error
+FIELD 	  nFitAstrom,       N_FIT_ASTROM,         short,      	  number of stars used for astrometry cal
+FIELD 	  nFitPhotom,       N_FIT_PHOTOM,         short,      	  number of stars used for photometry cal
+
+FIELD 	  photom_map_id,    PHOTOM_MAP_ID,        unsigned int,   reference to 2D zero point map
+FIELD 	  astrom_map_id,    ASTROM_MAP_ID,        unsigned int,   reference to 2D astrometry map
+# nFitPhotom lands on the old location of Mxxxx, which was used to mean nFitPhotom in some cases
Index: /trunk/Ohana/src/libautocode/def/image-ps1-v1.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-v1.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-v1.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-v2.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-v2.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-v2.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-v3.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-v3.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-v3.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-v4.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-v4.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-v4.d	(revision 37807)
@@ -7,5 +7,5 @@
 # elements of the image structure
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image-ps1-v5.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image-ps1-v5.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image-ps1-v5.d	(revision 37807)
@@ -8,5 +8,5 @@
 # careful of 8-byte boundaries
 
-SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
+SUBSTRUCT coords,           COORDS,               CoordsDisk,    astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
 SUBFIELD  crval2,           CRVAL2,               double,  	 coordinate at reference pixel
Index: /trunk/Ohana/src/libautocode/def/image.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/image.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/image.d	(revision 37807)
@@ -2,5 +2,5 @@
 EXTNAME      DVO_IMAGE
 TYPE         BINTABLE
-SIZE         360
+SIZE         384
 DESCRIPTION  DVO Image Table 
 
@@ -8,4 +8,5 @@
 # careful of 8-byte boundaries
 
+# Coords is an internal structure defined in def/common.h
 SUBSTRUCT coords,           COORDS,               Coords,        astrometric data
 SUBFIELD  crval1,           CRVAL1,               double,   	 coordinate at reference pixel
@@ -22,5 +23,7 @@
 SUBFIELD  ctype,            CTYPE,                char[15],      coordinate type
 SUBFIELD  Npolyterms,       NPOLYTERMS,           char,     	 order of polynomial
-# 120 bytes
+SUBFIELD  mosaic,           MOSAIC,               e_void,     	 pointer to parent mosaic
+SUBFIELD  imageMap,         IMAGE_MAP,            e_void,     	 pointer to image map
+# 136 bytes
 
 # change this to a double?
@@ -76,4 +79,6 @@
 FIELD 	  photom_map_id,    PHOTOM_MAP_ID,        unsigned int,   reference to 2D zero point map
 FIELD 	  astrom_map_id,    ASTROM_MAP_ID,        unsigned int,   reference to 2D astrometry map
+
+FIELD    *parent,           PARENT,               e_void,     	  pointer to parent mosaic (not save to disk)
 # nFitPhotom lands on the old location of Mxxxx, which was used to mean nFitPhotom in some cases
 
Index: /trunk/Ohana/src/libautocode/def/lensing-ps1-v5-alt.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/lensing-ps1-v5-alt.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/lensing-ps1-v5-alt.d	(revision 37807)
@@ -0,0 +1,49 @@
+STRUCT       Lensing_PS1_V5_ALT
+EXTNAME      DVO_LENSING_PS1_V5_ALT
+TYPE         BINTABLE
+SIZE         128
+DESCRIPTION  DVO Lensing Table 
+
+FIELD X11_sm_obj,     X11_SM_OBJ,    float,          lensing smear object X11
+FIELD X12_sm_obj,     X12_SM_OBJ,    float,          lensing smear object X12
+FIELD X22_sm_obj,     X22_SM_OBJ,    float,          lensing smear object X22
+FIELD  E1_sm_obj,      E1_SM_OBJ,    float,          lensing smear object E1
+FIELD  E2_sm_obj,      E2_SM_OBJ,    float,          lensing smear object E2
+
+FIELD X11_sh_obj,     X11_SH_OBJ,    float,          lensing shear object X11
+FIELD X12_sh_obj,     X12_SH_OBJ,    float,          lensing shear object X12
+FIELD X22_sh_obj,     X22_SH_OBJ,    float,          lensing shear object X22
+FIELD  E1_sh_obj,      E1_SH_OBJ,    float,          lensing shear object E1
+FIELD  E2_sh_obj,      E2_SH_OBJ,    float,          lensing shear object E2
+
+FIELD X11_sm_psf,     X11_SM_PSF,    float,          lensing smear psf stars X11
+FIELD X12_sm_psf,     X12_SM_PSF,    float,          lensing smear psf stars X12
+FIELD X22_sm_psf,     X22_SM_PSF,    float,          lensing smear psf stars X22
+FIELD  E1_sm_psf,      E1_SM_PSF,    float,          lensing smear psf stars E1
+FIELD  E2_sm_psf,      E2_SM_PSF,    float,          lensing smear psf stars E2
+
+FIELD X11_sh_psf,     X11_SH_PSF,    float,          lensing shear psf stars X11
+FIELD X12_sh_psf,     X12_SH_PSF,    float,          lensing shear psf stars X12
+FIELD X22_sh_psf,     X22_SH_PSF,    float,          lensing shear psf stars X22
+FIELD  E1_sh_psf,      E1_SH_PSF,    float,          lensing shear psf stars E1
+FIELD  E2_sh_psf,      E2_SH_PSF,    float,          lensing shear psf stars E2
+
+FIELD F_ApR5,        FLUX_AP_R5,     float,          Flux inside r = 5
+FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          error on Flux inside r = 5
+FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          stdev of Flux inside r = 5
+FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          fill factor for Flux inside r = 5
+
+FIELD F_ApR6,        FLUX_AP_R6,     float,          Flux inside r = 5
+FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          error on Flux inside r = 5
+FIELD sF_ApR6,       FLUX_STD_AP_R6, float,          stdev of Flux inside r = 5
+FIELD fF_ApR6,       FLUX_FIL_AP_R6, float,          fill factor for Flux inside r = 5
+
+FIELD   detID,          DET_ID,       unsigned int,   detection ID
+FIELD   objID,          OBJ_ID,       unsigned int,   unique ID for object in table
+FIELD   catID,          CAT_ID,       unsigned int,   unique ID for table in which object was first realized
+FIELD  averef,         AVE_REF,       unsigned int,   reference to average entry      
+
+# 28 x float
+# 4 x int
+# = 128 bytes / detection
+# for 2.5G objects + 60 overlaps, expect 15TB for forced warp
Index: /trunk/Ohana/src/libautocode/def/lensing-ps1-v5.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/lensing-ps1-v5.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/lensing-ps1-v5.d	(revision 37807)
@@ -30,12 +30,12 @@
 
 FIELD F_ApR5,        FLUX_AP_R5,     float,          Flux inside r = 5
-FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          Flux inside r = 5
-FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          Flux inside r = 5
-FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          Flux inside r = 5
+FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          error on Flux inside r = 5
+FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          stdev of Flux inside r = 5
+FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          fill factor for Flux inside r = 5
 
 FIELD F_ApR6,        FLUX_AP_R6,     float,          Flux inside r = 5
-FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          Flux inside r = 5
-FIELD sF_ApR6,       FLUX_AP_R6,     float,          Flux inside r = 5
-FIELD fF_ApR6,       FLUX_AP_R6,     float,          Flux inside r = 5
+FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          error on Flux inside r = 5
+FIELD sF_ApR6,       FLUX_STD_AP_R6, float,          stdev of Flux inside r = 5
+FIELD fF_ApR6,       FLUX_FIL_AP_R6, float,          fill factor for Flux inside r = 5
 
 FIELD  detID,          DET_ID,       unsigned int,   detection ID
Index: /trunk/Ohana/src/libautocode/def/lensing.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/lensing.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/lensing.d	(revision 37807)
@@ -29,13 +29,13 @@
 FIELD  E2_sh_psf,      E2_SH_PSF,    float,          lensing shear psf stars E2
 
-FIELD  F_ApR5,       FLUX_AP_R5,     float,          Flux inside r = 5
-FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          Flux inside r = 5
-FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          Flux inside r = 5
-FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          Flux inside r = 5
+FIELD F_ApR5,        FLUX_AP_R5,     float,          Flux inside r = 5
+FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          error on Flux inside r = 5
+FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          stdev of Flux inside r = 5
+FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          fill factor for Flux inside r = 5
 
-FIELD  F_ApR6,       FLUX_AP_R6,     float,          Flux inside r = 6
-FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          Flux inside r = 6
-FIELD sF_ApR6,       FLUX_STD_R6,    float,          Flux inside r = 6
-FIELD fF_ApR6,       FLUX_AP_R6,     float,          Flux inside r = 6
+FIELD F_ApR6,        FLUX_AP_R6,     float,          Flux inside r = 5
+FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          error on Flux inside r = 5
+FIELD sF_ApR6,       FLUX_STD_AP_R6, float,          stdev of Flux inside r = 5
+FIELD fF_ApR6,       FLUX_FIL_AP_R6, float,          fill factor for Flux inside r = 5
 
 FIELD   detID,          DET_ID,      int,   	     detection ID
Index: /trunk/Ohana/src/libautocode/def/lensobj-ps1-v5.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/lensobj-ps1-v5.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/lensobj-ps1-v5.d	(revision 37807)
@@ -30,12 +30,12 @@
 
 FIELD F_ApR5,        FLUX_AP_R5,     float,          Flux inside r = 5
-FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          Flux inside r = 5
-FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          Flux inside r = 5
-FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          Flux inside r = 5
+FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          error on Flux inside r = 5
+FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          stdev of Flux inside r = 5
+FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          fill factor for Flux inside r = 5
 
 FIELD F_ApR6,        FLUX_AP_R6,     float,          Flux inside r = 5
-FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          Flux inside r = 5
-FIELD sF_ApR6,       FLUX_AP_R6,     float,          Flux inside r = 5
-FIELD fF_ApR6,       FLUX_AP_R6,     float,          Flux inside r = 5
+FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          error on Flux inside r = 5
+FIELD sF_ApR6,       FLUX_STD_AP_R6, float,          stdev of Flux inside r = 5
+FIELD fF_ApR6,       FLUX_FIL_AP_R6, float,          fill factor for Flux inside r = 5
 
 FIELD  gamma,            GAMMA,      float
Index: /trunk/Ohana/src/libautocode/def/lensobj.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/lensobj.d	(revision 37806)
+++ /trunk/Ohana/src/libautocode/def/lensobj.d	(revision 37807)
@@ -29,13 +29,13 @@
 FIELD  E2_sh_psf,      E2_SH_PSF,    float,          mean lensing shear psf stars E2
 
-FIELD     F_ApR5,    FLUX_AP_R5,     float,          Flux inside r = 5
-FIELD    dF_ApR5,    FLUX_ERR_AP_R5, float,          Flux inside r = 5
-FIELD    sF_ApR5,    FLUX_STD_AP_R5, float,          Flux inside r = 5
-FIELD    fF_ApR5,    FLUX_FIL_AP_R5, float,          Flux inside r = 5
+FIELD F_ApR5,        FLUX_AP_R5,     float,          Flux inside r = 5
+FIELD dF_ApR5,       FLUX_ERR_AP_R5, float,          error on Flux inside r = 5
+FIELD sF_ApR5,       FLUX_STD_AP_R5, float,          stdev of Flux inside r = 5
+FIELD fF_ApR5,       FLUX_FIL_AP_R5, float,          fill factor for Flux inside r = 5
 
-FIELD     F_ApR6,    FLUX_AP_R6,     float,          Flux inside r = 5
-FIELD    dF_ApR6,    FLUX_ERR_AP_R6, float,          Flux inside r = 5
-FIELD    sF_ApR6,    FLUX_AP_R6,     float,          Flux inside r = 5
-FIELD    fF_ApR6,    FLUX_AP_R6,     float,          Flux inside r = 5
+FIELD F_ApR6,        FLUX_AP_R6,     float,          Flux inside r = 5
+FIELD dF_ApR6,       FLUX_ERR_AP_R6, float,          error on Flux inside r = 5
+FIELD sF_ApR6,       FLUX_STD_AP_R6, float,          stdev of Flux inside r = 5
+FIELD fF_ApR6,       FLUX_FIL_AP_R6, float,          fill factor for Flux inside r = 5
 
 FIELD      gamma,        GAMMA,      float
Index: /trunk/Ohana/src/libautocode/def/measure-ps1-sim.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/measure-ps1-sim.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/measure-ps1-sim.d	(revision 37807)
@@ -0,0 +1,56 @@
+STRUCT       Measure_PS1_SIM
+EXTNAME      DVO_MEASURE_PS1_SIM
+TYPE         BINTABLE
+SIZE         136
+DESCRIPTION  DVO Detection Measurement Table 
+
+FIELD R,              RA,            double,         RA at epoch,                  degrees
+FIELD D,              DEC,           double,         DEC at epoch,                 degrees
+FIELD M,              MAG,           float,          catalog mag,                    mag
+FIELD dM,             MAG_ERR,       float,          mag error,                      mag
+FIELD Mcal,           M_CAL,         float,          image cal mag,                  mag
+FIELD dt,             M_TIME,        float,          exposure time,                  2.5*log(exptime)
+
+# note that with airmass = 1.0 / cos(90 - alt), we have full alt/az representation
+FIELD airmass,        AIRMASS,      float,          (airmass - 1),                  airmass
+FIELD az,             AZ,           float,          telescope azimuth
+
+# new field elements needed for Pan-STARRS:
+FIELD Xccd,           X_CCD,        float,          X coord on chip (raw value),  pixels
+FIELD Yccd,           Y_CCD,        float,          Y coord on chip (raw value),  pixels
+
+FIELD Xfix,           X_FIX,        float,          X coord after correction,     pixels
+FIELD Yfix,           Y_FIX,        float,          Y coord after correction,     pixels
+
+FIELD XoffKH,         X_OFF_KH,      float,          X offset from correction,     pixels
+FIELD YoffKH,         Y_OFF_KH,      float,          Y offset from correction,     pixels
+FIELD XoffDCR,        X_OFF_DCR,     float,          X offset from correction,     pixels
+FIELD YoffDCR,        Y_OFF_DCR,     float,          Y offset from correction,     pixels
+FIELD RoffGAL,        R_OFF_GAL,     float,          RA offset from correction,    arcsec
+FIELD DoffGAL,        D_OFF_GAL,     float,          DEC offset from correction,   arcsec
+
+FIELD t,              TIME,         int,            time in seconds (UNIX)
+FIELD averef,         AVE_REF,      unsigned int,   reference to average entry      
+
+FIELD detID,          DET_ID,       unsigned int,   detection ID
+FIELD objID,          OBJ_ID,       unsigned int,   unique ID for object in table
+FIELD catID,          CAT_ID,       unsigned int,   unique ID for table in which object was first realized
+
+FIELD imageID,        IMAGE_ID,     unsigned int,   reference to DVO image ID
+
+# do we need more resolution than a short? should this be a log?
+FIELD psfQF,          PSF_QF,        float,          psf coverage/quality factor
+FIELD psfQFperf,      PSF_QF_PEFECT, float,          psf coverage / quality factor (all mask bits)
+
+FIELD photcode,       PHOTCODE,     unsigned short, photcode
+FIELD dXccd,          X_CCD_ERR,    short,          X coord error on chip,          1/100 of pixels
+FIELD dYccd,          Y_CCD_ERR,    short,          Y coord error on chip,          1/100 of pixels
+FIELD dRsys,          POS_SYS_ERR,  short,          systematic error from astrom,   1/100 of pixels
+
+FIELD dummy,          PADDING,      short,          padding
+FIELD posangle,       POSANGLE,     short,          position angle sky to chip,     (0xffff/360) deg
+FIELD pltscale,       PLTSCALE,     float,          plate scale,                    arcsec/pixel
+
+FIELD dbFlags,        DB_FLAGS,     unsigned int,   flags supplied by analysis in database
+FIELD photFlags,      PHOT_FLAGS,   unsigned int,   flags supplied by photometry program
+
Index: /trunk/Ohana/src/libautocode/def/secfilt-ps1-sim.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/secfilt-ps1-sim.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/secfilt-ps1-sim.d	(revision 37807)
@@ -0,0 +1,15 @@
+STRUCT       SecFilt_PS1_SIM
+EXTNAME      DVO_SECFILT_PS1_SIM
+TYPE         BINTABLE
+SIZE         16
+DESCRIPTION  DVO SecFilt : Secondary Filter Data 
+
+## *** this section is for per-exposure mean values *** (unlabled values are implicitly PSF values)
+FIELD  M,             MAG,             float,      average mag in this band,              mags
+FIELD  dM,            MAG_ERR,         float,      formal error on average mag,           mags
+
+FIELD  Ncode,         NCODE,           short,      number of detections in band (gpc1 chip only)
+FIELD  Nused,         NUSED,           short,      number of detections used to calculate secfilt.M
+
+FIELD  flags,         FLAGS,           uint32_t,   photometry flags
+
Index: /trunk/Ohana/src/libautocode/def/starpar-ps1-sim.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/starpar-ps1-sim.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/starpar-ps1-sim.d	(revision 37807)
@@ -0,0 +1,26 @@
+STRUCT       StarPar_PS1_SIM
+EXTNAME      DVO_STELLAR_PARAMS_PS1_SIM
+TYPE         BINTABLE
+SIZE         80
+DESCRIPTION  DVO Table of Stellar Properties
+
+FIELD R,              RA,            double,         ra
+FIELD D,              DEC,           double,         dec
+FIELD galLat,         GAL_LAT,       float,          galactic latitude
+FIELD galLon,         GAL_LON,       float,          galactic longitude
+FIELD Ebv,            E_BV,          float,          extinction
+FIELD dEbv,           E_BV_ERR,      float,          extinction error
+FIELD DistMag,        DISTANCE_MOD,     float,       mag
+FIELD dDistMag,       DISTANCE_MOD_ERR, float,       mag
+FIELD M_r,            M_R_ABS,       float,          r-band abs magnitude
+FIELD dM_r,           M_R_ABS_ERR,   float,          r-band abs magnitude error
+FIELD FeH,            F_E_H,         float,          metallicity
+FIELD dFeH,           F_E_H_ERR,     float,          metallicity error
+FIELD uRA,            U_RA,          float,          model guess for proper motion
+FIELD uDEC,           U_DEC,         float,          model guess for proper motion
+FIELD averef,         AVE_REF,       unsigned int,   reference to average entry      
+FIELD objID,          OBJ_ID,        unsigned int,   unique ID for object in table
+FIELD catID,          CAT_ID,        unsigned int,   unique ID for table in which object was first realized
+FIELD dummy,          DUMMY,         unsigned int,   dummy
+
+# this is the list of stellar parameters loaded from Greg Green's tables:
Index: /trunk/Ohana/src/libautocode/def/starpar-ps1-v5.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/starpar-ps1-v5.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/starpar-ps1-v5.d	(revision 37807)
@@ -0,0 +1,24 @@
+STRUCT       StarPar_PS1_V5
+EXTNAME      DVO_STARPAR_PS1_V5
+TYPE         BINTABLE
+SIZE         80
+DESCRIPTION  DVO Table of Stellar Properties
+
+FIELD R,              RA,            double,         ra
+FIELD D,              DEC,           double,         dec
+FIELD galLat,         GAL_LAT,       float,          galactic latitude
+FIELD galLon,         GAL_LON,       float,          galactic longitude
+FIELD Ebv,            E_BV,          float,          extinction
+FIELD dEbv,           E_BV_ERR,      float,          extinction error
+FIELD DistMag,        DISTANCE_MOD,     float,       mag
+FIELD dDistMag,       DISTANCE_MOD_ERR, float,       mag
+FIELD M_r,            M_R_ABS,       float,          r-band abs magnitude
+FIELD dM_r,           M_R_ABS_ERR,   float,          r-band abs magnitude error
+FIELD FeH,            F_E_H,         float,          metallicity
+FIELD dFeH,           F_E_H_ERR,     float,          metallicity error
+FIELD uRA,            U_RA,          float,          model guess for proper motion
+FIELD uDEC,           U_DEC,         float,          model guess for proper motion
+FIELD averef,         AVE_REF,       unsigned int,   reference to average entry      
+FIELD objID,          OBJ_ID,        unsigned int,   unique ID for object in table
+FIELD catID,          CAT_ID,        unsigned int,   unique ID for table in which object was first realized
+FIELD dummy,          DUMMY,         unsigned int,   dummy
Index: /trunk/Ohana/src/libautocode/def/starpar.d
===================================================================
--- /trunk/Ohana/src/libautocode/def/starpar.d	(revision 37807)
+++ /trunk/Ohana/src/libautocode/def/starpar.d	(revision 37807)
@@ -0,0 +1,26 @@
+STRUCT       StarPar
+EXTNAME      DVO_STELLAR_PARAMS
+TYPE         BINTABLE
+SIZE         80
+DESCRIPTION  DVO Table of Stellar Properties
+
+FIELD R,              RA,            double,         ra
+FIELD D,              DEC,           double,         dec
+FIELD galLat,         GAL_LAT,       float,          galactic latitude
+FIELD galLon,         GAL_LON,       float,          galactic longitude
+FIELD Ebv,            E_BV,          float,          extinction
+FIELD dEbv,           E_BV_ERR,      float,          extinction error
+FIELD DistMag,        DISTANCE_MOD,     float,       mag
+FIELD dDistMag,       DISTANCE_MOD_ERR, float,       mag
+FIELD M_r,            M_R_ABS,       float,          r-band abs magnitude
+FIELD dM_r,           M_R_ABS_ERR,   float,          r-band abs magnitude error
+FIELD FeH,            F_E_H,         float,          metallicity
+FIELD dFeH,           F_E_H_ERR,     float,          metallicity error
+FIELD uRA,            U_RA,          float,          model guess for proper motion
+FIELD uDEC,           U_DEC,         float,          model guess for proper motion
+FIELD averef,         AVE_REF,       unsigned int,   reference to average entry      
+FIELD objID,          OBJ_ID,        unsigned int,   unique ID for object in table
+FIELD catID,          CAT_ID,        unsigned int,   unique ID for table in which object was first realized
+FIELD dummy,          DUMMY,         unsigned int,   dummy
+
+# this is the list of stellar parameters loaded from Greg Green's tables:
Index: /trunk/Ohana/src/libautocode/doc/autocode.txt
===================================================================
--- /trunk/Ohana/src/libautocode/doc/autocode.txt	(revision 37806)
+++ /trunk/Ohana/src/libautocode/doc/autocode.txt	(revision 37807)
@@ -35,7 +35,20 @@
 
 - the element name
+- the element FITS field (in FITS table)
 - the element data type
 - the element description
 - the element physical unit
+
+SUBFIELD / SUBSTRUCT keywords allows the user to define elements of a
+structure which are themselves structures.  These fields are used as
+follow:
+
+Binary / ASCII FITS tables write out the SUBFIELD entries as if they
+were regular table rows (substructure distinction not visible in FITS
+table).  SUBSTRUCT rows are NOT added to the FITS table.
+
+In a C-structure, the SUBFIELD rows are skipped and only the SUBSTRUCT
+row is saved.  The definition of the substructure is assumed to be
+defined elsewhere in the C code.
 
 Template File
@@ -43,11 +56,13 @@
 The template file is a source code in whatever language is desired.
 Within the template, the defined keywords may be used as desired,
-wherever needed.  In addition, there are special directives which
-invoke additional special behavior.  All special directives have the
-form of comments within their target language.  They are replaced with
-a block of code in that target language, normally one which iterates
-over the FIELD elements of the schema file.  As such, the directive
-appropriate for one language should not be used within code for a
-different language.  Here is a list of existing special directives:
+wherever needed.  
+
+In addition, there are special directives which invoke additional
+special behavior.  All special directives have the form of comments
+within their target language.  They are replaced with a block of code
+in that target language, normally one which iterates over the FIELD
+elements of the schema file.  As such, the directive appropriate for
+one language should not be used within code for a different language.
+Here is a list of existing special directives:
 
 /** STRUCT DEFINITION **/
Index: /trunk/Ohana/src/libautocode/generate
===================================================================
--- /trunk/Ohana/src/libautocode/generate	(revision 37806)
+++ /trunk/Ohana/src/libautocode/generate	(revision 37807)
@@ -144,4 +144,5 @@
 	if ($type eq "e_time") 	      { $pt1 = "J"; }
 	if ($type eq "e_void") 	      { $pt1 = "B"; $Np = 8*$Np; }
+	# if ($type eq "Image") 	      { $pt1 = "B"; $Np = 8*$Np; }
 	# e_void is a 64 bit pointer, cast to size_t.  its value is not loaded
 	# from the table.
@@ -248,4 +249,5 @@
 	if ($type eq "e_time") 	      { $pt1 = sprintf "I%s", $length; }
 	if ($type eq "e_void") 	      { $pt1 = sprintf "I%s", $length; }
+	# if ($type eq "Image") 	      { $pt1 = sprintf "I%s", $length; }
 
 	if (!$pt1) { die "unknown type $type"; }
@@ -331,4 +333,5 @@
 	if ($type eq "e_time") 	      { $T = "WORD"; $n = 4; }
 	if ($type eq "e_void") 	      { $T = "DBLE"; $n = 8; }
+	# if ($type eq "Image") 	      { $T = "DBLE"; $n = 8; }
 
 	if (!$n) { die "unknown type $type"; }
@@ -375,4 +378,5 @@
 	if ($type eq "e_time") 	       { $Nbytes += 4*$Np; $valid = 1; }
 	if ($type eq "e_void") 	       { $Nbytes += 8*$Np; $valid = 1; }
+	# if ($type eq "Image") 	       { $Nbytes += 8*$Np; $valid = 1; }
 	if (!$valid) { die "unknown type $type"; }
     }
Index: /trunk/Ohana/src/libdvo/Makefile
===================================================================
--- /trunk/Ohana/src/libdvo/Makefile	(revision 37806)
+++ /trunk/Ohana/src/libdvo/Makefile	(revision 37807)
@@ -38,4 +38,5 @@
 $(DESTINC)/ps1_v5_defs.h \
 $(DESTINC)/ps1_ref_defs.h \
+$(DESTINC)/ps1_sim_defs.h \
 $(DESTINC)/cmf-ps1-dv3.h \
 $(DESTINC)/cmf-ps1-v5.h \
@@ -50,4 +51,5 @@
 $(SRC)/version.$(ARCH).o	 \
 $(SRC)/coordops.$(ARCH).o	 \
+$(SRC)/galaxy_model.$(ARCH).o	 \
 $(SRC)/dvosorts.$(ARCH).o	 \
 $(SRC)/dvo_photcode_ops.$(ARCH).o \
@@ -84,8 +86,12 @@
 $(SRC)/dvo_convert_PS1_V5.$(ARCH).o \
 $(SRC)/dvo_convert_PS1_REF.$(ARCH).o \
+$(SRC)/dvo_convert_PS1_SIM.$(ARCH).o \
 $(SRC)/flatcorr_io.$(ARCH).o    \
 $(SRC)/skyregion_io.$(ARCH).o    \
 $(SRC)/skyregion_gsc.$(ARCH).o    \
 $(SRC)/skyregion_ops.$(ARCH).o \
+$(SRC)/AstromOffsetMapIO.$(ARCH).o    \
+$(SRC)/AstromOffsetMapOps.$(ARCH).o \
+$(SRC)/AstromOffsetMapUtils.$(ARCH).o \
 $(SRC)/dvo_set_skyregion.$(ARCH).o \
 $(SRC)/dvo_set_catdir.$(ARCH).o \
Index: /trunk/Ohana/src/libdvo/include/dvo.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/dvo.h	(revision 37806)
+++ /trunk/Ohana/src/libdvo/include/dvo.h	(revision 37807)
@@ -24,4 +24,5 @@
   DVO_FORMAT_PS1_DEV_3,
   DVO_FORMAT_PS1_REF,
+  DVO_FORMAT_PS1_SIM,
   DVO_FORMAT_PS1_V1,
   DVO_FORMAT_PS1_V2,
@@ -36,29 +37,4 @@
 
 typedef enum {DVO_CAT_OPEN_FAIL, DVO_CAT_OPEN_OK, DVO_CAT_OPEN_EMPTY} DVOCatalogOpenModes;
-
-typedef enum {
-  PROJ_NONE, // undefined
-  PROJ_ZEA, // zenithal
-  PROJ_ZPL, // zenithal
-  PROJ_ARC, // zenithal
-  PROJ_STG, // zenithal
-  PROJ_SIN, // zenithal
-  PROJ_TAN, // zenithal
-  PROJ_TNX, // zenithal
-  PROJ_DIS, // zenithal (TAN + polyterms)
-  PROJ_LIN, // cartesian
-  PROJ_PLY, // cartesian
-  PROJ_WRP, // cartesian
-  PROJ_AIT, // pseudocyl
-  PROJ_GLS, // pseudocyl
-  PROJ_PAR, // pseudocyl
-} OhanaProjection;
-
-typedef enum {
-  PROJ_MODE_NONE,
-  PROJ_MODE_CARTESIAN,
-  PROJ_MODE_ZENITHAL,
-  PROJ_MODE_PSEUDOCYL,
-} OhanaProjectionMode;
 
 // these are used as NAN for types of int values
@@ -79,17 +55,19 @@
 
 /* catalog values to be loaded */
-# define LOAD_NONE 	0x000
-# define LOAD_AVES 	0x001
-# define LOAD_MEAS 	0x002
-# define LOAD_MISS 	0x004
-# define LOAD_SECF 	0x008 
-# define SKIP_AVES 	0x010
-# define SKIP_MEAS 	0x020
-# define SKIP_MISS 	0x040
-# define SKIP_SECF 	0x080
-# define LOAD_LENSING 	0x100
-# define LOAD_LENSOBJ 	0x200
-# define SKIP_LENSING 	0x400
-# define SKIP_LENSOBJ 	0x800
+# define LOAD_NONE 	0x0000
+# define LOAD_AVES 	0x0001
+# define LOAD_MEAS 	0x0002
+# define LOAD_MISS 	0x0004
+# define LOAD_SECF 	0x0008 
+# define SKIP_AVES 	0x0010
+# define SKIP_MEAS 	0x0020
+# define SKIP_MISS 	0x0040
+# define SKIP_SECF 	0x0080
+# define LOAD_LENSING 	0x0100
+# define LOAD_LENSOBJ 	0x0200
+# define SKIP_LENSING 	0x0400
+# define SKIP_LENSOBJ 	0x0800
+# define LOAD_STARPAR 	0x1000
+# define SKIP_STARPAR 	0x2000
 
 /* photometry code types */
@@ -114,4 +92,5 @@
   ID_IMAGE_ASTROM_FEW     = 0x00000100,  /* currently too few measurements for astrometry */
   ID_IMAGE_PHOTOM_UBERCAL = 0x00000200,  /* externally-supplied photometry zero point from ubercal analysis */
+  ID_IMAGE_ASTROM_GMM     = 0x00000400,  /* image was fitted to positions corrected by the galaxy motion model */
 } DVOImageFlags;
 
@@ -136,4 +115,6 @@
   ID_MEAS_STACK_PRIMARY  = 0x00010000,  // this stack measurement is in the primary skycell
   ID_MEAS_STACK_PHOT_SRC = 0x00020000,  // this measurement supplied the stack photometry
+  ID_MEAS_ICRF_QSO       = 0x00040000,  // this measurement is an ICRF reference position
+  ID_MEAS_IMAGE_EPOCH    = 0x00080000,  // this measurement is registered to the image epoch (not tied to ref catalog epoch)
 } DVOMeasureFlags;
 
@@ -152,34 +133,40 @@
 */
 
+/** these names were previously used for Average flags in old (LONEOS-era) dvo versions
+  ID_BAD_OBJECT        = 0x00004000, // if all measurements are bad, set this bit
+  ID_MOVING            = 0x00008000, // is a moving object
+  ID_ROCK              = 0x0000a000, // 0x8000 + 0x2000
+  ID_GHOST             = 0x0000c001, // 0x8000 + 0x4000 + 0x0001
+  ID_TRAIL             = 0x0000c002, // 0x8000 + 0x4000 + 0x0002
+  ID_BLEED             = 0x0000c003, // 0x8000 + 0x4000 + 0x0003
+  ID_COSMIC            = 0x0000c004, // 0x8000 + 0x4000 + 0x0004
+**/
+
 /* Average.flags values -- these values are 32 bit (as of PS1_V1) */
 typedef enum {
-  ID_STAR_FEW       = 0x00000001, // used within relphot: skip star
-  ID_STAR_POOR      = 0x00000002, // used within relphot: skip star
-  ID_PROPER         = 0x00000400, // star with large proper motion
-  ID_TRANSIENT      = 0x00001000, // is this mutually exclusive with USNO?
-  ID_VARIABLE       = 0x00002000, // not currently set?
-  ID_ASTEROID       = 0x00002000, // identified with an asteroid
-  ID_BAD_OBJECT     = 0x00004000, // if all measurements are bad, set this bit
-  ID_MOVING         = 0x00008000, // is a moving object
-  ID_ROCK           = 0x0000a000, // 0x8000 + 0x2000
-  ID_GHOST          = 0x0000c001, // 0x8000 + 0x4000 + 0x0001
-  ID_TRAIL          = 0x0000c002, // 0x8000 + 0x4000 + 0x0002
-  ID_BLEED          = 0x0000c003, // 0x8000 + 0x4000 + 0x0003
-  ID_COSMIC         = 0x0000c004, // 0x8000 + 0x4000 + 0x0004
-  ID_STAR_FIT_AVE   = 0x00010000, // average position fitted
-  ID_STAR_FIT_PM    = 0x00020000, // proper motion fitted
-  ID_STAR_FIT_PAR   = 0x00040000, // parallax fitted
-  ID_STAR_USE_AVE   = 0x00080000, // average position used (not PM or PAR)
-  ID_STAR_USE_PM    = 0x00100000, // proper motion used (not AVE or PAR)
-  ID_STAR_USE_PAR   = 0x00200000, // parallax used (not AVE or PM)
-  ID_STAR_NO_ASTROM = 0x00400000, // mean astrometry could not be measured
-  ID_STAR_BAD_PM    = 0x00800000, // mean astrometry could not be measured
-  ID_OBJ_EXT        = 0x01000000, // extended in our data (eg, PS)
-  ID_OBJ_EXT_ALT    = 0x02000000, // extended in external data (eg, 2MASS)
-  ID_OBJ_GOOD       = 0x04000000, // good-quality measurement in our data (eg,PS)
-  ID_OBJ_GOOD_ALT   = 0x08000000, // good-quality measurement in  external data (eg, 2MASS)
-  ID_OBJ_GOOD_STACK = 0x10000000, // good-quality object in the stack (> 1 good stack)
+  ID_STAR_FEW          = 0x00000001, // used within relphot: skip star
+  ID_STAR_POOR         = 0x00000002, // used within relphot: skip star
+  ID_ICRF_QSO          = 0x00000004, // object IDed with known ICRF quasar (may have ICRF position measurement)
+  ID_OTHEF_QSO         = 0x00000008, // object IDed with known or suspected quasar, without ICRF reference data
+  ID_PROPER            = 0x00000010, // star with large proper motion
+  ID_TRANSIENT         = 0x00000020, // identified as a non-periodic (stationary) transient
+  ID_VARIABLE          = 0x00000040, // identified as a period variable
+  ID_ASTEROID          = 0x00000080, // identified with a known solar-system object (asteroid or other)
+  // bits 0x00000100 - 0x00008000 are currently unused
+  ID_STAR_FIT_AVE      = 0x00010000, // average position fitted
+  ID_STAR_FIT_PM       = 0x00020000, // proper motion fitted
+  ID_STAR_FIT_PAR      = 0x00040000, // parallax fitted
+  ID_STAR_USE_AVE      = 0x00080000, // average position used (not PM or PAR)
+  ID_STAR_USE_PM       = 0x00100000, // proper motion used (not AVE or PAR)
+  ID_STAR_USE_PAR      = 0x00200000, // parallax used (not AVE or PM)
+  ID_STAR_NO_ASTROM    = 0x00400000, // mean astrometry could not be measured
+  ID_STAR_BAD_PM       = 0x00800000, // mean astrometry could not be measured
+  ID_OBJ_EXT           = 0x01000000, // extended in our data (eg, PS)
+  ID_OBJ_EXT_ALT       = 0x02000000, // extended in external data (eg, 2MASS)
+  ID_OBJ_GOOD          = 0x04000000, // good-quality measurement in our data (eg,PS)
+  ID_OBJ_GOOD_ALT      = 0x08000000, // good-quality measurement in  external data (eg, 2MASS)
+  ID_OBJ_GOOD_STACK    = 0x10000000, // good-quality object in the stack (> 1 good stack)
   ID_OBJ_SUSPECT_STACK = 0x20000000, // suspect object in the stack (> 1 good or suspect stack, < 2 good)
-  ID_OBJ_BAD_STACK  = 0x40000000, // good-quality object in the stack (> 1 good stack)
+  ID_OBJ_BAD_STACK     = 0x40000000, // good-quality object in the stack (> 1 good stack)
 } DVOAverageFlags;
 
@@ -202,4 +189,22 @@
 } DVOSecfiltFlags;
 
+/* definitions for parallel dvo host information 
+   XXX : need better names (safer namespace)
+*/
+
+typedef enum {
+  DATA_ON_TGT  = 0x01,
+  DATA_ON_BCK  = 0x02,
+  DATA_USE_BCK = 0x04,
+
+  DATA_COPY_FAILURE = 0x80,
+} SkyTableDataFlags;
+
+typedef enum {
+  HOST_STDIN = 0,
+  HOST_STDOUT = 1,
+  HOST_STDERR = 2,
+} HostInfoIOfd;
+
 /*** general dvo structures (internal use only / not IO) ***/
 
@@ -253,22 +258,4 @@
 } FlatCorrectionTable;
 
-/* definitions for parallel dvo host information 
-   XXX : need better names (safer namespace)
-*/
-
-typedef enum {
-  DATA_ON_TGT  = 0x01,
-  DATA_ON_BCK  = 0x02,
-  DATA_USE_BCK = 0x04,
-
-  DATA_COPY_FAILURE = 0x80,
-} SkyTableDataFlags;
-
-typedef enum {
-  HOST_STDIN = 0,
-  HOST_STDOUT = 1,
-  HOST_STDERR = 2,
-} HostInfoIOfd;
-
 typedef struct {
   char *hostname;	      // name of remote machine
@@ -289,4 +276,9 @@
 } HostTable;
 
+typedef struct {
+  int Nhosts;
+  HostInfo **hosts;
+} HostTableGroup;
+
 // A RegionHost processes data for some region in parallel with other regions
 typedef struct RegionHostInfo {
@@ -314,4 +306,6 @@
   Image *image;
   off_t *imseq;
+
+  AstromOffsetTable *astromTable;
 
   int *neighbors;	      // list of neighbor index values
@@ -444,4 +438,6 @@
   double         R;
   double         D;
+  float          RoffGAL;
+  float          DoffGAL;
   float          M;
   float          Mcal;
@@ -645,5 +641,5 @@
   int              lensobjOffset;        // offset to mean lensing data
   int              extendOffset;         // offset to extended object entry
-  int              paramsOffset;         // offset to stellar parameter data
+  int              starparOffset;        // offset to stellar parameter data
   float            refColorBlue;         // color of astrometry ref stars
   float            refColorRed;          // color of astrometry ref stars
@@ -679,9 +675,10 @@
   Lensing *lensing;
   Lensobj *lensobj;
+  StarPar *starpar;
 
   int Nsecfilt;  /* number of secfilt entries for each average entry */
-  off_t Naverage,   Nmeasure,   Nmissing,   Nlensing,      Nlensobj,      Nsecf_mem;  /* current number of each component in memory */
-  off_t Naves_disk, Nmeas_disk, Nmiss_disk, Nlensing_disk, Nlensobj_disk, Nsecf_disk; /* current number of each component on disk */
-  off_t Naves_off,  Nmeas_off,  Nmiss_off,  Nlensing_off,  Nlensobj_off,  Nsecf_off;  /* index of first loaded data value */
+  off_t Naverage,   Nmeasure,   Nmissing,   Nlensing,      Nlensobj,      Nstarpar,      Nsecf_mem;  /* current number of each component in memory */
+  off_t Naves_disk, Nmeas_disk, Nmiss_disk, Nlensing_disk, Nlensobj_disk, Nstarpar_disk, Nsecf_disk; /* current number of each component on disk */
+  off_t Naves_off,  Nmeas_off,  Nmiss_off,  Nlensing_off,  Nlensobj_off,  Nstarpar_off,  Nsecf_off;  /* index of first loaded data value */
 
   // note that we use these for the full-sky relphot analysis
@@ -704,4 +701,5 @@
   struct Catalog *lensing_catalog;		/* lensing catalog data (split) */
   struct Catalog *lensobj_catalog;		/* lensobj catalog data (split) */
+  struct Catalog *starpar_catalog;		/* starpar catalog data (split) */
 
   unsigned int objID;
@@ -739,21 +737,4 @@
 int   gfits_db_close               PROTO((FITS_DB *db));
 int   gfits_db_free                PROTO((FITS_DB *db));
-
-/* in coords.c, using libautocode/def/coords.d */
-int  XY_to_LM (double *L, double *M, double x,  double y,   Coords *coords);
-int  LM_to_XY (double *x,  double *y,   double L, double M, Coords *coords);
-int  RD_to_LM (double *L, double *M, double ra,  double dec,   Coords *coords);
-int  LM_to_RD (double *ra, double *dec,   double L, double M, Coords *coords);
-int  XY_to_RD (double *ra, double *dec, double x,  double y,   Coords *coords);
-int  RD_to_XY (double *x,  double *y,   double ra, double dec, Coords *coords);
-int  fXY_to_RD (float *ra, float *dec, double x,  double y,   Coords *coords);
-int  fRD_to_XY (float *x,  float *y,   double ra, double dec, Coords *coords);
-int  GetCoords (Coords *coords, Header *header);
-int  PutCoords (Coords *coords, Header *header);
-void RegisterMosaic (Coords *coords);
-void coords_precess (double *ra, double *dec, double in_epoch, double out_epoch);
-OhanaProjection GetProjection (char *ctype);
-int SetProjection (char *ctype, OhanaProjection proj);
-OhanaProjectionMode GetProjectionMode (OhanaProjection proj);
 
 char *libdvo_version (void);
@@ -913,4 +894,5 @@
 Lensing *FtableToLensing (FTable *ftable, off_t *Nlensing, char *format);
 Lensobj *FtableToLensobj (FTable *ftable, off_t *Nlensobj, char *format);
+StarPar *FtableToStarPar (FTable *ftable, off_t *Nstarpar, char *format);
 int      FtableToImage   (FTable *ftable, Header *theader, char *format);
 
@@ -920,4 +902,5 @@
 int LensingToFtable (FTable *ftable, Lensing *lensing, off_t Nlensing, char format);
 int LensobjToFtable (FTable *ftable, Lensobj *lensobj, off_t Nlensobj, char format);
+int StarParToFtable (FTable *ftable, StarPar *starpar, off_t Nstarpar, char format);
 int ImageToFtable (FTable *ftable, Header *theader, char format);
 int ImageToVtable (VTable *vtable, Header *theader, char format);
@@ -936,4 +919,5 @@
 # include "ps1_v5_defs.h"
 # include "ps1_ref_defs.h"
+# include "ps1_sim_defs.h"
 
 /*** DVO image db I/O Functions ***/
@@ -950,4 +934,8 @@
 void dvo_image_create (FITS_DB *db, double ZeroPoint);
 
+int gfits_table_set_Image (FTable *ftable);
+int gfits_table_mkheader_Image (Header *header);
+Image *gfits_table_get_Image (FTable *ftable, off_t *Ndata, char *swapped);
+
 /* flatcorr APIs */
 FlatCorrectionTable *FlatCorrectionLoad (char *filename, int VERBOSE);
@@ -963,4 +951,5 @@
 SkyTable  *SkyTableLoadOptimal 	   PROTO((char *catdir, char *SKYFILE, char *GSCFILE, int readwrite, int depth, int VERBOSE));
 int        SkyTableSetDepth    	   PROTO((SkyTable *sky, int depth));
+SkyList   *SkyRegionByIndex        PROTO((SkyTable *table, int index));
 SkyList   *SkyRegionByCPT          PROTO((SkyTable *table, char *filename));
 SkyList   *SkyRegionByPoint    	   PROTO((SkyTable *table, int depth, double ra, double dec));
@@ -1009,4 +998,9 @@
 int HostTableTestHost (SkyRegion *region, int hostID);
 
+void InitHost (HostInfo *host);
+
+HostTableGroup *HostTableGroups (HostTable *table, int *ngroups);
+int HostTableGroupWaitJobsGetIO (HostTableGroup *table, char *file, int lineno, int VERBOSE);
+
 // functions to support tiny versions of Average and Measure
 void CopyAverageToTiny (AverageTiny *averageT, Average *average);
@@ -1042,4 +1036,5 @@
 void dvo_lensing_init (Lensing *lensing);
 void dvo_lensobj_init (Lensobj *lensobj, int toZero);
+void dvo_starpar_init (StarPar *starpar);
 
 void InitRegionHosts (RegionHostInfo *hosts, int Nhosts, int NHOSTS);
@@ -1051,3 +1046,40 @@
 int RegionHostFindNeighbors (RegionHostTable *table, int Nhost);
 
+// galaxy_model:
+int TransformProperMotion_radians (double *uR, double *uD, double uL, double uB, double Rrad, double Drad, CoordTransform *transform);
+int TransformProperMotion (double *uR, double *uD, double uL, double uB, double R, double D, CoordTransform *transform);
+int SolarMotionModel_radians (double *uL_sol, double *uB_sol, double Lrad, double Brad, double distance);
+int SolarMotionModel (double *uL_sol, double *uB_sol, double L, double B, double distance);
+int GalaxyMotionModel_radians (double *uL_gal, double *uB_gal, double Lrad, double Brad);
+int GalaxyMotionModel (double *uL_gal, double *uB_gal, double L, double B);
+int InitGalaxyModel (char *version);
+
+# define LENSFIELD(NAME) float LensValue_##NAME (PhotCode *code, Lensobj *lensobj);
+
+LENSFIELD(X11_sm_obj)
+LENSFIELD(X12_sm_obj)
+LENSFIELD(X22_sm_obj)
+LENSFIELD(E1_sm_obj)
+LENSFIELD(E2_sm_obj)
+
+LENSFIELD(X11_sh_obj)
+LENSFIELD(X12_sh_obj)
+LENSFIELD(X22_sh_obj)
+LENSFIELD(E1_sh_obj)
+LENSFIELD(E2_sh_obj)
+
+LENSFIELD(X11_sm_psf)
+LENSFIELD(X12_sm_psf)
+LENSFIELD(X22_sm_psf)
+LENSFIELD(E1_sm_psf)
+LENSFIELD(E2_sm_psf)
+
+LENSFIELD(X11_sh_psf)
+LENSFIELD(X12_sh_psf)
+LENSFIELD(X22_sh_psf)
+LENSFIELD(E1_sh_psf)
+LENSFIELD(E2_sh_psf)
+
+# undef LENSFIELD
+
 # endif // DVO_H
Index: /trunk/Ohana/src/libdvo/include/dvodb.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/dvodb.h	(revision 37806)
+++ /trunk/Ohana/src/libdvo/include/dvodb.h	(revision 37807)
@@ -55,4 +55,38 @@
   MAG_OPTION_UC_DIST, 
   MAG_OPTION_FLAGS, 
+
+  MAG_OPTION_X11_SM_OBJ, 
+  MAG_OPTION_X12_SM_OBJ, 
+  MAG_OPTION_X22_SM_OBJ, 
+  MAG_OPTION_E1_SM_OBJ, 
+  MAG_OPTION_E2_SM_OBJ, 
+
+  MAG_OPTION_X11_SH_OBJ, 
+  MAG_OPTION_X12_SH_OBJ, 
+  MAG_OPTION_X22_SH_OBJ, 
+  MAG_OPTION_E1_SH_OBJ, 
+  MAG_OPTION_E2_SH_OBJ, 
+
+  MAG_OPTION_X11_SM_PSF, 
+  MAG_OPTION_X12_SM_PSF, 
+  MAG_OPTION_X22_SM_PSF, 
+  MAG_OPTION_E1_SM_PSF, 
+  MAG_OPTION_E2_SM_PSF, 
+
+  MAG_OPTION_X11_SH_PSF, 
+  MAG_OPTION_X12_SH_PSF, 
+  MAG_OPTION_X22_SH_PSF, 
+  MAG_OPTION_E1_SH_PSF, 
+  MAG_OPTION_E2_SH_PSF, 
+
+  MAG_OPTION_F_AP_R5, 
+  MAG_OPTION_F_ERR_AP_R5, 
+  MAG_OPTION_F_STDEV_AP_R5, 
+  MAG_OPTION_F_FILL_AP_R5, 
+
+  MAG_OPTION_F_AP_R6, 
+  MAG_OPTION_F_ERR_AP_R6, 
+  MAG_OPTION_F_STDEV_AP_R6, 
+  MAG_OPTION_F_FILL_AP_R6, 
 } dvoMagOptionType;
 
@@ -139,4 +173,10 @@
       MEAS_XFIX, 
       MEAS_YFIX, 
+      MEAS_XOFF_KH, 
+      MEAS_YOFF_KH, 
+      MEAS_XOFF_DCR, 
+      MEAS_YOFF_DCR, 
+      MEAS_ROFF_GAL, 
+      MEAS_DOFF_GAL, 
       MEAS_POS_SYS_ERR, 
       MEAS_XFIELD, 
@@ -162,5 +202,38 @@
       MEAS_FLAT,
       MEAS_CENTER_OFFSET,
-      MEAS_REF_COLOR,
+      MEAS_REF_COLOR_BLUE,
+      MEAS_REF_COLOR_RED,
+      MEAS_X11_SM_OBJ, 
+      MEAS_X12_SM_OBJ, 
+      MEAS_X22_SM_OBJ, 
+      MEAS_E1_SM_OBJ, 
+      MEAS_E2_SM_OBJ, 
+      MEAS_X11_SH_OBJ, 
+      MEAS_X12_SH_OBJ, 
+      MEAS_X22_SH_OBJ, 
+      MEAS_E1_SH_OBJ, 
+      MEAS_E2_SH_OBJ, 
+      MEAS_X11_SM_PSF, 
+      MEAS_X12_SM_PSF, 
+      MEAS_X22_SM_PSF, 
+      MEAS_E1_SM_PSF, 
+      MEAS_E2_SM_PSF, 
+      MEAS_X11_SH_PSF, 
+      MEAS_X12_SH_PSF, 
+      MEAS_X22_SH_PSF, 
+      MEAS_E1_SH_PSF, 
+      MEAS_E2_SH_PSF, 
+      MEAS_E_BV,		      // extinction (mags)
+      MEAS_E_BV_ERR,
+      MEAS_DISTANCE_MOD, // distance modulus (mags)
+      MEAS_DISTANCE_MOD_ERR,
+      MEAS_M_R,		      // absolute mag in r-band
+      MEAS_M_R_ERR,
+      MEAS_FEH,		      // metallicity
+      MEAS_FEH_ERR,
+      MEAS_URA_GALMODEL,      // model pm prediction
+      MEAS_UDEC_GALMODEL,     // model pm prediction
+      MEAS_RA_GALMODEL,      // model pm prediction
+      MEAS_DEC_GALMODEL,     // model pm prediction
 };
 
@@ -200,6 +273,19 @@
       AVE_EXTID_HI,
       AVE_EXTID_LO,
-      AVE_REF_COLOR,
+      AVE_REF_COLOR_BLUE,
+      AVE_REF_COLOR_RED,
       AVE_PHOT, // photometry class of values
+      AVE_E_BV,		      // extinction (mags)
+      AVE_E_BV_ERR,
+      AVE_DISTANCE_MOD, // distance modulus (mags)
+      AVE_DISTANCE_MOD_ERR,
+      AVE_M_R,		      // absolute mag in r-band
+      AVE_M_R_ERR,
+      AVE_FEH,		      // metallicity
+      AVE_FEH_ERR,
+      AVE_URA_GALMODEL,      // model pm prediction
+      AVE_UDEC_GALMODEL,     // model pm prediction
+      AVE_RA_GALMODEL,      // model pm prediction
+      AVE_DEC_GALMODEL,     // model pm prediction
 };
 
@@ -276,5 +362,6 @@
       IMAGE_NLINK_PHOTOM,
       IMAGE_NLINK_ASTROM,
-      IMAGE_REF_COLOR
+      IMAGE_REF_COLOR_BLUE,
+      IMAGE_REF_COLOR_RED
 };
 
@@ -368,6 +455,6 @@
 int          ParseImageField        PROTO((dbField *field, char *fieldName));
 
-dbValue      dbExtractAverages      PROTO((Average *average, SecFilt *secfilt, Measure *measure, dbField *field));
-dbValue      dbExtractMeasures      PROTO((Average *average, SecFilt *secfilt, Measure *measure, dbField *field));
+dbValue      dbExtractAverages      PROTO((Average *average, SecFilt *secfilt, Measure *measure, Lensobj *lensobj, StarPar *starpar, dbField *field));
+dbValue      dbExtractMeasures      PROTO((Average *average, SecFilt *secfilt, Measure *measure, Lensing *lensing, StarPar *starpar, dbField *field));
 dbValue      dbExtractImages        PROTO((Image *image, off_t Nimage, off_t N, dbField *field));
 
@@ -376,4 +463,9 @@
 int          dbAstroRegionLimits    PROTO((dbStack **stack, int *nstack, SkyRegionSelection *selection, int table));
 char        *strfloat               PROTO((float value));
+
+int dbFieldNeedMeasure (dbField *fields, int Nfields);
+int dbFieldNeedLensobj (dbField *fields, int Nfields);
+int dbFieldNeedLensing (dbField *fields, int Nfields);
+int dbFieldNeedStarpar (dbField *fields, int Nfields, int isAverage);
 
 void FreeImageSelection (void);
Index: /trunk/Ohana/src/libdvo/include/libdvo_astro.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/libdvo_astro.h	(revision 37806)
+++ /trunk/Ohana/src/libdvo/include/libdvo_astro.h	(revision 37807)
@@ -1,16 +1,37 @@
-#ifdef notdef
-# include "external.h"
-# include "shell.h"
-# include "dvomath.h"
-# include "convert.h"
-# include "display.h"
-# include "data.h"
-#endif
+# ifndef LIBDVO_ASTRO_H
+# define LIBDVO_ASTRO_H
 
-# ifndef ASTRO_H
-# define ASTRO_H
+typedef enum {
+  COORD_NONE, 
+  COORD_CELESTIAL, 
+  COORD_GALACTIC, // based on Liu et al 2011
+  COORD_ECLIPTIC,
+  COORD_GALACTIC_REID_2004, // an older definition of the galactic plane
+} CoordTransformSystem;
 
-void InitAstro (void);
-void FreeAstro (void);
+typedef enum {
+  PROJ_NONE, // undefined
+  PROJ_ZEA, // zenithal
+  PROJ_ZPL, // zenithal
+  PROJ_ARC, // zenithal
+  PROJ_STG, // zenithal
+  PROJ_SIN, // zenithal
+  PROJ_TAN, // zenithal
+  PROJ_TNX, // zenithal
+  PROJ_DIS, // zenithal (TAN + polyterms)
+  PROJ_LIN, // cartesian
+  PROJ_PLY, // cartesian (allow polyterms)
+  PROJ_WRP, // cartesian (allow polyterms, require mosaic)
+  PROJ_AIT, // pseudocyl
+  PROJ_GLS, // pseudocyl
+  PROJ_PAR, // pseudocyl
+} OhanaProjection;
+
+typedef enum {
+  PROJ_MODE_NONE,
+  PROJ_MODE_CARTESIAN,
+  PROJ_MODE_ZENITHAL,
+  PROJ_MODE_PSEUDOCYL,
+} OhanaProjectionMode;
 
 typedef struct {
@@ -29,8 +50,136 @@
 } CoordTransform;
 
-typedef enum {COORD_NONE, COORD_CELESTIAL, COORD_GALACTIC, COORD_ECLIPTIC} CoordTransformSystem;
+typedef struct {
+  int Nx;
+  int Ny;
+  float dX;
+  float dY;
+  float **dXv;
+  float **dYv;
+  unsigned int tableID;
+  unsigned int imageID;
+  int keep;
+} AstromOffsetMap;
+
+typedef struct {
+  int Nmap;
+  AstromOffsetMap **map;
+   int *imageIDtoTableSeq;
+  int MaxImageID;
+  int MaxTableID;
+} AstromOffsetTable;
+
+/* Internal version of Coords (see CoordsDisk in libautocode/def/coords-disk.d) */
+typedef struct Coords {
+  double           crval1;               // coordinate at reference pixel
+  double           crval2;               // coordinate at reference pixel
+  float            crpix1;               // coordinate of reference pixel
+  float            crpix2;               // coordinate of reference pixel
+  float            cdelt1;               // degrees per pixel
+  float            cdelt2;               // degrees per pixel
+  float            pc1_1;                // rotation matrix
+  float            pc1_2;                // rotation matrix
+  float            pc2_1;                // rotation matrix
+  float            pc2_2;                // rotation matrix
+  float            polyterms[7][2];      // higher order warping terms
+  char             ctype[15];            // coordinate type
+  char             Npolyterms;           // order of polynomial
+  struct Coords   *mosaic;               // pointer to parent mosaic
+  AstromOffsetMap *offsetMap;		 // pointer to offset map transformation
+} Coords;
+
+typedef struct Image {
+  Coords           coords;               // astrometric data
+  e_time           tzero;                // readout time (row 0)
+  unsigned int     nstar;                // number of stars on image
+  float            secz;                 // airmass (mag)
+  unsigned short   NX;                   // image width
+  unsigned short   NY;                   // image height
+  float            apmifit;              // aperture correction (mag)
+  float            dapmifit;             // apmifit error (mag)
+  float            Mcal;                 // calibration mag (mag)
+  float            dMcal;                // error on Mcal (mag)
+  short            Xm;                   // image chisq (10*log(value))
+  short            photcode;             // identifier for CCD,
+  float            exptime;              // exposure time (seconds)
+  float            sidtime;              // sidereal time of exposure
+  float            latitude;             // observatory latitude (degrees)
+  float            RAo;                  // image center (degrees)
+  float            DECo;                 // image center (degrees)
+  float            Radius;               // image radius (degrees)
+  float            refColorBlue;         // median astrometry ref color
+  float            refColorRed;          // median astrometry ref color
+  char             name[117];            // name of original image 
+  unsigned char    detection_limit;      // detection limit (10*mag)
+  unsigned char    saturation_limit;     // saturation limit (10*mag)
+  unsigned char    cerror;               // astrometric error (50*arcsec)
+  unsigned char    fwhm_x;               // PSF x width (25*arcsec)
+  unsigned char    fwhm_y;               // PSF y width (25*arcsec)
+  unsigned char    trate;                // scan rate (100 usec/pixel)
+  unsigned char    ccdnum;               // CCD ID number
+  unsigned int     flags;                // image quality flags
+  unsigned int     imageID;              // internal image ID
+  unsigned int     parentID;             // associated ref image
+  unsigned int     externID;             // external image ID
+  unsigned short   sourceID;             // analysis source ID
+  short            nLinkAstrom;          // mean number of matched measurements for astrometry
+  short            nLinkPhotom;          // mean number of matched measurements for astrometry
+  short            ubercalDist;          // distance to nearest ubercal image
+  float            dXpixSys;             // systematic astrometry error in X
+  float            dYpixSys;             // systematic astrometry error in Y
+  float            dMagSys;              // systematic photometry error
+  unsigned short   nFitAstrom;           // number of stars used for astrometry cal
+  unsigned short   nFitPhotom;           // number of stars used for photometry cal
+  unsigned int     photom_map_id;        // reference to 2D zero point map
+  unsigned int     astrom_map_id;        // reference to 2D astrometry map
+  struct Image    *parent;               // pointer to parent mosaic (not save to disk)
+} Image;
 
 CoordTransform *InitTransform (CoordTransformSystem input, CoordTransformSystem output);
 int ApplyTransform (double *x, double *y, double X, double Y, CoordTransform *transform);
 
+/* in coords.c */
+void InitCoords (Coords *coords, char *projection);
+void CopyCoords (Coords *tgt, Coords *src);
+
+int  XY_to_LM (double *L, double *M, double x,  double y,   Coords *coords);
+int  LM_to_XY (double *x,  double *y,   double L, double M, Coords *coords);
+int  RD_to_LM (double *L, double *M, double ra,  double dec,   Coords *coords);
+int  LM_to_RD (double *ra, double *dec,   double L, double M, Coords *coords);
+int  XY_to_RD (double *ra, double *dec, double x,  double y,   Coords *coords);
+int  RD_to_XY (double *x,  double *y,   double ra, double dec, Coords *coords);
+int  fXY_to_RD (float *ra, float *dec, double x,  double y,   Coords *coords);
+int  fRD_to_XY (float *x,  float *y,   double ra, double dec, Coords *coords);
+int  GetCoords (Coords *coords, Header *header);
+int  PutCoords (Coords *coords, Header *header);
+void coords_precess (double *ra, double *dec, double in_epoch, double out_epoch);
+OhanaProjection GetProjection (char *ctype);
+int SetProjection (char *ctype, OhanaProjection proj);
+OhanaProjectionMode GetProjectionMode (OhanaProjection proj);
+
+/* in AstromOffsetMapIO.c */
+AstromOffsetTable *AstromOffsetMapLoad (char *filename, int VERBOSE);
+int AstromOffsetMapSave (AstromOffsetTable *table, char *filename);
+AstromOffsetTable *AstromOffsetMapToTable(AstromOffsetMap_Disk_6x6 *map_disk, off_t Nmap);
+AstromOffsetMap_Disk_6x6 *AstromOffsetTableToMap(AstromOffsetTable *table, off_t *Nmap);
+
+/* in AstromOffsetMapOps.c */
+float AstromOffsetMapValue (AstromOffsetMap *map, float x, float y, int xdir);
+int AstromOffsetMapFit (AstromOffsetMap *map, float *x, float *y, float *f, int Npts, int xdir);
+
+/* in AstromOffsetMapUtils.c */
+AstromOffsetMap *AstromOffsetMapInit (int Nx, int Ny);
+void AstromOffsetMapFree (AstromOffsetMap *map);
+
+int AstromOffsetTableNewMap (AstromOffsetTable *table, int Nx, int Ny, Image *image);
+int AstromOffsetTableMatchChips (Image *images, off_t Nimages, AstromOffsetTable *table);
+AstromOffsetTable *AstromOffsetTableInit();
+void AstromOffsetTableFree(AstromOffsetTable *table);
+int AstromOffsetTableAddMapFromImage (AstromOffsetTable *table, Image *image);
+void AstromOffsetMapPrint (AstromOffsetMap *map, char *filename);
+int AstromOffsetMapRepair (AstromOffsetMap *map, int xdir);
+AstromOffsetMap *AstromOffsetMapCopy (AstromOffsetMap *map);
+void AstromOffsetMapCopyData (AstromOffsetMap *tgt, AstromOffsetMap *src);
+void AstromOffsetMapSetOrder (AstromOffsetMap *map, int Nx, int Ny, Image *image);
+
 # endif
Index: /trunk/Ohana/src/libdvo/include/ps1_sim_defs.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/ps1_sim_defs.h	(revision 37807)
+++ /trunk/Ohana/src/libdvo/include/ps1_sim_defs.h	(revision 37807)
@@ -0,0 +1,14 @@
+Image 		       	*Image_PS1_SIM_ToInternal (Image_PS1_SIM *in, off_t Nvalues, off_t Nalloc);
+Image_PS1_SIM    	*ImageInternalTo_PS1_SIM (Image *in, off_t Nvalues);
+
+Average 	       	*Average_PS1_SIM_ToInternal (Average_PS1_SIM *in, off_t Nvalues, SecFilt **primary);
+Average_PS1_SIM         *AverageInternalTo_PS1_SIM (Average *in, off_t Nvalues, SecFilt *primary);
+
+Measure 	       	*Measure_PS1_SIM_ToInternal (Average *ave, Measure_PS1_SIM *in, off_t Nvalues);
+Measure_PS1_SIM         *MeasureInternalTo_PS1_SIM (Average *ave, Measure *in, off_t Nvalues);
+
+SecFilt 	       	*SecFilt_PS1_SIM_ToInternal (SecFilt_PS1_SIM *in, off_t Nvalues);
+SecFilt_PS1_SIM         *SecFiltInternalTo_PS1_SIM (SecFilt *in, off_t Nvalues);
+
+StarPar 	       	*StarPar_PS1_SIM_ToInternal (StarPar_PS1_SIM *in, off_t Nvalues);
+StarPar_PS1_SIM         *StarParInternalTo_PS1_SIM (StarPar *in, off_t Nvalues);
Index: /trunk/Ohana/src/libdvo/include/ps1_v5_defs.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/ps1_v5_defs.h	(revision 37806)
+++ /trunk/Ohana/src/libdvo/include/ps1_v5_defs.h	(revision 37807)
@@ -9,8 +9,12 @@
 
 Lensing 	       	*Lensing_PS1_V5_ToInternal (Lensing_PS1_V5 *in, off_t Nvalues);
+Lensing 	       	*Lensing_PS1_V5_ALT_ToInternal (Lensing_PS1_V5_ALT *in, off_t Nvalues);
 Lensing_PS1_V5          *LensingInternalTo_PS1_V5 (Lensing *in, off_t Nvalues);
 Lensobj 	       	*Lensobj_PS1_V5_ToInternal (Lensobj_PS1_V5 *in, off_t Nvalues);
 Lensobj_PS1_V5          *LensobjInternalTo_PS1_V5 (Lensobj *in, off_t Nvalues);
 
+StarPar 	       	*StarPar_PS1_V5_ToInternal (StarPar_PS1_V5 *in, off_t Nvalues);
+StarPar_PS1_V5          *StarParInternalTo_PS1_V5 (StarPar *in, off_t Nvalues);
+
 PhotCode                *PhotCode_PS1_V5_To_Internal (PhotCode_PS1_V5 *in, off_t Nvalues);
 PhotCode_PS1_V5         *PhotCode_Internal_To_PS1_V5 (PhotCode *in, off_t Nvalues);
Index: /trunk/Ohana/src/libdvo/src/AstromOffsetMapIO.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/AstromOffsetMapIO.c	(revision 37807)
+++ /trunk/Ohana/src/libdvo/src/AstromOffsetMapIO.c	(revision 37807)
@@ -0,0 +1,207 @@
+# include "dvo.h"
+
+AstromOffsetTable *AstromOffsetMapLoad (char *filename, int VERBOSE) {
+
+  off_t Nmap;
+
+  Header header;
+  Matrix matrix;
+  Header theader;
+  FTable ftable;
+
+  FILE *f = fopen (filename, "r");
+  if (f == NULL) {
+    if (VERBOSE) fprintf (stderr, "can't find Astrom Offset Map file %s\n", filename);
+    return (NULL);
+  }
+
+  /* load in table data */
+  ftable.header = &theader;
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read Astrom Offset Map header\n");
+    fclose (f);
+    return (NULL);
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read Astrom Offset Map matrix\n");
+    gfits_free_header (&header);
+    fclose (f);
+    return (NULL);
+  }
+
+  // for now, we only have one flavor (6x6)
+
+  if (!gfits_fread_ftable (f, &ftable, "ASTROM_OFFSET_MAP_DISK_6x6")) {
+    if (VERBOSE) fprintf (stderr, "can't read Astrom Offset Map table\n");
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+    fclose (f);
+    return (NULL);
+  }
+  AstromOffsetMap_Disk_6x6 *map_disk = gfits_table_get_AstromOffsetMap_Disk_6x6 (&ftable, &Nmap, NULL);
+  if (!map_disk) {
+    fprintf (stderr, "ERROR: failed to read Astrom Offset Map\n");
+    exit (2);
+  }
+
+  // AstromOffsetMap_Disk_6x6 *map_disk is an external format stored as an array of maps.
+  // Convert the disk array of maps to then internal format in a rich structure:
+  AstromOffsetTable *table = AstromOffsetMapToTable (map_disk, Nmap);
+
+  gfits_free_header (&theader);
+  gfits_free_matrix (&matrix);
+  gfits_free_table  (&ftable);
+
+  return (table);
+}
+
+int AstromOffsetMapSave (AstromOffsetTable *table, char *filename) {
+
+  off_t Nmap;
+
+  Header header;
+  Matrix matrix;
+  Header theader;
+  FTable ftable;
+
+  FILE *f;
+
+  /* make phu header (no matrix needed) */
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  // AstromOffsetMap_Disk_6x6 *map_disk is an external format stored as an array of maps.
+  // Convert the internal format in a rich structure into a disk array of maps.
+  AstromOffsetMap_Disk_6x6 *map_disk = AstromOffsetTableToMap (table, &Nmap); 
+
+  ftable.header = &theader;
+  gfits_table_set_AstromOffsetMap_Disk_6x6 (&ftable, map_disk, Nmap);
+
+  f = fopen (filename, "w");
+  if (f == (FILE *) NULL) { 
+    fprintf (stderr, "cannot open %s for output\n", filename);
+    return (FALSE);
+  }
+  
+  gfits_fwrite_header  (f, &header);
+  gfits_fwrite_matrix  (f, &matrix);
+  gfits_fwrite_Theader (f, &theader);
+  gfits_fwrite_table  (f, &ftable);
+  fclose (f);
+
+  return (TRUE);
+}
+
+AstromOffsetTable *AstromOffsetMapToTable(AstromOffsetMap_Disk_6x6 *map_disk, off_t Nmap) {
+
+  int i, j, k;
+
+  AstromOffsetTable *table = NULL;
+  ALLOCATE (table, AstromOffsetTable, 1);
+
+  table->Nmap = Nmap;
+  ALLOCATE (table->map, AstromOffsetMap *, Nmap);
+
+  // find the max value of imageID
+  int MaxTableID = 0;
+  int MaxImageID = 0;
+  for (i = 0; i < Nmap; i++) {
+    MaxTableID = MAX(map_disk[i].tableID, MaxTableID);
+    MaxImageID = MAX(map_disk[i].imageID, MaxImageID);
+  }
+  table->MaxTableID = MaxTableID;
+  table->MaxImageID = MaxImageID;
+
+  // generate the index and init values to -1
+  ALLOCATE (table->imageIDtoTableSeq, int, MaxImageID + 1);
+  for (i = 0; i <= MaxImageID; i++) {
+    table->imageIDtoTableSeq[i] = -1;
+  }
+
+  // assign the ID values
+  for (i = 0; i < Nmap; i++) {
+    int ImageID = map_disk[i].imageID;
+    myAssert (table->imageIDtoTableSeq[ImageID] == -1, "oops, duplicate image IDs");
+    table->imageIDtoTableSeq[ImageID] = i;
+  }
+
+  // assign the map values (this allocates just the area needed for each image, not the
+  // full 6x6, saving some memory while doing the analysis)
+  for (i = 0; i < Nmap; i++) {
+    ALLOCATE (table->map[i], AstromOffsetMap, 1);
+
+    table->map[i][0].Nx 	  = map_disk[i].Nx;
+    table->map[i][0].Ny 	  = map_disk[i].Ny;
+    table->map[i][0].tableID = map_disk[i].tableID;
+    table->map[i][0].imageID = map_disk[i].imageID;
+    
+    // STORE THESE VALUES?
+    table->map[i][0].dY 	  = map_disk[i].dX;
+    table->map[i][0].dX 	  = map_disk[i].dY;
+
+    // since this was on disk, we obviously keep it
+    table->map[i][0].keep 	  = TRUE;
+
+    ALLOCATE (table->map[i][0].dXv, float *, map_disk[i].Nx);
+    ALLOCATE (table->map[i][0].dYv, float *, map_disk[i].Nx);
+
+    for (j = 0; j < map_disk[i].Nx; j++) {
+      ALLOCATE (table->map[i][0].dXv[j], float, map_disk[i].Ny);
+      ALLOCATE (table->map[i][0].dYv[j], float, map_disk[i].Ny);
+
+      for (k = 0; k < map_disk[i].Ny; k++) {
+	table->map[i][0].dXv[j][k] = map_disk[i].dXv[j][k];
+	table->map[i][0].dYv[j][k] = map_disk[i].dYv[j][k];
+      }
+    }
+  }
+  return table;
+}
+
+AstromOffsetMap_Disk_6x6 *AstromOffsetTableToMap(AstromOffsetTable *table, off_t *Nmap) {
+
+  int i, j, k;
+
+  AstromOffsetMap_Disk_6x6 *map_disk = NULL;
+  ALLOCATE (map_disk, AstromOffsetMap_Disk_6x6, table->Nmap);
+
+  // assign the map values (this allocates just the area needed for each image, not the
+  // full 6x6, saving some memory while doing the analysis)
+  // some maps in the table should be skipped because their image no longer uses them
+  int Ndisk = 0;
+  for (i = 0; i < table->Nmap; i++) {
+    if (!table->map[i][0].keep) continue;
+    map_disk[Ndisk].Nx 	    = table->map[i][0].Nx;
+    map_disk[Ndisk].Ny 	    = table->map[i][0].Ny;
+    map_disk[Ndisk].tableID = table->map[i][0].tableID;
+    map_disk[Ndisk].imageID = table->map[i][0].imageID;
+    
+    map_disk[Ndisk].dX 	    = table->map[i][0].dX;
+    map_disk[Ndisk].dY 	    = table->map[i][0].dY;
+
+    
+
+    for (j = 0; j < map_disk[Ndisk].Nx; j++) {
+      for (k = 0; k < map_disk[Ndisk].Ny; k++) {
+	map_disk[Ndisk].dXv[j][k] = table->map[i][0].dXv[j][k];
+	map_disk[Ndisk].dYv[j][k] = table->map[i][0].dYv[j][k];
+      }
+      for (k = map_disk[Ndisk].Ny; k < 6; k++) {
+	map_disk[Ndisk].dXv[j][k] = 0.0;
+	map_disk[Ndisk].dYv[j][k] = 0.0;
+      }
+    }
+    for (j = map_disk[Ndisk].Nx; j < 6; j++) {
+      for (k = 0; k < 6; k++) {
+	map_disk[Ndisk].dXv[j][k] = 0.0;
+	map_disk[Ndisk].dYv[j][k] = 0.0;
+      }
+    }
+    Ndisk ++;
+  }
+  *Nmap = Ndisk;
+  return map_disk;
+}
+
Index: /trunk/Ohana/src/libdvo/src/AstromOffsetMapOps.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/AstromOffsetMapOps.c	(revision 37807)
+++ /trunk/Ohana/src/libdvo/src/AstromOffsetMapOps.c	(revision 37807)
@@ -0,0 +1,543 @@
+# include "dvo.h"
+
+/* AstromOffsetMap functions:
+ */
+
+int dump_map_data (float *x, float *y, float *f, int Npts, char *filename);
+int AstromOffsetMapFit_Chisq (AstromOffsetMap *map, float *x, float *y, float *f, int Npts, int xdir);
+int AstromOffsetMapFit_Median (AstromOffsetMap *map, float *x, float *y, float *f, int Npts, int xdir);
+
+float AstromOffsetMapValue (AstromOffsetMap *map, float x, float y, int xdir) {
+
+  // given x,y find the offset (x-direction) at that location from the map
+
+  float **V = xdir ? map->dXv : map->dYv;
+
+  // if there is no spatial information, the value at the point is the value in the map
+  if ((map->Nx == 1) && (map->Ny == 1)) return V[0][0];
+
+  // if there is no spatial information in 1D, the interpolation is 1D
+  if (map->Nx == 1) {
+    float ymo = y * map->dY - 0.5;
+    int   ymi = floor(ymo);
+    ymi = MAX(0,MIN(map->Ny - 2, ymi)); // force range of ymi to be 0,Ny-2 (Ny must be > 1)
+
+    float ymf = ymo - ymi;
+
+    float value = ymf * V[0][ymi+1] + (1.0 - ymf) * V[0][ymi];
+    return value;
+  }
+
+  // if there is no spatial information in 1D, the interpolation is 1D
+  if (map->Ny == 1) {
+    float xmo = x * map->dX - 0.5;
+    int   xmi = floor(xmo);
+    xmi = MAX(0,MIN(map->Nx - 2, xmi)); // force range of ymi to be 0,Nx-2  (Nx must be > 1)
+
+    float xmf = xmo - xmi;
+
+    float value = xmf * V[xmi+1][0] + (1.0 - xmf) * V[xmi][0];
+    return value;
+  }
+
+  // x & y are in Big Image coordinates, convert to fractional map coordinates 
+
+  float xmo = x * map->dX - 0.5;
+  int   xmi = floor(xmo);
+  xmi = MAX(0,MIN(map->Nx - 2, xmi)); // force range of ymi to be 0,Nx-1
+  float xmf = xmo - xmi;
+
+  float ymo = y * map->dY - 0.5;
+  int   ymi = floor(ymo);
+  ymi = MAX(0,MIN(map->Ny - 2, ymi)); // force range of ymi to be 0,Ny-2
+  float ymf = ymo - ymi;
+
+  float V00 = V[xmi+0][ymi+0];
+  float V01 = V[xmi+0][ymi+1];
+  float V10 = V[xmi+1][ymi+0];
+  float V11 = V[xmi+1][ymi+1];
+
+  float Vx0 = V10*xmf + V00*(1.0 - xmf);
+  float Vx1 = V11*xmf + V01*(1.0 - xmf);
+
+  float value = ymf * Vx1 + (1.0 - ymf) * Vx0;
+  return value;
+}
+
+int AstromOffsetMapFit (AstromOffsetMap *map, float *x, float *y, float *f, int Npts, int xdir) {
+
+  int status = AstromOffsetMapFit_Median (map, x, y, f, Npts, xdir);
+  return status;
+}
+
+// given (x,y),value vector sets, choose the map that minimizes the difference between 
+// the values and bilinear interpolation of the map
+int AstromOffsetMapFit_Chisq (AstromOffsetMap *map, float *x, float *y, float *f, int Npts, int xdir) {
+
+  int i, j, ix, iy, jx, jy;
+
+  // choose to map direction:
+  float **V = xdir ? map->dXv : map->dYv;
+
+  // special cases:
+  if ((map->Nx == 1) && (map->Ny == 1)) {
+
+    // no spatial information, just return (clipped?) median or mean or something
+    VStatsType stats;
+    stats.statmode = VSTATS_INNER_MEAN;
+
+    vstats_getstats_f (f, NULL, NULL, Npts, &stats);
+    V[0][0] = stats.mean;
+    return TRUE;
+  }
+
+  // special cases:
+  if (map->Nx == 1) {
+    fprintf (stderr, "1 x N not yet coded\n");
+    exit (1);
+    return TRUE;
+  }
+
+  if (map->Ny == 1) {
+    fprintf (stderr, "N x 1 not yet coded\n");
+    exit (1);
+    return TRUE;
+  }
+
+  // this bit is copied nearly intact from psImageMapFit.c
+
+  // set up the redirection table so we can use sA[-1][-1], etc
+  float SAm[3][3], *SAv[3], **sA;
+
+  for (i = 0; i < 3; i++) {
+    SAv[i] = SAm[i] + 1;
+  }
+  sA = SAv + 1;
+
+  int Nx = map->Nx;
+  int Ny = map->Ny;
+
+  double **A, **B;
+  ALLOCATE (A, double *, Nx*Ny);
+  ALLOCATE (B, double *, Nx*Ny);
+  for (i = 0; i < Nx*Ny; i++) {
+    ALLOCATE (A[i], double, Nx*Ny);
+    memset (A[i], 0, sizeof(double)*Nx*Ny);
+    ALLOCATE (B[i], double, 1);
+    memset (B[i], 0, sizeof(double));
+  }    
+
+  // we are looping over the Nx,Ny image map elements;
+  // the matrix equation contains Nx*Ny rows and columns
+  // for (int n = 1; n < Nx - 1; n++) {
+  // for (int m = 1; m < Ny - 1; m++) {
+
+  if (0) {
+    FILE *fd = fopen ("stats.dump.txt", "w");
+    for (i = 0; i < Npts; i++) {
+      fprintf (fd, "%d %f %f %f\n", i, x[i], y[i], f[i]);
+    }
+    fclose(fd);
+  }
+
+  // float Total = 0.0;
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+      // define & init summing variables
+      double rx_rx_ry_ry = 0;
+      double rx_rx_dy_ry = 0;
+      double dx_rx_ry_ry = 0;
+      double dx_rx_dy_ry = 0;
+      double fi_rx_ry    = 0;
+      double rx_rx_py_py = 0;
+      double rx_rx_qy_py = 0;
+      double dx_rx_py_py = 0;
+      double dx_rx_qy_py = 0;
+      double fi_rx_py    = 0;
+      double px_px_ry_ry = 0;
+      double px_px_dy_ry = 0;
+      double qx_px_ry_ry = 0;
+      double qx_px_dy_ry = 0;
+      double fi_px_ry    = 0;
+      double px_px_py_py = 0;
+      double px_px_qy_py = 0;
+      double qx_px_py_py = 0;
+      double qx_px_qy_py = 0;
+      double fi_px_py    = 0;
+
+      // generate the sums for the fitting matrix element I,J
+      // I = n + nX*m
+      // J = (n + jn) + nX*(m + jm)
+      for (i = 0; i < Npts; i++) {
+
+	// data value & weight for this point
+	if (!isfinite(f[i])) continue;
+
+	// if (mask && (mask[i] & maskValue)) continue;
+
+	// base coordinate offset for this point (x,y) relative to this map element (n,m)
+	// double dx = psImageBinningGetRuffX (map->binning, x->data.F32[i]) - (n + 0.5);
+	// double dy = psImageBinningGetRuffY (map->binning, y->data.F32[i]) - (m + 0.5);
+
+	double dx = x[i] * map->dX - ix - 0.5;
+	double dy = y[i] * map->dY - iy - 0.5;
+
+	// XXX do I need to do something different at the edge or not?  I want to allow
+	// x[i] to extend beyond the grid
+
+# if (0)
+	// edge cases to include:
+	bool edgeX = false;
+	edgeX |= ((n == 1) && (dx < -1.0));
+	edgeX |= ((n == Nx - 2) && (dx > +1.0));
+
+	bool edgeY = false;
+	edgeY |= ((m == 1) && (dy < -1.0));
+	edgeY |= ((m == Ny - 2) && (dy > +1.0));
+
+	// skip points outside of 2x2 grid centered on n,m:
+	if (!edgeX && (fabs(dx) > 1.0)) continue;
+	if (!edgeY && (fabs(dy) > 1.0)) continue;
+# endif
+
+	if (fabs(dx) > 1.0) continue;
+	if (fabs(dy) > 1.0) continue;
+
+	// related offset values
+	double rx = 1.0 - dx;
+	double ry = 1.0 - dy;
+	double px = 1.0 + dx;
+	double py = 1.0 + dy;
+	double qx = 0.0 - dx;
+	double qy = 0.0 - dy;
+
+	// sum the appropriate elements for the different quadrants
+	int Qx = (dx >= 0) ? 1 : 0;
+	int Qy = (dy >= 0) ? 1 : 0;
+
+	// XXX why change the selection at the edges?
+	// if (n ==      0) Qx = 1;
+	// if (n == Nx - 1) Qx = 0;
+	// if (m ==      0) Qy = 1;
+	// if (m == Ny - 1) Qy = 0;
+
+	float fi = f[i];
+	assert (isfinite(fi));
+	assert (isfinite(rx));
+	assert (isfinite(ry));
+
+	// points at offset 1,1
+	if ((Qx == 1) && (Qy == 1)) {
+	  rx_rx_ry_ry += rx*rx*ry*ry;
+	  rx_rx_dy_ry += rx*rx*dy*ry;
+	  dx_rx_ry_ry += dx*rx*ry*ry;
+	  dx_rx_dy_ry += dx*rx*dy*ry;
+	  fi_rx_ry    += fi*rx*ry;
+	}
+	// points at offset 1,0
+	if ((Qx == 1) && (Qy == 0)) {
+	  rx_rx_py_py += rx*rx*py*py;
+	  rx_rx_qy_py += rx*rx*qy*py;
+	  dx_rx_py_py += dx*rx*py*py;
+	  dx_rx_qy_py += dx*rx*qy*py;
+	  fi_rx_py    += fi*rx*py;
+	}
+	// points at offset 0,1
+	if ((Qx == 0) && (Qy == 1)) {
+	  px_px_ry_ry += px*px*ry*ry;
+	  px_px_dy_ry += px*px*dy*ry;
+	  qx_px_ry_ry += qx*px*ry*ry;
+	  qx_px_dy_ry += qx*px*dy*ry;
+	  fi_px_ry    += fi*px*ry;
+	}
+	// points at offset 0,0
+	if ((Qx == 0) && (Qy == 0)) {
+	  px_px_py_py += px*px*py*py;
+	  px_px_qy_py += px*px*qy*py;
+	  qx_px_py_py += qx*px*py*py;
+	  qx_px_qy_py += qx*px*qy*py;
+	  fi_px_py    += fi*px*py;
+	}
+      }
+
+      // the chi-square derivatives have elements of the form g(n+jn,m+jm)*A(jn,jm),
+      // jn,jm = -1 to +1. Convert the sums above into the correct coefficients
+      sA[-1][-1] = qx_px_qy_py;
+      sA[-1][ 0] = qx_px_ry_ry + qx_px_py_py;
+      sA[-1][+1] = qx_px_dy_ry;
+      sA[ 0][-1] = rx_rx_qy_py + px_px_qy_py;
+      sA[ 0][ 0] = rx_rx_ry_ry + px_px_ry_ry + rx_rx_py_py + px_px_py_py;
+      sA[ 0][+1] = rx_rx_dy_ry + px_px_dy_ry;
+      sA[+1][-1] = dx_rx_qy_py;
+      sA[+1][ 0] = dx_rx_ry_ry + dx_rx_py_py;
+      sA[+1][+1] = dx_rx_dy_ry;
+
+      // I[ 0][ 0] = index for this n,m element:
+      int I = ix + Nx * iy;
+      B[I][0] = fi_rx_ry + fi_rx_py + fi_px_ry + fi_px_py;
+
+      // insert these values into their corresponding locations in A, B
+      // float Sum = 0.0;
+      for (jx = -1; jx <= +1; jx++) {
+	if (ix + jx <   0) continue;
+	if (ix + jx >= Nx) continue;
+	for (jy = -1; jy <= +1; jy++) {
+	  if (iy + jy <   0) continue;
+	  if (iy + jy >= Ny) continue;
+	  int J = (ix + jx) + Nx * (iy + jy);
+	  A[J][I] = sA[jx][jy];
+	  if (abs(A[J][I]) > 1000) {
+	    fprintf (stderr, "A %d %d (%d %d : %d %d): %f\n", I, J, ix, iy, ix + jx, iy + jy, sA[jx][jy]);
+	  }
+	  // Sum += sA[jn][jm];
+	}
+      }
+      // fprintf (stderr, "B %d (%d %d) : %f  :  %f\n", I, n, m, B->data.F32[I], Sum);
+      // Total += Sum;
+    }
+  }
+  // fprintf (stderr, "Total: %f\n", Total);
+
+  // test for empty diagonal elements (unconstained cells), mark, and set pivots to 1.0
+  int *Empty = NULL;
+  ALLOCATE (Empty, int, Nx*Ny);
+
+  // find the max value
+  double MaxPivot = 0.0;
+  for (i = 0; i < Nx*Ny; i++) {
+    MaxPivot = MAX(A[i][i], MaxPivot);
+  }
+
+  // elements of A[i][j] are just the fractional weight of the number of input points to the pixel.
+  // any pixels which have < 5% contribution are not very well constrained.  NAN them for now...
+  double MinPivot = 0.05 * MaxPivot;
+  for (i = 0; i < Nx*Ny; i++) {
+    Empty[i] = 0;
+    if (fabs(A[i][i]) < MinPivot) {
+      Empty[i] = 1;
+      for (j = 0; j < Nx*Ny; j++) {
+	A[i][j] = 0.0;
+	A[j][i] = 0.0;
+      }
+      A[i][i] = 1.0;
+      B[i][0] = 0.0;
+    }
+  }
+
+  if (1) {
+    FILE *fd = fopen ("matrix.dat", "w");
+    for (i = 0; i < Nx*Ny; i++) {
+      for (j = 0; j < Nx*Ny; j++) {
+	fprintf (fd, "%10.4f ", A[i][j]);
+      }
+      fprintf (fd, " : %10.4f\n", B[i][0]);
+    }
+    fclose (fd);
+  }
+
+  if (!dgaussjordan(A, B, Nx*Ny, 1)) {
+    fprintf (stderr, "FAIL\n");
+    exit (1);
+  }
+
+  // set bad values to NaN
+  for (i = 0; i < Nx*Ny; i++) {
+    if (Empty[i]) {
+      B[i][0] = NAN;
+      A[i][i] = 0;
+    }
+  }
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+      int I = ix + Nx * iy;
+      V[ix][iy] = B[I][0];
+      // error[ix][iy] = sqrt(A[I][I]);
+    }
+  }
+
+  // XXX free someone?
+  for (i = 0; i < Nx*Ny; i++) {
+    free (A[i]);
+    free (B[i]);
+  }    
+  free (A);
+  free (B);
+  free (Empty);
+
+  return TRUE;
+}
+
+// given (x,y),value vector sets, choose the map that minimizes the difference between 
+// the values and bilinear interpolation of the map
+int AstromOffsetMapFit_Median (AstromOffsetMap *map, float *x, float *y, float *f, int Npts, int xdir) {
+
+  int i, ix, iy;
+
+  // choose to map direction:
+  float **V = xdir ? map->dXv : map->dYv;
+
+  // measure clipped median in each bin
+  VStatsType stats;
+  stats.statmode = VSTATS_INNER_MEAN;
+
+  int Nx = map->Nx;
+  int Ny = map->Ny;
+  int Npix = Nx*Ny;
+
+  double **values;
+  int    *Nvalue;
+  ALLOCATE (Nvalue, int, Npix);
+  ALLOCATE (values, double *, Npix);
+  for (i = 0; i < Npix; i++) {
+    ALLOCATE (values[i], double, Npts);
+    Nvalue[i] = 0;
+  }
+
+  // assign the points to the map cells
+  for (i = 0; i < Npts; i++) {
+
+    // data value & weight for this point
+    if (!isfinite(f[i])) continue;
+
+    // if (mask && (mask[i] & maskValue)) continue;
+    
+    // base coordinate offset for this point (x,y) relative to this map element (n,m)
+    // double dx = psImageBinningGetRuffX (map->binning, x->data.F32[i]) - (n + 0.5);
+    // double dy = psImageBinningGetRuffY (map->binning, y->data.F32[i]) - (m + 0.5);
+
+    // bin for this point
+    ix = MAX(0, MIN(Nx-1, (int)(x[i] * map->dX)));
+    iy = MAX(0, MIN(Ny-1, (int)(y[i] * map->dY)));
+    int I = ix + Nx * iy;
+
+    int N = Nvalue[I];
+    values[I][N] = f[i];
+    Nvalue[I] ++;
+  }    
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+      int I = ix + Nx * iy;
+      if (Nvalue[I] > 5) {
+	vstats_getstats (values[I], NULL, NULL, Nvalue[I], &stats);
+	V[ix][iy] = stats.mean;
+      } else {
+	V[ix][iy] = NAN;
+      }
+    }
+  }
+
+  // XXX free someone?
+  for (i = 0; i < Npix; i++) {
+    free (values[i]);
+  }    
+  free (values);
+  free (Nvalue);
+
+  return TRUE;
+}
+
+// this function repairs an image with NAN pixels (only valid for a small-scale map -- no robust mean)
+int AstromOffsetMapRepair (AstromOffsetMap *map, int xdir) {
+
+  // we are going to repair the image by:
+  // 1) finding NAN pixels
+  // 2) if any of the neighbors are valid,
+  //    replace with the mean of the neighbors
+  // 3) otherwise, replace with the image mean
+
+  int Nx = map->Nx;
+  int Ny = map->Ny;
+
+  int ix, iy;
+
+  float **Vfix = NULL;
+  ALLOCATE (Vfix, float *, Nx);
+  for (ix = 0; ix < Nx; ix++) {
+    ALLOCATE (Vfix[ix], float, Ny);
+    for (iy = 0; iy < Ny; iy++) {
+      Vfix[ix][iy] = NAN;
+    }
+  }
+
+  // find the global mean
+  float mean = 0.0;
+  float npix = 0.0;
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+      float value = xdir ? map->dXv[ix][iy] : map->dYv[ix][iy];
+      if (isnan(value)) continue;
+      mean += value;
+      npix += 1.0;
+    }
+  }
+  mean /= npix;
+
+  // find the NAN pixels:
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+
+      float value = xdir ? map->dXv[ix][iy] : map->dYv[ix][iy];
+      if (!isnan(value)) {
+	Vfix[ix][iy] = value;
+	continue;
+      }
+
+      // find mean of all possible neighbors
+      float meanLocal = 0.0;
+      float npixLocal = 0.0;
+
+      int jx, jy;
+      for (jx = -1; jx <= +1; jx++) {
+	int nx = ix + jx;
+	if (nx < 0) continue;
+	if (nx >= Nx) continue;
+	for (jy = -1; jy <= +1; jy++) {
+	  int ny = iy + jy;
+	  if (ny < 0) continue;
+	  if (ny >= Ny) continue;
+	  float value = xdir ? map->dXv[nx][ny] : map->dYv[nx][ny];
+	  if (isnan(value)) continue;
+
+	  meanLocal += value;
+	  npixLocal += 1.0;
+	}
+      }
+      // if there are no valid (non-NAN) local pixels, use global mean
+      meanLocal = (npixLocal > 0.0) ? meanLocal / npixLocal : mean;
+      Vfix[ix][iy] = meanLocal;
+    }
+  }
+    
+  // replace the bad pixels:
+  for (ix = 0; ix < Ny; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+      if (xdir) {
+	map->dXv[ix][iy] = Vfix[ix][iy];
+      } else {
+	map->dYv[ix][iy] = Vfix[ix][iy];
+      }
+    }
+  }
+  for (ix = 0; ix < Nx; ix++) {
+    free (Vfix[ix]);
+  }
+  free (Vfix);
+
+  return TRUE;
+}
+
+int dump_map_data (float *x, float *y, float *f, int Npts, char *filename) {
+
+  FILE *fout = fopen (filename, "w");
+  
+  int i;
+  for (i = 0; i < Npts; i++) {
+    fprintf (fout, "%d %f %f %f\n", i, x[i], y[i], f[i]);
+  }
+  fclose (fout);
+  return TRUE;
+} 
+
Index: /trunk/Ohana/src/libdvo/src/AstromOffsetMapUtils.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/AstromOffsetMapUtils.c	(revision 37807)
+++ /trunk/Ohana/src/libdvo/src/AstromOffsetMapUtils.c	(revision 37807)
@@ -0,0 +1,280 @@
+# include <dvo.h>
+
+int AstromOffsetTableMatchChips (Image *images, off_t Nimages, AstromOffsetTable *table) {
+
+  // we have a table of astrometry offset maps.  we want to find the chips that match to
+  // each of the maps so we can assign the map to the image.coords.offsetMap entry
+
+  // we have the lookup table of table->imageIDtoTableSeq to find the match by imageID
+  
+  // we are just going to assume that table->imageIDtoTableSeq is correct and complete
+  off_t i;
+  for (i = 0; i < Nimages; i++) {
+    int imageID = images[i].imageID;
+    if (imageID < 0) continue;
+    if (imageID > table->MaxImageID) continue;
+    
+    int seq = table->imageIDtoTableSeq[imageID];
+    if (seq < 0) continue;
+    if (seq >= table->Nmap) continue; // this one is probably not valid, right?
+
+    images[i].coords.offsetMap = table->map[seq];    
+  }
+  return (TRUE);
+}
+
+AstromOffsetMap *AstromOffsetMapInit (int Nx, int Ny) {
+
+  AstromOffsetMap *map = NULL;
+  ALLOCATE (map, AstromOffsetMap, 1);
+
+  map->dX = NAN; 
+  map->dY = NAN; 
+  map->Nx = Nx; // output map size
+  map->Ny = Ny; // output map size
+ 
+  map->keep = TRUE; // output map size
+
+  ALLOCATE (map->dXv, float *, map->Nx);
+  ALLOCATE (map->dYv, float *, map->Nx);
+
+  int j, k;
+  for (j = 0; j < map->Nx; j++) {
+    ALLOCATE (map->dXv[j], float, map->Ny);
+    ALLOCATE (map->dYv[j], float, map->Ny);
+
+    for (k = 0; k < map->Ny; k++) {
+      map->dXv[j][k] = 0.0;
+      map->dYv[j][k] = 0.0;
+    }
+  }
+
+  return map;
+}
+
+void AstromOffsetMapFree (AstromOffsetMap *map) {
+
+  if (!map) return;
+
+  int j;
+  for (j = 0; j < map->Nx; j++) {
+    if (map->dXv) free (map->dXv[j]);
+    if (map->dYv) free (map->dYv[j]);
+  }
+
+  free (map->dXv);
+  free (map->dYv);
+
+  free (map);
+
+  return;
+}
+
+void AstromOffsetMapSetOrder (AstromOffsetMap *map, int Nx, int Ny, Image *image) {
+
+  int j, k;
+
+  // rather than try to figure out how to resize, and free/allocate, lets just free the old arrays and make new ones
+  for (j = 0; j < map->Nx; j++) {
+    if (map->dXv) free (map->dXv[j]);
+    if (map->dYv) free (map->dYv[j]);
+  }
+  free (map->dXv);
+  free (map->dYv);
+
+  map->Nx = Nx; // output map size
+  map->Ny = Ny; // output map size
+  map->dX = Nx / (float) image->NX;
+  map->dY = Ny / (float) image->NY;
+ 
+  map->keep = TRUE; // output map size
+
+  ALLOCATE (map->dXv, float *, map->Nx);
+  ALLOCATE (map->dYv, float *, map->Nx);
+
+  for (j = 0; j < map->Nx; j++) {
+    ALLOCATE (map->dXv[j], float, map->Ny);
+    ALLOCATE (map->dYv[j], float, map->Ny);
+
+    for (k = 0; k < map->Ny; k++) {
+      map->dXv[j][k] = 0.0;
+      map->dYv[j][k] = 0.0;
+    }
+  }
+  return;
+}
+
+AstromOffsetMap *AstromOffsetMapCopy (AstromOffsetMap *map) {
+
+  if (!map) return NULL;
+
+  AstromOffsetMap *tgt = AstromOffsetMapInit (map->Nx, map->Ny);
+
+  tgt->dX = map->dX; 
+  tgt->dY = map->dY; 
+  tgt->imageID = map->imageID; 
+  tgt->tableID = map->tableID; 
+
+  int j, k;
+  for (j = 0; j < tgt->Nx; j++) {
+    for (k = 0; k < map->Ny; k++) {
+      tgt->dXv[j][k] = map->dXv[j][k];
+      tgt->dYv[j][k] = map->dYv[j][k];
+    }
+  }
+  return tgt;
+}
+
+// copy the data from one to another, assuming a pre-allocated structure
+void AstromOffsetMapCopyData (AstromOffsetMap *tgt, AstromOffsetMap *src) {
+
+  myAssert (tgt->Nx == src->Nx, "programming error");
+  myAssert (tgt->Ny == src->Ny, "programming error");
+
+  tgt->dX = src->dX; 
+  tgt->dY = src->dY; 
+  tgt->imageID = src->imageID; 
+  tgt->tableID = src->tableID; 
+
+  tgt->keep = src->keep; 
+
+  int j, k;
+  for (j = 0; j < tgt->Nx; j++) {
+    for (k = 0; k < src->Ny; k++) {
+      tgt->dXv[j][k] = src->dXv[j][k];
+      tgt->dYv[j][k] = src->dYv[j][k];
+    }
+  }
+  return;
+}
+
+void AstromOffsetTableFree (AstromOffsetTable *table) {
+
+  if (!table) return;
+
+  int i, j;
+  for (i = 0; i < table->Nmap; i++) {
+    for (j = 0; j < table->map[i][0].Nx; j++) {
+      free (table->map[i][0].dXv[j]);
+      free (table->map[i][0].dYv[j]);
+    }
+    if (!table->map[i]) continue;
+    FREE (table->map[i][0].dXv);
+    FREE (table->map[i][0].dYv);
+    FREE (table->map[i]);
+  }
+  FREE (table->imageIDtoTableSeq);
+  FREE (table->map);
+}
+
+int AstromOffsetTableNewMap (AstromOffsetTable *table, int Nx, int Ny, Image *image) {
+
+  off_t i;
+  if (image->imageID > table->MaxImageID) {
+    int oldMaxID = table->MaxImageID;
+    table->MaxImageID = image->imageID;
+    REALLOCATE (table->imageIDtoTableSeq, int, table->MaxImageID + 1);
+    for (i = oldMaxID + 1; i < table->MaxImageID + 1; i++) {
+      table->imageIDtoTableSeq[i] = -1;
+    }
+  }
+  int seq = table->imageIDtoTableSeq[image->imageID];
+  if (seq != -1) {
+    // already assigned (but not used)
+    AstromOffsetMapSetOrder (table->map[seq], Nx, Ny, image);
+    image[0].coords.offsetMap = table->map[seq];    
+    return TRUE;
+  }
+
+  int Nmap = table->Nmap;
+
+  table->Nmap++;
+  REALLOCATE (table->map, AstromOffsetMap *, table->Nmap);
+
+  myAssert (table->imageIDtoTableSeq[image->imageID] == -1, "table IDtoSeq not initiazed or image collision");
+  table->imageIDtoTableSeq[image->imageID] = Nmap;
+  
+  table->map[Nmap] = AstromOffsetMapInit (Nx, Ny);
+
+  table->MaxTableID ++;
+  table->map[Nmap][0].tableID = table->MaxTableID;
+  table->map[Nmap][0].imageID = image->imageID;
+  
+  table->map[Nmap][0].dX = Nx / (float) image->NX;
+  table->map[Nmap][0].dY = Ny / (float) image->NY;
+
+  image[0].coords.offsetMap = table->map[Nmap];
+  return TRUE;    
+}
+
+int AstromOffsetTableAddMapFromImage (AstromOffsetTable *table, Image *image) {
+
+  int Nmap = table->Nmap;
+
+  table->Nmap++;
+  REALLOCATE (table->map, AstromOffsetMap *, table->Nmap);
+
+  // find the imageID and update the imageIDtoTableSeq allocation if needed
+  off_t i;
+  if (image->imageID > table->MaxImageID) {
+    int oldMaxID = table->MaxImageID;
+    table->MaxImageID = image->imageID;
+    REALLOCATE (table->imageIDtoTableSeq, int, table->MaxImageID + 1);
+    for (i = oldMaxID + 1; i < table->MaxImageID + 1; i++) {
+      table->imageIDtoTableSeq[i] = -1;
+    }
+  }
+  myAssert (table->imageIDtoTableSeq[image->imageID] == -1, "table IDtoSeq not initiazed or image collision");
+  table->imageIDtoTableSeq[image->imageID] = Nmap;
+  
+  table->map[Nmap] = image[0].coords.offsetMap;
+  return TRUE;    
+}
+
+AstromOffsetTable *AstromOffsetTableInit() {
+
+  AstromOffsetTable *table = NULL;
+  ALLOCATE (table, AstromOffsetTable, 1);
+
+  table->Nmap = 0;
+  ALLOCATE (table->map, AstromOffsetMap *, 1);
+
+  table->MaxTableID = 0;
+  table->MaxImageID = 0;
+
+  // generate the index and init values to -1
+  ALLOCATE (table->imageIDtoTableSeq, int, 1);
+  table->imageIDtoTableSeq[0] = -1;
+
+  return table;
+}
+
+void AstromOffsetMapPrint (AstromOffsetMap *map, char *filename) {
+
+  FILE *f = stderr;
+  if (strcasecmp(filename, "stderr") && strcasecmp(filename, "stdout")) {
+    f = fopen (filename, "w");
+    if (!f) {
+      fprintf (stderr, "failed to open output file %s\n", filename);
+    }
+  }
+
+  fprintf (f, "imageID: %d, tableID: %d (dX: %f, dY: %f), keep: %d\n", map->imageID, map->tableID, map->dX, map->dY, map->keep);
+  int ix, iy;
+  fprintf (f, "dXv map:\n");
+  for (ix = 0; ix < map->Nx; ix++) {
+    for (iy = 0; iy < map->Ny; iy++) {
+      fprintf (f, "%9.5f ", map->dXv[ix][iy]);
+    }
+    fprintf (f, "\n");
+  }
+  fprintf (f, "dYv map:\n");
+  for (ix = 0; ix < map->Nx; ix++) {
+    for (iy = 0; iy < map->Ny; iy++) {
+      fprintf (f, "%9.5f ", map->dYv[ix][iy]);
+    }
+    fprintf (f, "\n");
+  }
+  if (f != stderr) fclose (f);
+  return;
+}
Index: /trunk/Ohana/src/libdvo/src/BoundaryTree.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/BoundaryTree.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/BoundaryTree.c	(revision 37807)
@@ -371,4 +371,6 @@
 int BoundaryTreeCellCoords (BoundaryTree *tree, int *zone, int *band, double ra, double dec) {
 
+  ra = ohana_normalize_angle (ra);
+
   // first, find the containing zone
 
Index: /trunk/Ohana/src/libdvo/src/HostTable.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/HostTable.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/HostTable.c	(revision 37807)
@@ -3,15 +3,20 @@
 # include <sys/wait.h>
 
+void InitHost (HostInfo *host) {
+  host->hostname = NULL;
+  host->pathname = NULL;
+  host->results = NULL;
+  host->stdio[HOST_STDIN] = -1;
+  host->stdio[HOST_STDOUT] = -1;
+  host->stdio[HOST_STDERR] = -1;
+  host->pid = 0;
+  return;
+}
+
 void InitHosts (HostInfo *hosts, int Nhosts, int NHOSTS) {
 
   int i;
   for (i = Nhosts; i < NHOSTS; i++) {
-    hosts[i].hostname = NULL;
-    hosts[i].pathname = NULL;
-    hosts[i].results = NULL;
-    hosts[i].stdio[HOST_STDIN] = -1;
-    hosts[i].stdio[HOST_STDOUT] = -1;
-    hosts[i].stdio[HOST_STDERR] = -1;
-    hosts[i].pid = 0;
+    InitHost (&hosts[i]);
   }
   return;
@@ -36,4 +41,15 @@
   }
   if (table->index) free (table->index);
+  free (table);
+  return;
+}
+
+// a HostTableGroup is a pointer set to one or more HostTable entries
+void FreeTableGroup (HostTableGroup *table) {
+
+  if (!table) return;
+  if (table->hosts) {
+    free (table->hosts);
+  }
   free (table);
   return;
@@ -136,4 +152,66 @@
   fclose (f);
   return table;
+}
+
+// split a host table into Ngroups, each with a unique set of hosts
+HostTableGroup *HostTableGroups (HostTable *table, int *ngroups) {
+
+  // identify the unique host names and the number of times they each exist
+  // (Host tables are not very large; I can afford to do this in an inefficient way)
+  int i, j, k;
+
+  int Nunique = 0;
+  char **unique_hosts = NULL; // an array of pointers to a set of the names
+  int  *Nunique_hosts = NULL; // an array of pointers to the count of each
+  ALLOCATE ( unique_hosts, char *, table->Nhosts);
+  ALLOCATE (Nunique_hosts, int, table->Nhosts);
+  for (i = 0; i < table->Nhosts; i++) Nunique_hosts[i] = 0;
+
+  for (i = 0; i < table->Nhosts; i++) {
+    int found = FALSE;
+    for (j = 0; !found && (j < Nunique); j++) {
+      if (!strcmp(table->hosts[i].hostname, unique_hosts[j])) {
+	Nunique_hosts[j] ++;
+	found = TRUE;
+      }
+    }
+    if (!found) {
+      unique_hosts[Nunique] = table->hosts[i].hostname;
+      Nunique_hosts[Nunique] = 1;
+      Nunique ++;
+    }
+  }
+
+  int Ngroups = 0;
+  for (i = 0; i < Nunique; i++) {
+    Ngroups = MAX (Nunique_hosts[i], Ngroups);
+  }
+
+  HostTableGroup *groups = NULL;
+  int *foundHost = NULL;
+  
+  ALLOCATE (foundHost, int, table->Nhosts);
+  ALLOCATE (groups, HostTableGroup, Ngroups);
+
+  for (i = 0; i < table->Nhosts; i++) foundHost[i] = FALSE;
+
+  // in each group, attempt to add one of each unique host
+  for (i = 0; i < Ngroups; i++) {
+    groups[i].Nhosts = 0;
+    ALLOCATE (groups[i].hosts, HostInfo *, Nunique);
+    for (j = 0; j < Nunique; j++) {
+      int found = FALSE;
+      for (k = 0; !found && (k < table->Nhosts); k++) {
+	if (foundHost[k]) continue;
+	if (strcmp(unique_hosts[j], table->hosts[k].hostname)) continue;
+	groups[i].hosts[groups[i].Nhosts] = &table->hosts[k];
+	groups[i].Nhosts ++;
+	found = TRUE;
+	foundHost[k] = TRUE;
+      }
+    }
+  }
+  *ngroups = Ngroups;
+  return groups;
 }
 
@@ -255,5 +333,5 @@
 
   // this loop has 2 chunks: (a) check for I/O + (b) check for jobs done
-  while (1) {
+  while (Nfound < Nrunning) {
 
     // Wait up to 0.5 second for host to provide I/O
@@ -284,5 +362,5 @@
 	// if (table->hosts[i].stdout.Nbuffer > 0x10000) {
 	if (table->hosts[i].stdout.Nbuffer > 0x1000) {
-	  int printHead = VERBOSE || (table->hosts[i].stdout.Nbuffer > 0);
+	  int printHead = VERBOSE && (table->hosts[i].stdout.Nbuffer > 0);
 	  if (printHead) fprintf (stdout, "--- stdout from %s --- (%d bytes, v1)\n", table->hosts[i].hostname, table->hosts[i].stdout.Nbuffer);
 	  int Nout = write (STDOUT_FILENO, table->hosts[i].stdout.buffer, table->hosts[i].stdout.Nbuffer);
@@ -298,5 +376,5 @@
 	// if (table->hosts[i].stderr.Nbuffer > 0x10000) {
 	if (table->hosts[i].stderr.Nbuffer > 0x1000) {
-	  int printHead = VERBOSE || (table->hosts[i].stderr.Nbuffer > 0);
+	  int printHead = VERBOSE && (table->hosts[i].stderr.Nbuffer > 0);
 	  if (printHead) fprintf (stdout, "--- stderr from %s --- (%d bytes, v1)\n", table->hosts[i].hostname, table->hosts[i].stderr.Nbuffer);
 	  int Nout = write (STDOUT_FILENO, table->hosts[i].stderr.buffer, table->hosts[i].stderr.Nbuffer);
@@ -314,5 +392,5 @@
       if (!pid) {
 	// fprintf (stderr, "no hosts to harvest\n");
-	usleep (500000);
+	usleep (200000);
 	break; // no outstanding jobs have finished 
       }
@@ -367,9 +445,6 @@
 	}
       }
-      if (!found) {
-	fprintf (stderr, "Programming error: failed to matched finished job to known host!\n");
-	exit (2);
-      }
-      Nfound ++;
+      if (found) Nfound ++;
+      // we sometimes harvest children not in the list (eg, closed kapa window).  just ignore
       if (Nfound == Nrunning) goto escape; // we've harvested all jobs
     }
@@ -389,4 +464,180 @@
 }
 
+// wait for all children to complete, report output to stdout
+int HostTableGroupWaitJobsGetIO (HostTableGroup *table, char *file, int lineno, int VERBOSE) {
+
+  // we have launched table->Nhosts jobs; wait for all of them to complete...
+  // if one (N) failed to launch, we will get an ECHILD error from the last (N) calls
+
+  // we need to read any data waiting on stderr or stdout from these jobs, or the overfull
+  // buffers can cause a problem.  we alternate between 'select' and 'waitpid' calls with
+  // timeouts for both
+
+  // add all hosts' sockets to the fd_sets
+  fd_set rdSet, wtSet;
+  FD_ZERO (&rdSet);
+  FD_ZERO (&wtSet);
+
+  // XXX can I set the fd_sets once, since I am not actually closing the fd's?
+
+  int globalStatus = TRUE;
+
+  int i;
+  int Nmax = 0;
+  for (i = 0; i < table->Nhosts; i++) {
+    if (!table->hosts[i][0].pid) continue; // any unconnected hosts should be skipped
+    FD_SET (table->hosts[i][0].stdio[HOST_STDIN], &wtSet);
+    Nmax = MAX (Nmax, table->hosts[i][0].stdio[HOST_STDIN]);
+    FD_SET (table->hosts[i][0].stdio[HOST_STDOUT], &rdSet);
+    Nmax = MAX (Nmax, table->hosts[i][0].stdio[HOST_STDOUT]);
+    FD_SET (table->hosts[i][0].stdio[HOST_STDERR], &rdSet);
+    Nmax = MAX (Nmax, table->hosts[i][0].stdio[HOST_STDERR]);
+  }    
+  Nmax ++;
+
+  // need the list of connected hosts for exit test below
+  int Nrunning = 0;
+  for (i = 0; i < table->Nhosts; i++) {
+    if (!table->hosts[i][0].pid) continue; // any unconnected hosts should be skipped
+    Nrunning ++;
+  }
+
+  int Nfound = 0;
+
+  // this loop has 2 chunks: (a) check for I/O + (b) check for jobs done
+  while (1) {
+
+    // Wait up to 0.5 second for host to provide I/O
+    // timeout gets mucked: need to reset before each select
+    struct timeval timeout;
+    timeout.tv_sec = 10;
+    timeout.tv_usec = 500000;
+
+    int status = select (Nmax, NULL, &wtSet, NULL, &timeout);
+    if (status == -1) {
+      perror("select()");
+      exit (2);
+    }
+
+    // we have some sockets to check, check sockets for all hosts
+    for (i = 0; (status > 0) && (i < table->Nhosts); i++) {
+      if (!table->hosts[i][0].pid) continue; // any unconnected hosts should be skipped
+
+      if (FALSE && FD_ISSET (table->hosts[i][0].stdio[HOST_STDIN], &wtSet)) {
+	// this host is waiting for input : this is an error, so exit
+	fprintf (stderr, "host %s is waiting for input\n", table->hosts[i][0].hostname);
+	abort();
+      }
+      
+      if ((table->hosts[i][0].stdio[HOST_STDOUT] > 0) && FD_ISSET (table->hosts[i][0].stdio[HOST_STDOUT], &rdSet)) {
+	// this host has waiting output : read to buffer, and dump if necessary
+	ReadtoIOBuffer (&table->hosts[i][0].stdout, table->hosts[i][0].stdio[HOST_STDOUT]);
+	// if (table->hosts[i][0].stdout.Nbuffer > 0x10000) {
+	if (table->hosts[i][0].stdout.Nbuffer > 0x1000) {
+	  int printHead = VERBOSE || (table->hosts[i][0].stdout.Nbuffer > 0);
+	  if (printHead) fprintf (stdout, "--- stdout from %s --- (%d bytes, v1)\n", table->hosts[i][0].hostname, table->hosts[i][0].stdout.Nbuffer);
+	  int Nout = write (STDOUT_FILENO, table->hosts[i][0].stdout.buffer, table->hosts[i][0].stdout.Nbuffer);
+	  if (Nout != table->hosts[i][0].stdout.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+	  FlushIOBuffer (&table->hosts[i][0].stdout);
+	  if (printHead) fprintf (stdout, "\n");
+	}
+      }
+
+      if ((table->hosts[i][0].stdio[HOST_STDERR] > 0) && FD_ISSET (table->hosts[i][0].stdio[HOST_STDERR], &rdSet)) {
+	// this host has waiting output : read to buffer, and dump if necessary
+	ReadtoIOBuffer (&table->hosts[i][0].stderr, table->hosts[i][0].stdio[HOST_STDERR]);
+	// if (table->hosts[i][0].stderr.Nbuffer > 0x10000) {
+	if (table->hosts[i][0].stderr.Nbuffer > 0x1000) {
+	  int printHead = VERBOSE || (table->hosts[i][0].stderr.Nbuffer > 0);
+	  if (printHead) fprintf (stdout, "--- stderr from %s --- (%d bytes, v1)\n", table->hosts[i][0].hostname, table->hosts[i][0].stderr.Nbuffer);
+	  int Nout = write (STDOUT_FILENO, table->hosts[i][0].stderr.buffer, table->hosts[i][0].stderr.Nbuffer);
+	  if (Nout != table->hosts[i][0].stderr.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+	  FlushIOBuffer (&table->hosts[i][0].stderr);
+	  if (printHead) fprintf (stdout, "\n");
+	}
+      }
+    }
+
+    // now check if any children have finished...
+    while (TRUE) {
+      int status = 0;
+      int pid = waitpid (-1, &status, WNOHANG);
+      if (!pid) {
+	// fprintf (stderr, "no hosts to harvest\n");
+	usleep (500000);
+	break; // no outstanding jobs have finished 
+      }
+      if ((pid == -1) && (errno == ECHILD)) goto escape; // no more jobs on which to wait
+      if ((pid == -1) && (errno != ECHILD)) {
+	fprintf (stderr, "programming error (2)? %s %d", file, lineno);
+	exit (2);
+      }
+
+      // find the host which has finished
+      int found = FALSE;
+      for (i = 0; (i < table->Nhosts) && !found; i++) {
+	if (table->hosts[i][0].pid != pid) continue;
+	found = TRUE;
+
+	HostInfo *host = table->hosts[i];
+
+	// check on the status of this and report any output?
+	if (VERBOSE) fprintf (stdout, "job finished for %s (%d)\n", host->hostname, pid);
+
+	// read stdout
+	int printHead;
+	printHead = VERBOSE || (host->stdout.Nbuffer > 0);
+	EmptyIOBuffer (&host->stdout, 100, host->stdio[HOST_STDOUT]);
+	if (printHead) fprintf (stdout, "--- stdout from %s --- (%d bytes, v2)\n", host->hostname, host->stdout.Nbuffer);
+	int Nout = write (STDOUT_FILENO, host->stdout.buffer, host->stdout.Nbuffer);
+	if (Nout != host->stdout.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+	FlushIOBuffer (&host->stdout);
+	if (printHead) fprintf (stdout, "\n");
+	    
+	// read stderr
+	printHead = VERBOSE || (host->stderr.Nbuffer > 0);
+	EmptyIOBuffer (&host->stderr, 100, host->stdio[HOST_STDERR]);
+	if (printHead) fprintf (stdout, "--- stderr from %s --- (%d bytes, v2)\n", host->hostname, host->stderr.Nbuffer);
+	Nout = write (STDOUT_FILENO, host->stderr.buffer, host->stderr.Nbuffer);
+	if (Nout != host->stderr.Nbuffer) { fprintf (stderr, "(error writing log?)\n"); }
+	FlushIOBuffer (&host->stderr);
+	if (printHead) fprintf (stdout, "\n");
+
+	if (WIFEXITED(status)) {
+	  if (VERBOSE) fprintf (stdout, "normal completion, exit status is %d\n", WEXITSTATUS(status));
+	  host->status = WEXITSTATUS(status);
+	  if (host->status) {
+	    fprintf (stdout, "job failed on %s\n", host->hostname);
+	    globalStatus = FALSE;
+	  }
+	} else {
+	  host->status = -1;
+	  fprintf (stdout, "job exited abnormally on %s\n", host->hostname);
+	  globalStatus = FALSE;
+	  continue;
+	}
+      }
+      if (!found) {
+	fprintf (stderr, "Programming error: failed to matched finished job to known host!\n");
+	exit (2);
+      }
+      Nfound ++;
+      if (Nfound == Nrunning) goto escape; // we've harvested all jobs
+    }
+  }
+
+escape:
+
+  // close all opened connections
+  for (i = 0; i < table->Nhosts; i++) {
+    if (!table->hosts[i][0].pid) continue; // any unconnected hosts should be skipped
+    close (table->hosts[i][0].stdio[HOST_STDIN]);
+    close (table->hosts[i][0].stdio[HOST_STDOUT]);
+    close (table->hosts[i][0].stdio[HOST_STDERR]);
+  }
+
+  return globalStatus;
+}
+
 int HostTableTestHost (SkyRegion *region, int hostID) {
 
Index: /trunk/Ohana/src/libdvo/src/ImageMetadata.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/ImageMetadata.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/ImageMetadata.c	(revision 37807)
@@ -156,13 +156,13 @@
   // assign the storage arrays
   for (i = 0; i < Nimage; i++) {
-    int Nmosaic = FindMosaicForImage (image, Nimage, i);
-    if (!Nmosaic) continue;
-    Nmosaic --;
+    if (!image[i].coords.mosaic) continue;
     imageID[i]  = image[i].imageID;
     externID[i] = image[i].externID;
-    crval1[i]   = image[Nmosaic].coords.crval1;
-    crval2[i]   = image[Nmosaic].coords.crval2;
-
-    theta[i]    = DEG_RAD*atan2(image[Nmosaic].coords.pc1_2, image[Nmosaic].coords.pc1_1);
+    Coords *mosaic = image[i].coords.mosaic;
+
+    crval1[i]   = mosaic->crval1;
+    crval2[i]   = mosaic->crval2;
+
+    theta[i]    = DEG_RAD*atan2(mosaic->pc1_2, mosaic->pc1_1);
 
     Mcal[i]     = image[i].Mcal;
Index: /trunk/Ohana/src/libdvo/src/ImageMetadataSelection.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/ImageMetadataSelection.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/ImageMetadataSelection.c	(revision 37807)
@@ -13,18 +13,10 @@
   
   /* field defines a frame with 0,0 at the field center, and 1 arcsec / pixel */
-  field.crpix1 = field.crpix2 = 0.0;
+  InitCoords (&field, "DEC--SIN");
   field.cdelt1 = field.cdelt2 = 1.0 / 3600;
-  field.pc1_1  = field.pc2_2  = 1.0;
-  field.pc1_2  = field.pc2_1  = 0.0;
-  field.Npolyterms = 0;
-  strcpy (field.ctype, "DEC--SIN");
 
   /* mosaic defines a frame with 0,0 at the mosaic center, and 1 arcsec / pixel */
-  mosaic.crpix1 = mosaic.crpix2 = 0.0;
+  InitCoords (&mosaic, "DEC--SIN");
   mosaic.cdelt1 = mosaic.cdelt2 = 1.0 / 3600;
-  mosaic.pc1_1  = mosaic.pc2_2  = 1.0;
-  mosaic.pc1_2  = mosaic.pc2_1  = 0.0;
-  mosaic.Npolyterms = 0;
-  strcpy (mosaic.ctype, "DEC--SIN");
 
   if ((image = ImageMetadataLoad (filename, &Nimage)) == NULL) return (FALSE);
Index: /trunk/Ohana/src/libdvo/src/ImageOps.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/ImageOps.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/ImageOps.c	(revision 37807)
@@ -55,5 +55,4 @@
     if (TimeSelect && ((image[i].tzero < tzero) || (image[i].tzero+image[i].trate*image[i].NY > tzero + trange))) continue;
     if (selection->useDisplay) {
-      if (!FindMosaicForImage (image, Nimage, i)) continue;
       // first check if region center is in image
       status = RD_to_XY (&X, &Y, Rmid, graph.coords.crval2, &image[i].coords);
@@ -89,5 +88,4 @@
     }
     if (selection->useSkyregion) {
-      if (!FindMosaicForImage (image, Nimage, i)) continue;
       /* project this image to screen display coords */
       x[0] = 0;           y[0] = 0;
Index: /trunk/Ohana/src/libdvo/src/ImageSelection.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/ImageSelection.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/ImageSelection.c	(revision 37807)
@@ -22,11 +22,7 @@
 
   if (mosaicMode) {
-      /* mosaic defines a frame with 0,0 at the mosaic center, and 1 arcsec / pixel */
-      mosaic.crpix1 = mosaic.crpix2 = 0.0;
-      mosaic.cdelt1 = mosaic.cdelt2 = 1.0 / 3600;
-      mosaic.pc1_1  = mosaic.pc2_2  = 1.0;
-      mosaic.pc1_2  = mosaic.pc2_1  = 0.0;
-      mosaic.Npolyterms = 0;
-      strcpy (mosaic.ctype, "DEC--SIN");
+    /* mosaic defines a frame with 0,0 at the mosaic center, and 1 arcsec / pixel */
+    InitCoords (&mosaic, "DEC--SIN");
+    mosaic.cdelt1 = mosaic.cdelt2 = 1.0 / 3600;
   }
 
@@ -64,5 +60,4 @@
   }
   if (m == -1) return (NULL);
-  if (!FindMosaicForImage (image, Nimage, m)) return (NULL);
   return (&image[m]);
 }
@@ -85,5 +80,4 @@
   }
   if (m == -1) return (NULL);
-  if (!FindMosaicForImage (image, Nimage, m)) return (NULL);
   return (&image[m]);
 }
Index: /trunk/Ohana/src/libdvo/src/LoadImages.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/LoadImages.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/LoadImages.c	(revision 37807)
@@ -9,4 +9,5 @@
 static Image *imageCache = NULL;
 static off_t cacheNimage = 0;
+AstromOffsetTable *table = NULL;
 
 static time_t getLastModified(char *filename);
@@ -39,5 +40,4 @@
     lastModified = 0;
   }
-
 
   gfits_db_init (&db);
@@ -77,4 +77,19 @@
   }
 
+  // assign image->parent and image->coords.mosaic 
+  BuildChipMatch (image, *Nimage);
+  // MARKTIME("build chip match: %f sec\n", dtime);
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", catdir);
+
+  if (table) AstromOffsetTableFree(table);
+  table = AstromOffsetMapLoad (mapfile, FALSE);
+
+  // assign images.coords.offsetMap -> table->map[i]
+  if (table) {
+    AstromOffsetTableMatchChips (image, *Nimage, table);
+  }
+
   return (image);
 }
@@ -92,4 +107,5 @@
   if (!dvoUseImageCache && (images != NULL)) {
     free(images);
+    AstromOffsetTableFree (table);
   } else {
     // defer free until next LoadImages with a different or modified Images.dat
Index: /trunk/Ohana/src/libdvo/src/RegionHostTable.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/RegionHostTable.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/RegionHostTable.c	(revision 37807)
@@ -31,4 +31,6 @@
     hosts[i].Nneighbors = 0;
     hosts[i].isNeighbor = FALSE;
+
+    hosts[i].astromTable = NULL;
   }
   return;
Index: /trunk/Ohana/src/libdvo/src/astrom_maps.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/astrom_maps.c	(revision 37807)
+++ /trunk/Ohana/src/libdvo/src/astrom_maps.c	(revision 37807)
@@ -0,0 +1,70 @@
+# include <dvo.h>
+
+int MatchChipsToAstromMaps (Image *images, off_t Nimages, AstromOffsetTable *table) {
+
+  // we have a table of astrometry offset maps.  we want to find the chips that match to
+  // each of the maps so we can assign the map to the image.coords.offsetMap entry
+
+  // we have the lookup table of table->IDtoSeq to find the match by imageID
+  
+  // we are just going to assume that table->IDtoSeq is correct and complete
+  for (i = 0; i < Nimages; i++) {
+    int imageID = images[i].imageID;
+    if (imageID < 0) continue;
+    if (imageID > table->maxID) continue;
+    
+    int seq = table->IDtoSeq[imageID];
+    if (seq < 0) continue;
+    if (seq >= table->Nmap) continue; // this one is probably not valid, right?
+
+    images[i].coords.offsetMap = &table->map[seq];    
+  }
+
+  return (TRUE);
+}
+
+int AstromOffsetTableNewMap (AstromOffsetTable *table, int order, Image *image) {
+
+  int Nmap = table->Nmap;
+
+  table->Nmap++;
+  REALLOCATE (table->map, AstromOffsetMap, table->Nmap);
+
+  int Nx = order;
+  int Ny = order;
+
+  if (image->imageID > table->MaxID) {
+    int oldMaxID = table->MaxID;
+    table->MaxID = image->imageID;
+    REALLOCATE (table->IDtoSeq, int, table->MaxID + 1);
+    for (i = oldMaxID + 1; i < table->MaxID + 1; i++) {
+      table->IDtoSeq[i] = -1;
+    }
+  }
+  myAssert (table->IDtoSeq[image->imageID] == -1, "table IDtoSeq not initiazed or image collision");
+  table->IDtoSeq[image->imageID] = Nmap;
+  
+  table->map[Nmap].Nx      = Nx;
+  table->map[Nmap].Ny      = Ny;
+  table->map[Nmap].ID      = table->maxID; table->maxID ++;
+  table->map[Nmap].imageID = image->imageID;
+  
+  table->map[Nmap].dX = Nx / image->Nx;
+  table->map[Nmap].dY = Ny / image->Ny;
+
+  ALLOCATE (table->map[Nmap].dXv, float *, Nx);
+  ALLOCATE (table->map[Nmap].dYv, float *, Nx);
+
+  for (j = 0; Nx; j++) {
+    ALLOCATE (table->map[i].dXv[j], float, Ny);
+    ALLOCATE (table->map[i].dYv[j], float, Ny);
+
+    for (k = 0; k < Ny; k++) {
+      table->map[i].dXv[j][k] = 0.0;
+      table->map[i].dYv[j][k] = 0.0;
+    }
+  }
+  image[0].coords.offsetMap = &table->map[Nmap];
+  return TRUE;    
+}
+
Index: /trunk/Ohana/src/libdvo/src/coord_systems.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/coord_systems.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/coord_systems.c	(revision 37807)
@@ -1,4 +1,3 @@
 # include "dvo.h"
-# include "libdvo_astro.h"
     
 CoordTransform *InitTransform (CoordTransformSystem input, CoordTransformSystem output) {
@@ -40,6 +39,6 @@
 	  // \delta^p_J = +27d 07' 41.7087"
 	  // \theta_J   = 122.93192526 deg
+
 	  // these differ from the above at the several milliarcsec level
-
 
 	  // These values were used in the past, but transform to/from B1950.0
@@ -47,4 +46,20 @@
 	  // transform->Xo  = 282.25*RAD_DEG;
 	  // transform->xo  =  33.00;
+
+	  break;
+	case COORD_GALACTIC_REID_2004:
+
+	  // J2000 FK5 transformation from Reid et al 2004, ApJ 616, 872
+	  transform->phi = (27.128336111 - 90.0)*RAD_DEG; // +27d 07' 42.01" (J2000) 
+	  transform->Xo  = (192.859508333 + 90.0)*RAD_DEG; // 12h 51m 26.282s (J2000)
+	  transform->xo  =  32.932;
+
+	  // comments from LSD code (lsd/builtins/misc.py, Mario Juric)
+	  // # Appendix of Reid et al. (http://adsabs.harvard.edu/cgi-bin/bib_query?2004ApJ...616..872R)
+	  // # This convention is also used by LAMBDA/WMAP (http://lambda.gsfc.nasa.gov/toolbox/tb_coordconv.cfm)
+
+	  // _angp = np.radians(192.859508333) #  12h 51m 26.282s (J2000)
+	  // _dngp = np.radians(27.128336111)  # +27d 07' 42.01" (J2000) 
+	  // _l0   = np.radians(32.932)
 
 	  break;
@@ -87,8 +102,33 @@
       switch (output) {
 	case COORD_CELESTIAL:
-	  // J2000 transformation from Liu et al 2011 ()
+	  // J2000 transformation from Liu et al 2011 (A&A 526, A16)
 	  transform->phi =  62.8717488056*RAD_DEG;
 	  transform->Xo  =  32.9319185700*RAD_DEG;
 	  transform->xo  = 282.8594812080;
+
+	  // These values were used in the past, but transform to/from B1950.0
+	  // transform->phi =  62.60*RAD_DEG;
+	  // transform->Xo  =  33.00*RAD_DEG;
+	  // transform->xo  = 282.25;
+	  break;
+	case COORD_GALACTIC:
+	  transform->phi = 0.0;
+	  transform->Xo  = 0.0;
+	  transform->xo  = 0.0;
+	  transform->isIdentity = TRUE;
+	  break;
+	case COORD_ECLIPTIC:
+	  return NULL;
+	default:
+	  abort();
+      }
+      break;
+    case COORD_GALACTIC_REID_2004:
+      switch (output) {
+	case COORD_CELESTIAL:
+	  // J2000 FK5 transformation from Reid et al 2004, ApJ 616, 872
+	  transform->phi = (90.0 - 27.128336111)*RAD_DEG; // +27d 07' 42.01" (J2000) 
+	  transform->Xo  =  32.932*RAD_DEG;
+	  transform->xo  = (192.859508333 + 90.0); // 12h 51m 26.282s (J2000)
 
 	  // These values were used in the past, but transform to/from B1950.0
@@ -144,10 +184,16 @@
   Y *= RAD_DEG;
 
+  double cosY = cos(Y);
+  double sinY = sin(Y);
+  
+  double cosX = cos(X);
+  double sinX = sin(X);
+
   // recast with constants extracted:
-  sin_y = cos(Y)*sin(X)*transform->sin_phi_cos_Xo - cos(Y)*cos(X)*transform->sin_phi_sin_Xo + sin(Y)*transform->cos_phi;
+  sin_y = cosY*sinX*transform->sin_phi_cos_Xo - cosY*cosX*transform->sin_phi_sin_Xo + sinY*transform->cos_phi;
   cos_y = sqrt (1 - sin_y*sin_y);
 
-  sin_x = cos(Y)*sin(X)*transform->cos_phi_cos_Xo - cos(Y)*cos(X)*transform->cos_phi_sin_Xo - sin(Y)*transform->sin_phi;
-  cos_x = cos(Y)*cos(X)*transform->cos_Xo + cos(Y)*sin(X)*transform->sin_Xo;
+  sin_x = cosY*sinX*transform->cos_phi_cos_Xo - cosY*cosX*transform->cos_phi_sin_Xo - sinY*transform->sin_phi;
+  cos_x = cosY*cosX*transform->cos_Xo + cosY*sinX*transform->sin_Xo;
       
   // atan2 returns -pi : +pi
Index: /trunk/Ohana/src/libdvo/src/coordops.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/coordops.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/coordops.c	(revision 37807)
@@ -3,20 +3,51 @@
 /* note that Coords.ctype carries the DEC (ctype2) value */
 
-static Coords mosaic;
-static int gotMosaic = FALSE;
-
-void RegisterMosaic (Coords *coords) {
-  mosaic = *coords;
-  gotMosaic = TRUE;
-}
-
-int isRegisteredMosaic () {
-  return (gotMosaic);
-}
-
-int GetMosaicCoords (Coords *coords) {
-  *coords = mosaic;
-  return (TRUE);
-}
+// default coordinates have unit scale and no rotation
+void InitCoords (Coords *coords, char *projection) {
+  coords->crval1 = 0.0;
+  coords->crval2 = 0.0;
+  coords->crpix1 = 0.0;
+  coords->crpix2 = 0.0;
+
+  coords->cdelt1 = 1.0;
+  coords->cdelt2 = 1.0;
+
+  coords->pc1_1 = 1.0; // EW is flipped relative to sky parity (dRA/dX > 0)
+  coords->pc2_2 = 1.0;
+  coords->pc2_1 = 0.0;
+  coords->pc1_2 = 0.0;
+
+  coords->Npolyterms = 1;
+  coords->mosaic = NULL;
+  coords->offsetMap = NULL;
+
+  memset (coords->polyterms, 0, 7*2*sizeof(float));
+
+  if (projection) {
+    strcpy (coords->ctype, projection);
+  } else {
+    strcpy (coords->ctype, "NONE");
+  }
+} 
+
+// default coordinates have unit scale and no rotation
+void CopyCoords (Coords *tgt, Coords *src) {
+  tgt->crval1     = src->crval1;
+  tgt->crval2     = src->crval2;
+  tgt->crpix1     = src->crpix1;
+  tgt->crpix2     = src->crpix2;
+  tgt->cdelt1     = src->cdelt1;
+  tgt->cdelt2     = src->cdelt2;
+  tgt->pc1_1      = src->pc1_1;
+  tgt->pc2_2      = src->pc2_2;
+  tgt->pc2_1      = src->pc2_1;
+  tgt->pc1_2      = src->pc1_2;
+  tgt->Npolyterms = src->Npolyterms;
+  tgt->mosaic     = src->mosaic;      // pointer to another structure
+  tgt->offsetMap  = src->offsetMap;   // pointer to another structure
+
+  memcpy (tgt->polyterms, src->polyterms, 7*2*sizeof(float));
+  strcpy (tgt->ctype,     src->ctype);
+} 
 
 int XY_to_LM (double *L, double *M, double x, double y, Coords *coords) {
@@ -28,4 +59,17 @@
   proj = GetProjection (coords[0].ctype);
   mode = GetProjectionMode (proj);
+
+  // if we have an image map, it should be applied to the pixel coordinates 
+  // before anything else is done.  It is easier to invert the map if it is 
+  // of the form (dX,dY) = f(X,Y), then if (dL,dM) = f(X,Y).
+  if (coords[0].Npolyterms == -1) {
+    myAssert (coords[0].offsetMap, "offsetMap is requested by not defined");
+    float dX = AstromOffsetMapValue (coords->offsetMap, x, y, TRUE);
+    float dY = AstromOffsetMapValue (coords->offsetMap, x, y, FALSE);
+    if (isnan(dX)) dX = 0.0;
+    if (isnan(dY)) dY = 0.0;
+    x += dX;
+    y += dY;
+  }
 
   /** convert pixel coordinates to cartesian system **/
@@ -78,6 +122,9 @@
     /* mosaic astrometry : WRP is chip astrometry; apply mosaic (DIS) term */
     if (proj == PROJ_WRP) {
-      if (!gotMosaic) return (FALSE);
-      XY_to_RD (ra, dec, L, M, &mosaic);
+      if (!coords->mosaic) {
+	myAbort ("missing mosaic element");
+	return (FALSE);
+      }
+      XY_to_RD (ra, dec, L, M, coords->mosaic);
     }
     return (TRUE);
@@ -149,5 +196,5 @@
     *dec = DEG_RAD*delta;
 
-    *ra = ohana_normalize_angle (*ra);
+    // *ra = ohana_normalize_angle (*ra);
 
     return (TRUE);
@@ -182,5 +229,5 @@
     *dec = delta + coords[0].crval2;
 
-    *ra = ohana_normalize_angle (*ra);
+    // *ra = ohana_normalize_angle (*ra);
 
     return (TRUE);
@@ -208,6 +255,9 @@
   if (mode == PROJ_MODE_CARTESIAN) {
     if (proj == PROJ_WRP) {
-      if (!gotMosaic) return (FALSE);
-      RD_to_XY (L, M, ra, dec, &mosaic);
+      if (!coords->mosaic) {
+	myAbort ("missing mosaic element");
+	return (FALSE);
+      }
+      RD_to_XY (L, M, ra, dec, coords->mosaic);
       return (TRUE);
     }
@@ -381,4 +431,43 @@
   *y = Yo / coords[0].cdelt2 + coords[0].crpix2;
 
+  // if we have an image map, it should be applied to the pixel coordinates 
+  // after anything else is done.  It is easier to invert the map if it is 
+  // of the form (dX,dY) = f(X,Y), then if (dL,dM) = f(X,Y).
+  if (coords[0].Npolyterms == -1) {
+    myAssert (coords[0].offsetMap, "offsetMap is requested by not defined");
+
+    double xraw = *x;
+    double yraw = *y;
+
+    double dXo = 0.0;
+    double dYo = 0.0;
+
+    int i;
+    for (i = 0; i < 4; i++) {
+      double dX = AstromOffsetMapValue (coords->offsetMap, xraw, yraw, TRUE);
+      double dY = AstromOffsetMapValue (coords->offsetMap, xraw, yraw, FALSE);
+
+      if (isnan(dX)) dX = 0.0;
+      if (isnan(dY)) dY = 0.0;
+
+      dX -= dXo;
+      dY -= dYo;
+
+      xraw -= dX;
+      yraw -= dY;
+
+      dXo += dX;
+      dYo += dY;
+    }
+
+    *x = xraw;
+    *y = yraw;
+
+    // I need to iterate since the position at which I have first made the correction is
+    // not the true position.  but if dX,dY is small and the gradient of dX,dY is also
+    // small, then even a single pass gets us close.
+
+    // XXX test this and use a while (hypot(dX,dY) > XXX) condition 
+  }
   return (TRUE);
 }
@@ -449,10 +538,6 @@
   
   rotate = 0.0;
-  coords[0].crval1 = coords[0].crpix1 = coords[0].cdelt1 = 0.0;
-  coords[0].crval2 = coords[0].crpix2 = coords[0].cdelt2 = 0.0;
-  coords[0].pc1_1 = coords[0].pc2_2 = 1.0;
-  coords[0].pc2_1 = coords[0].pc1_2 = 0.0;
-  coords[0].Npolyterms = 1;
-  strcpy (coords[0].ctype, "NONE");
+
+  InitCoords (coords, NULL);
   
   mode = COORD_TYPE_NONE;
@@ -619,5 +704,5 @@
     } 
   }
-
+  
   if (!status) {
     // fprintf (stderr, "error getting all elements for coordinate mode %s\n", coords[0].ctype);
Index: /trunk/Ohana/src/libdvo/src/dbExtractAverages.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dbExtractAverages.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dbExtractAverages.c	(revision 37807)
@@ -50,5 +50,5 @@
 
 /* return average.field based on the selection */
-dbValue dbExtractAverages (Average *average, SecFilt *secfilt, Measure *measure, dbField *field) {
+dbValue dbExtractAverages (Average *average, SecFilt *secfilt, Measure *measure, Lensobj *lensobj, StarPar *starpar, dbField *field) {
 
   // off_t i;
@@ -161,6 +161,9 @@
       value.Flt = average[0].stargal;
       break;
-    case AVE_REF_COLOR:
+    case AVE_REF_COLOR_BLUE:
       value.Flt = average[0].refColorBlue;
+      break;
+    case AVE_REF_COLOR_RED:
+      value.Flt = average[0].refColorRed;
       break;
 
@@ -306,7 +309,4 @@
 	  value.Flt = PhotUCdist (field->photcode, average, secfilt);
 	  break;
-	// XX case MAG_OPTION_STACK_DET_ID:
-	// XX   value.Int = PhotStackID (field->photcode, average, secfilt);
-	// XX   break;
 	case MAG_OPTION_FLAGS: {
 	  int Nsec = GetPhotcodeNsec (field->photcode->code);
@@ -315,8 +315,50 @@
 	  break;
 	}
+
+	  // g:X11_SM_OBJ, etc, are only valid for average 
+	case MAG_OPTION_X11_SM_OBJ: { value.Flt = LensValue_X11_sm_obj (field->photcode, lensobj); break; }
+	case MAG_OPTION_X12_SM_OBJ: { value.Flt = LensValue_X12_sm_obj (field->photcode, lensobj); break; }
+	case MAG_OPTION_X22_SM_OBJ: { value.Flt = LensValue_X22_sm_obj (field->photcode, lensobj); break; }
+	case MAG_OPTION_E1_SM_OBJ:  { value.Flt = LensValue_E1_sm_obj  (field->photcode, lensobj); break; }
+	case MAG_OPTION_E2_SM_OBJ:  { value.Flt = LensValue_E2_sm_obj  (field->photcode, lensobj); break; }
+
+	case MAG_OPTION_X11_SH_OBJ: { value.Flt = LensValue_X11_sh_obj (field->photcode, lensobj); break; }
+	case MAG_OPTION_X12_SH_OBJ: { value.Flt = LensValue_X12_sh_obj (field->photcode, lensobj); break; }
+	case MAG_OPTION_X22_SH_OBJ: { value.Flt = LensValue_X22_sh_obj (field->photcode, lensobj); break; }
+	case MAG_OPTION_E1_SH_OBJ:  { value.Flt = LensValue_E1_sh_obj  (field->photcode, lensobj); break; }
+	case MAG_OPTION_E2_SH_OBJ:  { value.Flt = LensValue_E2_sh_obj  (field->photcode, lensobj); break; }
+
+	case MAG_OPTION_X11_SM_PSF: { value.Flt = LensValue_X11_sm_psf (field->photcode, lensobj); break; }
+	case MAG_OPTION_X12_SM_PSF: { value.Flt = LensValue_X12_sm_psf (field->photcode, lensobj); break; }
+	case MAG_OPTION_X22_SM_PSF: { value.Flt = LensValue_X22_sm_psf (field->photcode, lensobj); break; }
+	case MAG_OPTION_E1_SM_PSF:  { value.Flt = LensValue_E1_sm_psf  (field->photcode, lensobj); break; }
+	case MAG_OPTION_E2_SM_PSF:  { value.Flt = LensValue_E2_sm_psf  (field->photcode, lensobj); break; }
+
+	case MAG_OPTION_X11_SH_PSF: { value.Flt = LensValue_X11_sh_psf (field->photcode, lensobj); break; }
+	case MAG_OPTION_X12_SH_PSF: { value.Flt = LensValue_X12_sh_psf (field->photcode, lensobj); break; }
+	case MAG_OPTION_X22_SH_PSF: { value.Flt = LensValue_X22_sh_psf (field->photcode, lensobj); break; }
+	case MAG_OPTION_E1_SH_PSF:  { value.Flt = LensValue_E1_sh_psf  (field->photcode, lensobj); break; }
+	case MAG_OPTION_E2_SH_PSF:  { value.Flt = LensValue_E2_sh_psf  (field->photcode, lensobj); break; }
+
 	case MAG_OPTION_NONE:
 	  break;
-      }
-      break;
+
+	default:
+	  break;
+      }
+      break;
+
+    case AVE_E_BV:             { value.Flt = starpar->Ebv;      break; }
+    case AVE_E_BV_ERR:         { value.Flt = starpar->dEbv;     break; }
+    case AVE_DISTANCE_MOD:     { value.Flt = starpar->DistMag;  break; }
+    case AVE_DISTANCE_MOD_ERR: { value.Flt = starpar->dDistMag; break; }
+    case AVE_M_R:     	       { value.Flt = starpar->M_r;      break; }
+    case AVE_M_R_ERR: 	       { value.Flt = starpar->dM_r;     break; }
+    case AVE_FEH:     	       { value.Flt = starpar->FeH;      break; }
+    case AVE_FEH_ERR: 	       { value.Flt = starpar->dFeH;     break; }
+    case AVE_URA_GALMODEL:     { value.Flt = starpar->uRA;      break; }
+    case AVE_UDEC_GALMODEL:    { value.Flt = starpar->uDEC;     break; }
+    case AVE_RA_GALMODEL:      { value.Flt = starpar->R;        break; }
+    case AVE_DEC_GALMODEL:     { value.Flt = starpar->D;        break; }
 
     case AVE_TYPE:
@@ -324,4 +366,6 @@
     case AVE_TYPEFRAC:
       break;
+
+
   }
   return (value);
Index: /trunk/Ohana/src/libdvo/src/dbExtractImages.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dbExtractImages.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dbExtractImages.c	(revision 37807)
@@ -59,5 +59,4 @@
   time_t t;
   dbValue value;
-  off_t Nmosaic;
 
   value.Flt = NAN;
@@ -68,5 +67,4 @@
     case IMAGE_RA:
       if (!haveCelestial) {
-	if (!FindMosaicForImage (image, Nimage, N)) return value;
 	if (!strcmp(&image[N].coords.ctype[4], "-DIS")) {
 	  x = 0.0;
@@ -83,5 +81,4 @@
     case IMAGE_DEC:
       if (!haveCelestial) {
-	if (!FindMosaicForImage (image, Nimage, N)) return value;
 	if (!strcmp(&image[N].coords.ctype[4], "-DIS")) {
 	  x = 0.0;
@@ -99,5 +96,4 @@
       if (!haveGalactic) {
 	if (!haveCelestial) {
-	  if (!FindMosaicForImage (image, Nimage, N)) return value;
 	  if (!strcmp(&image[N].coords.ctype[4], "-DIS")) {
 	    x = 0.0;
@@ -118,5 +114,4 @@
       if (!haveGalactic) {
 	if (!haveCelestial) {
-	  if (!FindMosaicForImage (image, Nimage, N)) return value;
 	  if (!strcmp(&image[N].coords.ctype[4], "-DIS")) {
 	    x = 0.0;
@@ -137,5 +132,4 @@
       if (!haveEcliptic) {
 	if (!haveCelestial) {
-	  if (!FindMosaicForImage (image, Nimage, N)) return value;
 	  if (!strcmp(&image[N].coords.ctype[4], "-DIS")) {
 	    x = 0.0;
@@ -156,5 +150,4 @@
       if (!haveEcliptic) {
 	if (!haveCelestial) {
-	  if (!FindMosaicForImage (image, Nimage, N)) return value;
 	  if (!strcmp(&image[N].coords.ctype[4], "-DIS")) {
 	    x = 0.0;
@@ -269,17 +262,14 @@
 
     case IMAGE_FWHM_MEDIAN:
-      if (!(Nmosaic = FindMosaicForImage (image, Nimage, N))) return value;
-      Nmosaic --; // XXX kind of a hack: FindMosaicForImage returns 0 or the mosaic seq number + 1
-      value.Flt = (image[Nmosaic].fwhm_x + image[Nmosaic].fwhm_y) / 50.0;
+      if (!image[N].parent) return value;
+      value.Flt = (image[N].parent->fwhm_x + image[N].parent->fwhm_y) / 50.0;
       break;
     case IMAGE_FWHM_MAJ_MEDIAN:
-      if (!(Nmosaic = FindMosaicForImage (image, Nimage, N))) return value;
-      Nmosaic --; // XXX kind of a hack: FindMosaicForImage returns 0 or the mosaic seq number + 1
-      value.Flt = image[Nmosaic].fwhm_x / 25.0;
+      if (!image[N].parent) return value;
+      value.Flt = image[N].parent->fwhm_x / 25.0;
       break;
     case IMAGE_FWHM_MIN_MEDIAN:
-      if (!(Nmosaic = FindMosaicForImage (image, Nimage, N))) return value;
-      Nmosaic --; // XXX kind of a hack: FindMosaicForImage returns 0 or the mosaic seq number + 1
-      value.Flt = image[Nmosaic].fwhm_y / 25.0;
+      if (!image[N].parent) return value;
+      value.Flt = image[N].parent->fwhm_y / 25.0;
       break;
 
@@ -375,6 +365,9 @@
       break;
 
-    case IMAGE_REF_COLOR:
+    case IMAGE_REF_COLOR_BLUE:
       value.Flt = image[N].refColorBlue;
+      break;
+    case IMAGE_REF_COLOR_RED:
+      value.Flt = image[N].refColorRed;
       break;
   }
Index: /trunk/Ohana/src/libdvo/src/dbExtractMeasures.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dbExtractMeasures.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dbExtractMeasures.c	(revision 37807)
@@ -85,5 +85,5 @@
 
 /* return measure.field based on the selection */
-dbValue dbExtractMeasures (Average *average, SecFilt *secfilt, Measure *measure, dbField *field) {
+dbValue dbExtractMeasures (Average *average, SecFilt *secfilt, Measure *measure, Lensing *lensing, StarPar *starpar, dbField *field) {
 
   int Nsec;
@@ -249,4 +249,33 @@
 	case MAG_OPTION_UC_DIST:
 	case MAG_OPTION_FLAGS:
+	case MAG_OPTION_X11_SM_OBJ: 
+	case MAG_OPTION_X12_SM_OBJ: 
+	case MAG_OPTION_X22_SM_OBJ: 
+	case MAG_OPTION_E1_SM_OBJ: 
+	case MAG_OPTION_E2_SM_OBJ: 
+	case MAG_OPTION_X11_SH_OBJ: 
+	case MAG_OPTION_X12_SH_OBJ: 
+	case MAG_OPTION_X22_SH_OBJ: 
+	case MAG_OPTION_E1_SH_OBJ: 
+	case MAG_OPTION_E2_SH_OBJ: 
+	case MAG_OPTION_X11_SM_PSF: 
+	case MAG_OPTION_X12_SM_PSF: 
+	case MAG_OPTION_X22_SM_PSF: 
+	case MAG_OPTION_E1_SM_PSF: 
+	case MAG_OPTION_E2_SM_PSF: 
+	case MAG_OPTION_X11_SH_PSF: 
+	case MAG_OPTION_X12_SH_PSF: 
+	case MAG_OPTION_X22_SH_PSF: 
+	case MAG_OPTION_E1_SH_PSF: 
+	case MAG_OPTION_E2_SH_PSF: 
+	case MAG_OPTION_F_AP_R5: 
+	case MAG_OPTION_F_ERR_AP_R5: 
+	case MAG_OPTION_F_STDEV_AP_R5: 
+	case MAG_OPTION_F_FILL_AP_R5: 
+	case MAG_OPTION_F_AP_R6: 
+	case MAG_OPTION_F_ERR_AP_R6: 
+	case MAG_OPTION_F_STDEV_AP_R6: 
+	case MAG_OPTION_F_FILL_AP_R6: 
+
 	case MAG_OPTION_NONE:
 	  break;
@@ -474,6 +503,9 @@
       break;
 
-    case MEAS_REF_COLOR:
+    case MEAS_REF_COLOR_BLUE:
       value.Flt = average[0].refColorBlue;
+      break;
+    case MEAS_REF_COLOR_RED:
+      value.Flt = average[0].refColorRed;
       break;
 
@@ -528,19 +560,16 @@
 # endif
       break;
-    case MEAS_XFIX: /* OK */
-      value.Flt = measure[0].Xfix;
-      break;
-    case MEAS_YFIX: /* OK */
-      value.Flt = measure[0].Yfix;
-      break;
-    case MEAS_XCCD_ERR: /* OK */
-      value.Flt = FromShortPixels(measure[0].dXccd);
-      break;
-    case MEAS_YCCD_ERR: /* OK */
-      value.Flt = FromShortPixels(measure[0].dYccd);
-      break;
-    case MEAS_POS_SYS_ERR: /* OK */
-      value.Flt = FromShortPixels(measure[0].dRsys);
-      break;
+    case MEAS_XFIX: { value.Flt = measure[0].Xfix; break; }
+    case MEAS_YFIX: { value.Flt = measure[0].Yfix; break; }
+    case MEAS_XCCD_ERR: { value.Flt = FromShortPixels(measure[0].dXccd); break; }
+    case MEAS_YCCD_ERR: { value.Flt = FromShortPixels(measure[0].dYccd); break; }
+    case MEAS_POS_SYS_ERR: { value.Flt = FromShortPixels(measure[0].dRsys); }
+
+    case MEAS_XOFF_KH:  { value.Flt = measure[0].XoffKH;  break; }
+    case MEAS_YOFF_KH:  { value.Flt = measure[0].YoffKH;  break; }
+    case MEAS_XOFF_DCR: { value.Flt = measure[0].XoffDCR; break; }
+    case MEAS_YOFF_DCR: { value.Flt = measure[0].YoffDCR; break; }
+    case MEAS_ROFF_GAL: { value.Flt = measure[0].RoffGAL; break; }
+    case MEAS_DOFF_GAL: { value.Flt = measure[0].DoffGAL; break; }
 
     case MEAS_XFIELD: /* offset relative to exposure center in ra,dec space */
@@ -581,12 +610,5 @@
     case MEAS_XMOSAIC: /* offset relative to exposure center in camera coords */
       if (!haveMosaicMeas) {
-	if (REMOTE_CLIENT) {
-	  mosaic = MatchMosaicMetadata (measure[0].imageID);
-	} else {
-	  // fprintf (stderr, "non-parallel Xmos broken\n");
-	  // abort();
-	  // mosaic = MatchMosaic (measure[0].t, measure[0].photcode);
-	  mosaic = MatchMosaicMetadata (measure[0].imageID);
-	}
+	mosaic = MatchMosaicMetadata (measure[0].imageID);
 	if (mosaic == NULL) break;
 	double Rm = measure[0].R;
@@ -598,12 +620,5 @@
     case MEAS_YMOSAIC: /* OK */
       if (!haveMosaicMeas) {
-	if (REMOTE_CLIENT) {
-	  mosaic = MatchMosaicMetadata (measure[0].imageID);
-	} else {
-	  // fprintf (stderr, "non-parallel Xmos broken\n");
-	  // abort();
-	  // mosaic = MatchMosaic (measure[0].t, measure[0].photcode);
-	  mosaic = MatchMosaicMetadata (measure[0].imageID);
-	}
+	mosaic = MatchMosaicMetadata (measure[0].imageID);
 	if (mosaic == NULL) break;
 	double Rm = measure[0].R;
@@ -680,7 +695,5 @@
       break;
 
-    case MEAS_MCAL_OFFSET: /* OK */
-      value.Flt = measure[0].Mcal;
-      break;
+    case MEAS_MCAL_OFFSET: { value.Flt = measure[0].Mcal; break; }
 
     case MEAS_FLAT: /* OK */
@@ -717,4 +730,17 @@
       }
       break;
+
+    case MEAS_E_BV:             { value.Flt = starpar->Ebv;      break; }
+    case MEAS_E_BV_ERR:         { value.Flt = starpar->dEbv;     break; }
+    case MEAS_DISTANCE_MOD:     { value.Flt = starpar->DistMag;  break; }
+    case MEAS_DISTANCE_MOD_ERR: { value.Flt = starpar->dDistMag; break; }
+    case MEAS_M_R:              { value.Flt = starpar->M_r;      break; }
+    case MEAS_M_R_ERR:          { value.Flt = starpar->dM_r;     break; }
+    case MEAS_FEH:              { value.Flt = starpar->FeH;      break; }
+    case MEAS_FEH_ERR:          { value.Flt = starpar->dFeH;     break; }
+    case MEAS_URA_GALMODEL:     { value.Flt = starpar->uRA;      break; }
+    case MEAS_UDEC_GALMODEL:    { value.Flt = starpar->uDEC;     break; }
+    case MEAS_RA_GALMODEL:      { value.Flt = starpar->R;        break; }
+    case MEAS_DEC_GALMODEL:     { value.Flt = starpar->D;        break; }
 
     // case MEAS_FLUX_PSF: /* OK */
Index: /trunk/Ohana/src/libdvo/src/dbFields.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dbFields.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dbFields.c	(revision 37807)
@@ -28,8 +28,108 @@
 }
 
+int dbFieldNeedMeasure (dbField *fields, int Nfields) {
+  int i;
+  for (i = 0; i < Nfields; i++) {
+    if (fields[i].photcode == NULL) continue; // non-measure fields do not have a photcode
+    if (fields[i].photcode[0].type == PHOT_REF) return TRUE;
+    if (fields[i].photcode[0].type == PHOT_DEP) return TRUE;
+  }
+  return FALSE;
+}
+
+int dbFieldNeedLensing (dbField *fields, int Nfields) {
+  int i;
+  for (i = 0; i < Nfields; i++) {
+    if (fields[i].ID == MEAS_X11_SM_OBJ) return TRUE;
+    if (fields[i].ID == MEAS_X12_SM_OBJ) return TRUE;
+    if (fields[i].ID == MEAS_X22_SM_OBJ) return TRUE;
+    if (fields[i].ID == MEAS_E1_SM_OBJ)  return TRUE;
+    if (fields[i].ID == MEAS_E2_SM_OBJ)  return TRUE;
+    if (fields[i].ID == MEAS_X11_SH_OBJ) return TRUE;
+    if (fields[i].ID == MEAS_X12_SH_OBJ) return TRUE;
+    if (fields[i].ID == MEAS_X22_SH_OBJ) return TRUE;
+    if (fields[i].ID == MEAS_E1_SH_OBJ)  return TRUE;
+    if (fields[i].ID == MEAS_E2_SH_OBJ)  return TRUE;
+    if (fields[i].ID == MEAS_X11_SM_PSF) return TRUE;
+    if (fields[i].ID == MEAS_X12_SM_PSF) return TRUE;
+    if (fields[i].ID == MEAS_X22_SM_PSF) return TRUE;
+    if (fields[i].ID == MEAS_E1_SM_PSF)  return TRUE;
+    if (fields[i].ID == MEAS_E2_SM_PSF)  return TRUE;
+    if (fields[i].ID == MEAS_X11_SH_PSF) return TRUE;
+    if (fields[i].ID == MEAS_X12_SH_PSF) return TRUE;
+    if (fields[i].ID == MEAS_X22_SH_PSF) return TRUE;
+    if (fields[i].ID == MEAS_E1_SH_PSF)  return TRUE;
+    if (fields[i].ID == MEAS_E2_SH_PSF)  return TRUE;
+  }
+  return FALSE;
+}
+
+int dbFieldNeedLensobj (dbField *fields, int Nfields) {
+  int i;
+  for (i = 0; i < Nfields; i++) {
+    if (fields[i].magOption == MAG_OPTION_NONE) continue; // non-measure fields do not have a photcode
+
+    if (fields[i].magOption == MAG_OPTION_X11_SM_OBJ) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X12_SM_OBJ) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X22_SM_OBJ) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E1_SM_OBJ)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E2_SM_OBJ)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X11_SH_OBJ) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X12_SH_OBJ) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X22_SH_OBJ) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E1_SH_OBJ)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E2_SH_OBJ)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X11_SM_PSF) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X12_SM_PSF) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X22_SM_PSF) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E1_SM_PSF)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E2_SM_PSF)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X11_SH_PSF) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X12_SH_PSF) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_X22_SH_PSF) return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E1_SH_PSF)  return TRUE;
+    if (fields[i].magOption == MAG_OPTION_E2_SH_PSF)  return TRUE;
+  }
+  return FALSE;
+}
+
+int dbFieldNeedStarpar (dbField *fields, int Nfields, int isAverage) {
+  int i;
+  for (i = 0; i < Nfields; i++) {
+    if (isAverage) {
+      if (fields[i].ID == AVE_E_BV)             return TRUE;
+      if (fields[i].ID == AVE_E_BV_ERR)         return TRUE;
+      if (fields[i].ID == AVE_DISTANCE_MOD)     return TRUE;
+      if (fields[i].ID == AVE_DISTANCE_MOD_ERR) return TRUE;
+      if (fields[i].ID == AVE_M_R)              return TRUE;
+      if (fields[i].ID == AVE_M_R_ERR)          return TRUE;
+      if (fields[i].ID == AVE_FEH)              return TRUE;
+      if (fields[i].ID == AVE_FEH_ERR)          return TRUE;
+      if (fields[i].ID == AVE_URA_GALMODEL)     return TRUE;
+      if (fields[i].ID == AVE_UDEC_GALMODEL)    return TRUE;
+      if (fields[i].ID == AVE_RA_GALMODEL)      return TRUE;
+      if (fields[i].ID == AVE_DEC_GALMODEL)     return TRUE;
+    } else {
+      if (fields[i].ID == MEAS_E_BV)             return TRUE;
+      if (fields[i].ID == MEAS_E_BV_ERR)         return TRUE;
+      if (fields[i].ID == MEAS_DISTANCE_MOD)     return TRUE;
+      if (fields[i].ID == MEAS_DISTANCE_MOD_ERR) return TRUE;
+      if (fields[i].ID == MEAS_M_R)              return TRUE;
+      if (fields[i].ID == MEAS_M_R_ERR)          return TRUE;
+      if (fields[i].ID == MEAS_FEH)              return TRUE;
+      if (fields[i].ID == MEAS_FEH_ERR)          return TRUE;
+      if (fields[i].ID == MEAS_URA_GALMODEL)     return TRUE;
+      if (fields[i].ID == MEAS_UDEC_GALMODEL)    return TRUE;
+      if (fields[i].ID == MEAS_RA_GALMODEL)      return TRUE;
+      if (fields[i].ID == MEAS_DEC_GALMODEL)     return TRUE;
+    }
+  }
+  return FALSE;
+}
+
 dvoMagSourceType GetMagSource (char *string) {
 
-  if (!strcasecmp (string, "chip")) return (MAG_SRC_CHP);
-  if (!strcasecmp (string, "warp")) return (MAG_SRC_WRP);
+  if (!strcasecmp (string, "chip"))  return (MAG_SRC_CHP);
+  if (!strcasecmp (string, "warp"))  return (MAG_SRC_WRP);
   if (!strcasecmp (string, "stack")) return (MAG_SRC_STK);
 
@@ -66,89 +166,48 @@
 dvoMagOptionType GetMagOption (char *string) {
 
-  if (!strcasecmp (string, "mag"))          return (MAG_OPTION_MAG);
-  if (!strcasecmp (string, "err"))          return (MAG_OPTION_ERR);
-  if (!strcasecmp (string, "magerr"))       return (MAG_OPTION_ERR);
-  if (!strcasecmp (string, "flux"))         return (MAG_OPTION_FLUX);
-  if (!strcasecmp (string, "fluxerr"))      return (MAG_OPTION_FLUX_ERR);
-  if (!strcasecmp (string, "stdev"))        return (MAG_OPTION_STDEV);
-  if (!strcasecmp (string, "chisq"))        return (MAG_OPTION_CHISQ);
-  if (!strcasecmp (string, "min"))          return (MAG_OPTION_MIN);
-  if (!strcasecmp (string, "max"))          return (MAG_OPTION_MAX);
-  if (!strcasecmp (string, "ncode"))        return (MAG_OPTION_NCODE);
-  if (!strcasecmp (string, "nphot"))        return (MAG_OPTION_NPHOT);
-  if (!strcasecmp (string, "uc_dist"))      return (MAG_OPTION_UC_DIST);
-  if (!strcasecmp (string, "flags"))        return (MAG_OPTION_FLAGS);
+  if (!strcasecmp (string, "mag"))           return MAG_OPTION_MAG;
+  if (!strcasecmp (string, "err"))           return MAG_OPTION_ERR;
+  if (!strcasecmp (string, "magerr"))        return MAG_OPTION_ERR;
+  if (!strcasecmp (string, "flux"))          return MAG_OPTION_FLUX;
+  if (!strcasecmp (string, "fluxerr"))       return MAG_OPTION_FLUX_ERR;
+  if (!strcasecmp (string, "stdev"))         return MAG_OPTION_STDEV;
+  if (!strcasecmp (string, "chisq"))         return MAG_OPTION_CHISQ;
+  if (!strcasecmp (string, "min"))           return MAG_OPTION_MIN;
+  if (!strcasecmp (string, "max"))           return MAG_OPTION_MAX;
+  if (!strcasecmp (string, "ncode"))         return MAG_OPTION_NCODE;
+  if (!strcasecmp (string, "nphot"))         return MAG_OPTION_NPHOT;
+  if (!strcasecmp (string, "uc_dist"))       return MAG_OPTION_UC_DIST;
+  if (!strcasecmp (string, "flags"))         return MAG_OPTION_FLAGS;
+  if (!strcasecmp (string, "X11_SM_OBJ"))    return MAG_OPTION_X11_SM_OBJ; 
+  if (!strcasecmp (string, "X12_SM_OBJ"))    return MAG_OPTION_X12_SM_OBJ; 
+  if (!strcasecmp (string, "X22_SM_OBJ"))    return MAG_OPTION_X22_SM_OBJ; 
+  if (!strcasecmp (string, "E1_SM_OBJ"))     return MAG_OPTION_E1_SM_OBJ; 
+  if (!strcasecmp (string, "E2_SM_OBJ"))     return MAG_OPTION_E2_SM_OBJ; 
+  if (!strcasecmp (string, "X11_SH_OBJ"))    return MAG_OPTION_X11_SH_OBJ; 
+  if (!strcasecmp (string, "X12_SH_OBJ"))    return MAG_OPTION_X12_SH_OBJ; 
+  if (!strcasecmp (string, "X22_SH_OBJ"))    return MAG_OPTION_X22_SH_OBJ; 
+  if (!strcasecmp (string, "E1_SH_OBJ"))     return MAG_OPTION_E1_SH_OBJ; 
+  if (!strcasecmp (string, "E2_SH_OBJ"))     return MAG_OPTION_E2_SH_OBJ; 
+  if (!strcasecmp (string, "X11_SM_PSF"))    return MAG_OPTION_X11_SM_PSF; 
+  if (!strcasecmp (string, "X12_SM_PSF"))    return MAG_OPTION_X12_SM_PSF; 
+  if (!strcasecmp (string, "X22_SM_PSF"))    return MAG_OPTION_X22_SM_PSF; 
+  if (!strcasecmp (string, "E1_SM_PSF"))     return MAG_OPTION_E1_SM_PSF; 
+  if (!strcasecmp (string, "E2_SM_PSF"))     return MAG_OPTION_E2_SM_PSF; 
+  if (!strcasecmp (string, "X11_SH_PSF"))    return MAG_OPTION_X11_SH_PSF; 
+  if (!strcasecmp (string, "X12_SH_PSF"))    return MAG_OPTION_X12_SH_PSF; 
+  if (!strcasecmp (string, "X22_SH_PSF"))    return MAG_OPTION_X22_SH_PSF; 
+  if (!strcasecmp (string, "E1_SH_PSF"))     return MAG_OPTION_E1_SH_PSF; 
+  if (!strcasecmp (string, "E2_SH_PSF"))     return MAG_OPTION_E2_SH_PSF; 
+  // if (!strcasecmp (string, "F_AP_R5"))       return MAG_OPTION_F_AP_R5; 
+  // if (!strcasecmp (string, "F_ERR_AP_R5"))   return MAG_OPTION_F_ERR_AP_R5; 
+  // if (!strcasecmp (string, "F_STDEV_AP_R5")) return MAG_OPTION_F_STDEV_AP_R5; 
+  // if (!strcasecmp (string, "F_FILL_AP_R5"))  return MAG_OPTION_F_FILL_AP_R5; 
+  // if (!strcasecmp (string, "F_AP_R6"))       return MAG_OPTION_F_AP_R6; 
+  // if (!strcasecmp (string, "F_ERR_AP_R6"))   return MAG_OPTION_F_ERR_AP_R6; 
+  // if (!strcasecmp (string, "F_STDEV_AP_R6")) return MAG_OPTION_F_STDEV_AP_R6; 
+  // if (!strcasecmp (string, "F_FILL_AP_R6"))  return MAG_OPTION_F_FILL_AP_R6; 
 
   return MAG_OPTION_NONE;
 }
-
-# if (0)
-int GetMagMode (char *string) {
-
-  // these all imply PSF mags:
-  if (!strcasecmp (string, "inst"))        return (MAG_INST);
-  if (!strcasecmp (string, "cat"))         return (MAG_CAT);
-  if (!strcasecmp (string, "sys"))         return (MAG_SYS);
-  if (!strcasecmp (string, "rel"))         return (MAG_REL);
-  if (!strcasecmp (string, "cal"))         return (MAG_CAL);
-  if (!strcasecmp (string, "ave"))         return (MAG_AVE);
-  if (!strcasecmp (string, "ref"))         return (MAG_REF);
-  if (!strcasecmp (string, "err"))         return (MAG_ERR);
-  if (!strcasecmp (string, "aveerr"))      return (MAG_AVE_ERR);
-
-  // these are explicit PSF mags:
-  if (!strcasecmp (string, "psf_inst"))    return (MAG_INST);
-  if (!strcasecmp (string, "psf_cat"))     return (MAG_CAT);
-  if (!strcasecmp (string, "psf_sys"))     return (MAG_SYS);
-  if (!strcasecmp (string, "psf_rel"))     return (MAG_REL);
-  if (!strcasecmp (string, "psf_cal"))     return (MAG_CAL);
-  if (!strcasecmp (string, "psf_ave"))     return (MAG_AVE);
-  if (!strcasecmp (string, "psf_ref"))     return (MAG_REF);
-  if (!strcasecmp (string, "psf_err"))     return (MAG_ERR);
-  if (!strcasecmp (string, "psf_aveerr"))  return (MAG_AVE_ERR);
-
-  // these are aper mags:
-  if (!strcasecmp (string, "ap"))          return (MAG_APER);
-  if (!strcasecmp (string, "aper"))        return (MAG_APER);
-  if (!strcasecmp (string, "aperinst"))    return (MAG_APER_INST);
-  if (!strcasecmp (string, "aper_inst"))   return (MAG_APER_INST);
-  if (!strcasecmp (string, "aper_cat"))    return (MAG_APER_CAT);
-  if (!strcasecmp (string, "aper_sys"))    return (MAG_APER_SYS);
-  if (!strcasecmp (string, "aper_rel"))    return (MAG_APER_REL);
-  if (!strcasecmp (string, "aper_cal"))    return (MAG_APER_CAL);
-  if (!strcasecmp (string, "aper_ave"))    return (MAG_APER_AVE);
-  if (!strcasecmp (string, "aper_ref"))    return (MAG_APER_REF);
-  if (!strcasecmp (string, "aper_err"))    return (MAG_APER_ERR);
-
-  // these are kron mags:
-  if (!strcasecmp (string, "kron"))        return (MAG_KRON);
-  if (!strcasecmp (string, "kron_inst"))   return (MAG_KRON_INST);
-  if (!strcasecmp (string, "kron_cat"))    return (MAG_KRON_CAT);
-  if (!strcasecmp (string, "kron_sys"))    return (MAG_KRON_SYS);
-  if (!strcasecmp (string, "kron_rel"))    return (MAG_KRON_REL);
-  if (!strcasecmp (string, "kron_cal"))    return (MAG_KRON_CAL);
-  if (!strcasecmp (string, "kron_ave"))    return (MAG_KRON_AVE);
-  if (!strcasecmp (string, "kron_ref"))    return (MAG_KRON_REF);
-  if (!strcasecmp (string, "kron_err"))    return (MAG_KRON_ERR);
-  if (!strcasecmp (string, "kroninst"))    return (MAG_KRON_INST);
-  if (!strcasecmp (string, "kronerr"))     return (MAG_KRON_ERR);
-
-  // other fields:
-  if (!strcasecmp (string, "photflags"))   return (MAG_PHOT_FLAGS);
-  if (!strcasecmp (string, "flags"))       return (MAG_PHOT_FLAGS);
-  if (!strcasecmp (string, "chisq"))       return (MAG_CHISQ);
-  if (!strcasecmp (string, "ncode"))       return (MAG_NCODE);
-  if (!strcasecmp (string, "nphot"))       return (MAG_NPHOT);
-  if (!strcasecmp (string, "stdev"))       return (MAG_STDEV);
-  if (!strcasecmp (string, "min"))         return (MAG_MIN);
-  if (!strcasecmp (string, "max"))         return (MAG_MAX);
-  if (!strcasecmp (string, "ucdist"))      return (MAG_UC_DIST);
-  if (!strcasecmp (string, "fluxpsf"))     return (MAG_FLUX_PSF);
-  if (!strcasecmp (string, "fluxpsferr"))  return (MAG_FLUX_PSF_ERR);
-  if (!strcasecmp (string, "fluxkron"))    return (MAG_FLUX_KRON);
-  if (!strcasecmp (string, "fluxkronerr")) return (MAG_FLUX_KRON_ERR);
-  return (MAG_NONE);
-}
-# endif
 
 // field may be of the form mag:psf:inst:wrp.  except for the first subword, 
@@ -285,4 +344,5 @@
     fprintf (stderr, "ERROR: unknown mag/photocode argument %s\n", word);
     free (word);
+    return FALSE;
   }
 
@@ -414,4 +474,10 @@
   if (!strcasecmp (fieldName, "XFIX"))       	 ESCAPE (MEAS_XFIX,           OPIHI_FLT);
   if (!strcasecmp (fieldName, "YFIX"))       	 ESCAPE (MEAS_YFIX,           OPIHI_FLT);
+  if (!strcasecmp (fieldName, "XOFF_KH"))      	 ESCAPE (MEAS_XOFF_KH,        OPIHI_FLT);
+  if (!strcasecmp (fieldName, "YOFF_KH"))      	 ESCAPE (MEAS_YOFF_KH,        OPIHI_FLT);
+  if (!strcasecmp (fieldName, "XOFF_DCR"))     	 ESCAPE (MEAS_XOFF_DCR,       OPIHI_FLT);
+  if (!strcasecmp (fieldName, "YOFF_DCR"))     	 ESCAPE (MEAS_YOFF_DCR,       OPIHI_FLT);
+  if (!strcasecmp (fieldName, "ROFF_GAL"))       ESCAPE (MEAS_ROFF_GAL,       OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DOFF_GAL"))       ESCAPE (MEAS_DOFF_GAL,       OPIHI_FLT);
   if (!strcasecmp (fieldName, "POS_SYS_ERR"))    ESCAPE (MEAS_POS_SYS_ERR,    OPIHI_FLT);
   if (!strcasecmp (fieldName, "XFIELD"))    	 ESCAPE (MEAS_XFIELD,         OPIHI_FLT);
@@ -441,5 +507,47 @@
   if (!strcasecmp (fieldName, "FLAT"))    	 ESCAPE (MEAS_FLAT,           OPIHI_FLT);
   if (!strcasecmp (fieldName, "CENTER_OFFSET"))  ESCAPE (MEAS_CENTER_OFFSET,  OPIHI_FLT);
-  // if (!strcasecmp (fieldName, "FLUX"))       	 ESCAPE (MEAS_FLUX_PSF,       OPIHI_FLT);
+
+  // individual lensing measurements are not grouped by photcode:
+  if (!strcasecmp (fieldName, "X11_SM_OBJ"))     ESCAPE (MEAS_X11_SM_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X12_SM_OBJ"))     ESCAPE (MEAS_X12_SM_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X22_SM_OBJ"))     ESCAPE (MEAS_X22_SM_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E1_SM_OBJ"))      ESCAPE (MEAS_E1_SM_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E2_SM_OBJ"))      ESCAPE (MEAS_E2_SM_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X11_SH_OBJ"))     ESCAPE (MEAS_X11_SH_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X12_SH_OBJ"))     ESCAPE (MEAS_X12_SH_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X22_SH_OBJ"))     ESCAPE (MEAS_X22_SH_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E1_SH_OBJ"))      ESCAPE (MEAS_E1_SH_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E2_SH_OBJ"))      ESCAPE (MEAS_E2_SH_OBJ,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X11_SM_PSF"))     ESCAPE (MEAS_X11_SM_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X12_SM_PSF"))     ESCAPE (MEAS_X12_SM_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X22_SM_PSF"))     ESCAPE (MEAS_X22_SM_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E1_SM_PSF"))      ESCAPE (MEAS_E1_SM_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E2_SM_PSF"))      ESCAPE (MEAS_E2_SM_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X11_SH_PSF"))     ESCAPE (MEAS_X11_SH_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X12_SH_PSF"))     ESCAPE (MEAS_X12_SH_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "X22_SH_PSF"))     ESCAPE (MEAS_X22_SH_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E1_SH_PSF"))      ESCAPE (MEAS_E1_SH_PSF,  OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E2_SH_PSF"))      ESCAPE (MEAS_E2_SH_PSF,  OPIHI_FLT);
+
+  if (!strcasecmp (fieldName, "E_BV"))             ESCAPE (MEAS_E_BV          ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E_BV_ERR"))         ESCAPE (MEAS_E_BV_ERR      ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DISTANCE_MOD"))     ESCAPE (MEAS_DISTANCE_MOD  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DISTANCE_MOD_ERR")) ESCAPE (MEAS_DISTANCE_MOD_ERR, OPIHI_FLT);
+  if (!strcasecmp (fieldName, "D_M"))     	   ESCAPE (MEAS_DISTANCE_MOD  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "D_M_ERR")) 	   ESCAPE (MEAS_DISTANCE_MOD_ERR, OPIHI_FLT);
+  if (!strcasecmp (fieldName, "M_R"))              ESCAPE (MEAS_M_R           ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "M_R_ERR"))          ESCAPE (MEAS_M_R_ERR       ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "FEH"))              ESCAPE (MEAS_FEH           ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "FEH_ERR"))          ESCAPE (MEAS_FEH_ERR       ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "URA_GALMODEL"))     ESCAPE (MEAS_URA_GALMODEL  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "UDEC_GALMODEL"))    ESCAPE (MEAS_UDEC_GALMODEL ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "RA_GALMODEL"))      ESCAPE (MEAS_RA_GALMODEL   ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DEC_GALMODEL"))     ESCAPE (MEAS_DEC_GALMODEL  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "URA_GAL"))     	   ESCAPE (MEAS_URA_GALMODEL  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "UDEC_GAL"))    	   ESCAPE (MEAS_UDEC_GALMODEL ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "RA_GAL"))      	   ESCAPE (MEAS_RA_GALMODEL   ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DEC_GAL"))     	   ESCAPE (MEAS_DEC_GALMODEL  ,   OPIHI_FLT);
+
+  // if (!strcasecmp (fieldName, "FLUX"))           ESCAPE (MEAS_FLUX_PSF,       OPIHI_FLT);
   // if (!strcasecmp (fieldName, "FLUX_ERR"))       ESCAPE (MEAS_FLUX_PSF_ERR,   OPIHI_FLT);
   // if (!strcasecmp (fieldName, "FLUX_PSF"))       ESCAPE (MEAS_FLUX_PSF,       OPIHI_FLT);
@@ -447,5 +555,6 @@
   // if (!strcasecmp (fieldName, "FLUX_KRON"))      ESCAPE (MEAS_FLUX_KRON,      OPIHI_FLT);
   // if (!strcasecmp (fieldName, "FLUX_KRON_ERR"))  ESCAPE (MEAS_FLUX_KRON_ERR,  OPIHI_FLT);
-  if (!strcasecmp (fieldName, "REF_COLOR"))      ESCAPE (MEAS_REF_COLOR,      OPIHI_FLT);
+  if (!strcasecmp (fieldName, "REF_COLOR_BLUE"))    ESCAPE (MEAS_REF_COLOR_BLUE, OPIHI_FLT);
+  if (!strcasecmp (fieldName, "REF_COLOR_RED"))     ESCAPE (MEAS_REF_COLOR_RED,  OPIHI_FLT);
 
   // for words that don't parse, try a photcode
@@ -522,5 +631,20 @@
   if (!strcasecmp (fieldName, "EXTID_HI"))    ESCAPE (AVE_EXTID_HI,    OPIHI_INT);
   if (!strcasecmp (fieldName, "EXTID_LO"))    ESCAPE (AVE_EXTID_LO,    OPIHI_INT);
-  if (!strcasecmp (fieldName, "REF_COLOR"))   ESCAPE (AVE_REF_COLOR,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "REF_COLOR_BLUE")) ESCAPE (AVE_REF_COLOR_BLUE, OPIHI_FLT);
+  if (!strcasecmp (fieldName, "REF_COLOR_RED"))  ESCAPE (AVE_REF_COLOR_RED,  OPIHI_FLT);
+
+  if (!strcasecmp (fieldName, "E_BV"))             ESCAPE (AVE_E_BV          ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "E_BV_ERR"))         ESCAPE (AVE_E_BV_ERR      ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DISTANCE_MOD"))     ESCAPE (AVE_DISTANCE_MOD  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DISTANCE_MOD_ERR")) ESCAPE (AVE_DISTANCE_MOD_ERR, OPIHI_FLT);
+  if (!strcasecmp (fieldName, "M_R"))              ESCAPE (AVE_M_R           ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "M_R_ERR"))          ESCAPE (AVE_M_R_ERR       ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "FEH"))              ESCAPE (AVE_FEH           ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "FEH_ERR"))          ESCAPE (AVE_FEH_ERR       ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "URA_GALMODEL"))     ESCAPE (AVE_URA_GALMODEL  ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "UDEC_GALMODEL"))    ESCAPE (AVE_UDEC_GALMODEL ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "RA_GALMODEL"))      ESCAPE (AVE_RA_GALMODEL   ,   OPIHI_FLT);
+  if (!strcasecmp (fieldName, "DEC_GALMODEL"))     ESCAPE (AVE_DEC_GALMODEL  ,   OPIHI_FLT);
+
 
   // check for code:mode in photcode name 
@@ -652,5 +776,6 @@
   if (!strcasecmp (fieldName, "NLINK_PHOTOM")) ESCAPE (IMAGE_NLINK_PHOTOM, OPIHI_INT);
   if (!strcasecmp (fieldName, "NLINK_ASTROM")) ESCAPE (IMAGE_NLINK_ASTROM, OPIHI_INT);
-  if (!strcasecmp (fieldName, "REF_COLOR"))    ESCAPE (IMAGE_REF_COLOR,    OPIHI_FLT);
+  if (!strcasecmp (fieldName, "REF_COLOR_BLUE")) ESCAPE (IMAGE_REF_COLOR_BLUE, OPIHI_FLT);
+  if (!strcasecmp (fieldName, "REF_COLOR_RED"))  ESCAPE (IMAGE_REF_COLOR_RED,  OPIHI_FLT);
 
   // for words that don't parse, try a photcode
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog.c	(revision 37807)
@@ -58,4 +58,5 @@
   if (!strcasecmp (catformat, "PS1_V5"))          return (DVO_FORMAT_PS1_V5);
   if (!strcasecmp (catformat, "PS1_REF"))         return (DVO_FORMAT_PS1_REF);
+  if (!strcasecmp (catformat, "PS1_SIM"))         return (DVO_FORMAT_PS1_SIM);
   return (DVO_FORMAT_UNDEF);
 }
@@ -129,4 +130,5 @@
   average->Nlensing        = 0;
   average->Nlensobj        = 0;
+  average->Nstarpar        = 0;
   average->Nextend         = 0;
 
@@ -135,6 +137,6 @@
   average->lensingOffset   = -1;
   average->lensobjOffset   = -1;
+  average->starparOffset   = -1;
   average->extendOffset    = -1;
-  average->paramsOffset    = -1;
 
   average->refColorBlue    = NAN;
@@ -144,4 +146,5 @@
   average->skycellID       = 0;
   average->projectionID    = 0;
+
   average->dummy           = 0;
 
@@ -437,4 +440,25 @@
 
 // init all data, or just catalog data
+void dvo_starpar_init (StarPar *starpar) {
+  starpar->R        = NAN;
+  starpar->D        = NAN;
+  starpar->galLat   = NAN;
+  starpar->galLon   = NAN;
+  starpar->Ebv      = NAN;
+  starpar->dEbv     = NAN;
+  starpar->DistMag  = NAN;
+  starpar->dDistMag = NAN;
+  starpar->M_r      = NAN;
+  starpar->dM_r     = NAN;
+  starpar->FeH      = NAN;
+  starpar->dFeH     = NAN;
+  starpar->uRA      = NAN;
+  starpar->uDEC     = NAN;
+  starpar->averef   = -1;
+  starpar->objID    = -1;
+  starpar->catID    = -1;
+}
+
+// init all data, or just catalog data
 void dvo_catalog_init (Catalog *catalog, int complete) {
 
@@ -462,4 +486,6 @@
   catalog[0].lensobj = NULL; 
 
+  catalog[0].starpar = NULL; 
+
   catalog[0].averageT = NULL;
   catalog[0].measureT = NULL; 
@@ -477,4 +503,6 @@
   catalog[0].Nlensobj = 0;
 
+  catalog[0].Nstarpar = 0;
+
   catalog[0].Naves_disk = 0;
   catalog[0].Nmeas_disk = 0;
@@ -485,4 +513,6 @@
   catalog[0].Nlensobj_disk = 0;
 
+  catalog[0].Nstarpar_disk = 0;
+
   catalog[0].Naves_off  = 0;
   catalog[0].Nmeas_off  = 0;
@@ -492,4 +522,6 @@
   catalog[0].Nlensing_off  = 0;
   catalog[0].Nlensobj_off  = 0;
+
+  catalog[0].Nstarpar_off  = 0;
 
   /* pointers to SPLIT data files */
@@ -499,4 +531,5 @@
   catalog[0].lensing_catalog = NULL;
   catalog[0].lensobj_catalog = NULL;
+  catalog[0].starpar_catalog = NULL;
 
   /* pointers for data manipulation */
@@ -540,5 +573,10 @@
 
   if (catalog[0].f == (FILE *) NULL) return (2);
-  fflush (catalog[0].f);
+
+  if (fflush (catalog[0].f)) {
+    fprintf (stderr, "failed to flush file\n");
+    return FALSE;
+  }
+
   // fprintf (stderr, "unlocking: %s\n", catalog[0].filename);
 
@@ -552,12 +590,16 @@
 
   // closes f but does not set back to NULL
-  fclearlockfile (catalog[0].filename, catalog[0].f, catalog[0].lockmode, &dbstate);
+  if (!fclearlockfile (catalog[0].filename, catalog[0].f, catalog[0].lockmode, &dbstate)) {
+    fprintf (stderr, "failed to unlock or close file\n");
+    return (0);
+  }
 
   if (catalog[0].catmode == DVO_MODE_SPLIT) {
-    if (catalog[0].measure_catalog) dvo_catalog_unlock (catalog[0].measure_catalog);
-    if (catalog[0].missing_catalog) dvo_catalog_unlock (catalog[0].missing_catalog);
-    if (catalog[0].secfilt_catalog) dvo_catalog_unlock (catalog[0].secfilt_catalog);
-    if (catalog[0].lensing_catalog) dvo_catalog_unlock (catalog[0].lensing_catalog);
-    if (catalog[0].lensobj_catalog) dvo_catalog_unlock (catalog[0].lensobj_catalog);
+    if (catalog[0].measure_catalog) { if (!dvo_catalog_unlock (catalog[0].measure_catalog)) { fprintf (stderr, "failed to unlock measures\n"); return (0); }}
+    if (catalog[0].missing_catalog) { if (!dvo_catalog_unlock (catalog[0].missing_catalog)) { fprintf (stderr, "failed to unlock missing\n"); return (0); }}
+    if (catalog[0].secfilt_catalog) { if (!dvo_catalog_unlock (catalog[0].secfilt_catalog)) { fprintf (stderr, "failed to unlock secfilt\n"); return (0); }}
+    if (catalog[0].lensing_catalog) { if (!dvo_catalog_unlock (catalog[0].lensing_catalog)) { fprintf (stderr, "failed to unlock lensing\n"); return (0); }}
+    if (catalog[0].lensobj_catalog) { if (!dvo_catalog_unlock (catalog[0].lensobj_catalog)) { fprintf (stderr, "failed to unlock lensobj\n"); return (0); }}
+    if (catalog[0].starpar_catalog) { if (!dvo_catalog_unlock (catalog[0].starpar_catalog)) { fprintf (stderr, "failed to unlock starpar\n"); return (0); }}
   }
   return (1);
@@ -597,4 +639,5 @@
   }
   
+  // NOTE: this only check if we can write a backup; it does not actually write the backup
   if (!check_file_access (catalog[0].filename, BACKUP, READWRITE, VERBOSE)) {
     fprintf (stderr, "no permission to access %s\n", catalog[0].filename);
@@ -835,4 +878,9 @@
       free (catalog[0].lensobj_catalog);
     }
+    if (catalog[0].starpar_catalog) {
+      free (catalog[0].starpar_catalog[0].filename);
+      dvo_catalog_free (catalog[0].starpar_catalog);
+      free (catalog[0].starpar_catalog);
+    }
   }
   dvo_catalog_free_data (catalog);
@@ -873,4 +921,9 @@
     catalog[0].Nlensobj = 0;
     catalog[0].lensobj = NULL;
+  }
+  if (catalog[0].starpar) {
+    free (catalog[0].starpar); 
+    catalog[0].Nstarpar = 0;
+    catalog[0].starpar = NULL;
   }
   if (catalog[0].nOwn_t)      { free (catalog[0].nOwn_t); catalog[0].nOwn_t = NULL; }
@@ -985,4 +1038,14 @@
       }
     }
+    if (catalog[0].starpar_catalog != NULL) {
+      if (catalog[0].Nstarpar_disk == 0) {
+	// need to relock (and re-open) file for close elsewhere
+	status = dvo_catalog_lock (catalog[0].starpar_catalog, lockmode);
+      } else {
+	if (!dvo_catalog_backup (catalog[0].starpar_catalog, FALSE)) {
+	  return FALSE;
+	}
+      }
+    }
   }
   return TRUE;
@@ -1036,4 +1099,9 @@
       }
     }
+    if ((catalog[0].starpar_catalog != NULL) && (catalog[0].Nstarpar_disk > 0)) {
+      if (!dvo_catalog_unlink_backup (catalog[0].starpar_catalog, FALSE)) {
+	return FALSE;
+      }
+    }
   }
   return TRUE;
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_create.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_create.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_create.c	(revision 37807)
@@ -95,4 +95,16 @@
     free (file);
 
+    /* define starpar catalog file */
+    ALLOCATE (catalog[0].starpar_catalog, Catalog, 1);
+    dvo_catalog_init (catalog[0].starpar_catalog, TRUE);
+
+    /* create basic data for starpar catalog file */
+    gfits_create_header (&catalog[0].starpar_catalog[0].header);
+    ALLOCATE (catalog[0].starpar_catalog[0].filename, char, length);
+    sprintf (catalog[0].starpar_catalog[0].filename, "%s/%s.cpz", path, root);
+    file = filebasename (catalog[0].starpar_catalog[0].filename);
+    gfits_modify (&catalog[0].header, "STARPAR", "%s", 1, file);
+    free (file);
+
     free (path);
     free (root);
@@ -117,4 +129,8 @@
     }
     if (dvo_catalog_lock (catalog[0].lensobj_catalog, catalog[0].lockmode) != DVO_CAT_OPEN_EMPTY) {
+      fprintf (stderr, "error with file lock\n");
+      exit (2);
+    }
+    if (dvo_catalog_lock (catalog[0].starpar_catalog, catalog[0].lockmode) != DVO_CAT_OPEN_EMPTY) {
       fprintf (stderr, "error with file lock\n");
       exit (2);
@@ -146,4 +162,5 @@
   ALLOCATE (catalog[0].lensing, Lensing, 1);
   ALLOCATE (catalog[0].lensobj, Lensobj, 1);
+  ALLOCATE (catalog[0].starpar, StarPar, 1);
 
   /* setup secondary filters to match photcodes:
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_mef.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_mef.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_mef.c	(revision 37807)
@@ -4,9 +4,4 @@
   
   off_t Nbytes;
-  off_t Naverage;
-  off_t Nmeasure;
-  off_t Nmissing;
-  off_t Nlensing;
-  off_t Nlensobj;
   off_t Nitems, Nexpect;
   int Nsecfilt;
@@ -18,12 +13,4 @@
 
   ftable.header = &header;
-
-  /* get the components from the header */
-  if (!gfits_scan (&catalog[0].header, "NSTARS",   OFF_T_FMT, 1,  &Naverage)) return (FALSE);
-  if (!gfits_scan (&catalog[0].header, "NMEAS",    OFF_T_FMT, 1,  &Nmeasure)) return (FALSE);
-  if (!gfits_scan (&catalog[0].header, "NMISS",    OFF_T_FMT, 1,  &Nmissing)) return (FALSE);
-  if (!gfits_scan (&catalog[0].header, "NSECFILT", "%d",   1,               &Nsecfilt)) Nsecfilt = 0;
-  if (!gfits_scan (&catalog[0].header, "NLENSING", OFF_T_FMT, 1,  &Nlensing)) return (FALSE);
-  if (!gfits_scan (&catalog[0].header, "NLENSOBJ", OFF_T_FMT, 1,  &Nlensobj)) return (FALSE);
 
   /* the OBJID is a counter that uniquely defines an average entry and never changes.  if
@@ -38,26 +25,34 @@
     catalog[0].catID = 0;
   }
+
+  // NSTARS, average, Naves_disk
+
+# define GET_TABLE_SIZES(HFIELD, CFIELD, DFIELD, REQUIRED)		\
+  off_t N##CFIELD;							\
+  if (REQUIRED) {							\
+    if (!gfits_scan (&catalog[0].header, HFIELD, OFF_T_FMT, 1, &N##CFIELD)) return (FALSE); \
+  } else {								\
+    if (!gfits_scan (&catalog[0].header, HFIELD, OFF_T_FMT, 1, &N##CFIELD)) N##CFIELD = 0; \
+  }									\
+  /* save the current number so we can do partial updates */		\
+  catalog[0].DFIELD = N##CFIELD;					\
+  /* default values, but we will assign these a valid value before we exit (even if empty) */ \
+  catalog[0].CFIELD = NULL;
   
-  /* save the current number so we can do partial updates */
-  catalog[0].Naves_disk = Naverage;
-  catalog[0].Nmeas_disk = Nmeasure;
-  catalog[0].Nmiss_disk = Nmissing;
-  catalog[0].Nsecf_disk = Naverage * Nsecfilt;
-  catalog[0].Nlensing_disk = Nlensing;
-  catalog[0].Nlensobj_disk = Nlensobj;
+  /* get the components and sizes from the header */
+  GET_TABLE_SIZES ("NSTARS",   average, Naves_disk,    TRUE);
+  GET_TABLE_SIZES ("NMEAS",    measure, Nmeas_disk,    TRUE);
+  GET_TABLE_SIZES ("NMISS",    missing, Nmiss_disk,    FALSE);
+  GET_TABLE_SIZES ("NLENSING", lensing, Nlensing_disk, FALSE);
+  GET_TABLE_SIZES ("NLENSOBJ", lensobj, Nlensobj_disk, FALSE);
+  GET_TABLE_SIZES ("NSTARPAR", starpar, Nstarpar_disk, FALSE);
 
   /**  Nsecfilt is unusual: it does not list the number of data items in the table
        instead, the number of items is Nsecfilt * Naverage.  **/
+
+  if (!gfits_scan (&catalog[0].header, "NSECFILT", "%d", 1, &Nsecfilt)) Nsecfilt = 0;
   catalog[0].Nsecfilt = Nsecfilt;
-
-  /* default values, but we will assign these a valid value before we exit (even if empty) */
-  catalog[0].average = NULL;
-  catalog[0].measure = NULL;
-  catalog[0].missing = NULL;
+  catalog[0].Nsecf_disk = Naverage * Nsecfilt;
   catalog[0].secfilt = NULL;
-  catalog[0].lensing = NULL;
-  catalog[0].lensobj = NULL;
-
-  /* validate table mode ?*/
 
   /*** Average Table ***/
@@ -259,4 +254,29 @@
   }
 
+  /* read StarPar table header */
+  if (!gfits_fread_header (catalog[0].f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read table starpar header");
+    return (FALSE);
+  }
+  /* read StarPar table data */
+  if (catalog[0].catflags & LOAD_STARPAR) {
+    if (!gfits_fread_ftable_data (catalog[0].f, &ftable, FALSE)) {
+      if (VERBOSE) fprintf (stderr, "can't read table starpar data");
+      return (FALSE);
+    }
+    catalog[0].starpar = FtableToStarPar (&ftable, &catalog[0].Nstarpar, &catalog[0].catformat);
+    if (Nstarpar != catalog[0].Nstarpar_disk) {
+      fprintf (stderr, "Warning: mismatch between Nstarpar in PHU and Table headers ("OFF_T_FMT" vs "OFF_T_FMT")\n",  Nstarpar,  catalog[0].Nstarpar_disk);
+    }
+    catalog[0].Nstarpar = catalog[0].Nstarpar_disk;
+    catalog[0].Nstarpar_off = 0;
+  } else {
+    Nbytes = gfits_data_size (&header);
+    fseeko (catalog[0].f, Nbytes, SEEK_CUR);
+    ALLOCATE (catalog[0].starpar, StarPar, 1);
+    catalog[0].Nstarpar = 0;
+    catalog[0].Nstarpar_off = catalog[0].Nstarpar_disk;
+  }
+
   return (TRUE);
 }
@@ -309,4 +329,5 @@
   gfits_modify (&catalog[0].header, "NLENSING", OFF_T_FMT, 1,  catalog[0].Nlensing);
   gfits_modify (&catalog[0].header, "NLENSOBJ", OFF_T_FMT, 1,  catalog[0].Nlensobj);
+  gfits_modify (&catalog[0].header, "NSTARPAR", OFF_T_FMT, 1,  catalog[0].Nstarpar);
   gfits_modify_alt (&catalog[0].header, "EXTEND",   "%t", 1, TRUE);
   gfits_modify (&catalog[0].header, "OBJID",    "%d", 1, catalog[0].objID);
@@ -403,4 +424,24 @@
   /* write out Lensobj table (convert to FITS table format) */
   LensobjToFtable (&ftable, catalog[0].lensobj, catalog[0].Nlensobj, catalog[0].catformat);
+  if (!gfits_fwrite_Theader (catalog[0].f, &header)) {
+    fprintf (stderr, "can't write table header");
+    goto failure;
+  }
+  if (!gfits_fwrite_table (catalog[0].f, &ftable)) {
+    fprintf (stderr, "can't write table data");
+    goto failure;
+  }
+  gfits_free_table (&ftable);
+  gfits_free_header (&header);
+
+  /* free temp storage */
+  if (primary != NULL) {
+    free (primary);
+    free (secfilt);
+  }
+  return (TRUE);
+
+  /* write out StarPar table (convert to FITS table format) */
+  StarParToFtable (&ftable, catalog[0].starpar, catalog[0].Nstarpar, catalog[0].catformat);
   if (!gfits_fwrite_Theader (catalog[0].f, &header)) {
     fprintf (stderr, "can't write table header");
@@ -446,4 +487,6 @@
   lensobj header
   lensobj table
+  starpar header
+  starpar table
 */
    
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_raw.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_raw.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_raw.c	(revision 37807)
@@ -92,4 +92,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
@@ -287,4 +288,5 @@
   if (catalog[0].catformat == DVO_FORMAT_PS1_V5)          gfits_modify (&catalog[0].header, "FORMAT", "%s", 1, "PS1_V5");
   if (catalog[0].catformat == DVO_FORMAT_PS1_REF)         gfits_modify (&catalog[0].header, "FORMAT", "%s", 1, "PS1_REF");
+  if (catalog[0].catformat == DVO_FORMAT_PS1_SIM)         gfits_modify (&catalog[0].header, "FORMAT", "%s", 1, "PS1_SIM");
 
   /* rewind file pointers and truncate file */
@@ -391,4 +393,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
@@ -443,4 +446,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
@@ -498,4 +502,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
@@ -550,4 +555,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
@@ -606,4 +612,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
@@ -658,4 +665,5 @@
       FORMAT_CASE (PS1_V5,    PS1_V5);
       FORMAT_CASE (PS1_REF,   PS1_REF);
+      FORMAT_CASE (PS1_SIM,   PS1_SIM);
 
     default:
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c	(revision 37807)
@@ -75,6 +75,62 @@
   Matrix matrix;
 
+  // ohana_memcheck_func (TRUE);
+  
+
+  if (0) {
+    // XXX a TEST : is the data corrupted or the file?
+    char name[1024];
+    snprintf (name, 1024, "%s.phu.v2", catalog->filename);
+    FILE *f = fopen (name, "w");
+    fwrite (catalog->header.buffer, sizeof(char), catalog->header.datasize, f);
+    // fprintf (stderr, "save %d bytes in %s (%zx)\n", (int) catalog->header.datasize, name, (size_t) catalog->header.buffer);
+    // fprintf (stderr, "first bytes are %c%c%c%c%c%c%c%c\n", 
+    // 	     catalog->header.buffer[0], catalog->header.buffer[1], catalog->header.buffer[2], catalog->header.buffer[3], 
+    // 	     catalog->header.buffer[4], catalog->header.buffer[5], catalog->header.buffer[6], catalog->header.buffer[7]);
+    fclose (f);
+  }
+  if (0) {
+    // XXX a TEST : is the data corrupted or the file?
+    char name[1024];
+    snprintf (name, 1024, "%s.phu", catalog->filename);
+    FILE *f = fopen (name, "w");
+    fwrite (catalog->header.buffer, sizeof(char), catalog->header.datasize, f);
+    // fprintf (stderr, "save %d bytes in %s (%zx)\n", (int) catalog->header.datasize, name, (size_t) catalog->header.buffer);
+    // fprintf (stderr, "first bytes are %c%c%c%c%c%c%c%c\n", 
+    // 	     catalog->header.buffer[0], catalog->header.buffer[1], catalog->header.buffer[2], catalog->header.buffer[3], 
+    // 	     catalog->header.buffer[4], catalog->header.buffer[5], catalog->header.buffer[6], catalog->header.buffer[7]);
+    fclose (f);
+  }
+# if (0)
+  {
+    // XXX a TEST : is the data corrupted or the file?
+    char name[1024];
+    snprintf (name, 1024, "%s.phu.v2", catalog->filename);
+    FILE *f = fopen (name, "w");
+    fwrite (catalog->header.buffer, sizeof(char), catalog->header.datasize, f);
+    fprintf (stderr, "save %d bytes in %s (%zx)\n", (int) catalog->header.datasize, name, (size_t) catalog->header.buffer);
+    fprintf (stderr, "first bytes are %c%c%c%c%c%c%c%c\n", 
+	     catalog->header.buffer[0], catalog->header.buffer[1], catalog->header.buffer[2], catalog->header.buffer[3], 
+	     catalog->header.buffer[4], catalog->header.buffer[5], catalog->header.buffer[6], catalog->header.buffer[7]);
+    fclose (f);
+  }
+  {
+    // XXX a TEST : is the data corrupted or the file?
+    char name[1024];
+    snprintf (name, 1024, "%s.phu.v3", catalog->filename);
+    FILE *f = fopen (name, "w");
+    fwrite (catalog->header.buffer, sizeof(char), catalog->header.datasize, f);
+    fprintf (stderr, "save %d bytes in %s (%zx)\n", (int) catalog->header.datasize, name, (size_t) catalog->header.buffer);
+    fprintf (stderr, "first bytes are %c%c%c%c%c%c%c%c\n", 
+	     catalog->header.buffer[0], catalog->header.buffer[1], catalog->header.buffer[2], catalog->header.buffer[3], 
+	     catalog->header.buffer[4], catalog->header.buffer[5], catalog->header.buffer[6], catalog->header.buffer[7]);
+    fclose (f);
+  }
+# endif
   /* rewind file pointers and truncate (file is still open) */
-  fseeko (catalog->f, 0LL, SEEK_SET);
+  if (fseeko (catalog->f, 0LL, SEEK_SET)) {
+    perror ("dvo_catalog_save_subset: ");
+    fprintf (stderr, "failed to seek to beginning\n");
+  }
 
   // write PHU header
@@ -134,8 +190,9 @@
   if (!gfits_scan (&catalog[0].header, name,  "%s", 1, string)) {
     free (path);
-    // databases created prior to ~2014.07.01 did not have the LENSING or LENSOBJ paths in their headers.
+    // databases created prior to ~2014.07.01 did not have the LENSING, LENSOBJ, STARPAR paths in their headers.
     // in these cases, we do not try to lock or open the relevant file
     if (!strcmp (name, "LENSING")) return (DVO_CAT_OPEN_EMPTY);
     if (!strcmp (name, "LENSOBJ")) return (DVO_CAT_OPEN_EMPTY);
+    if (!strcmp (name, "STARPAR")) return (DVO_CAT_OPEN_EMPTY);
     return (DVO_CAT_OPEN_FAIL);
   }
@@ -182,4 +239,5 @@
   off_t Nlensing;
   off_t Nlensobj;
+  off_t Nstarpar;
   off_t Nitems;
   int status, Nsecfilt;
@@ -196,5 +254,5 @@
 
   /* get the components from the header - these duplicate information in the split files (NAXIS2) */
-  // NSTARS, NMEAS, NMISS are required; NLENSING, NLENSOBJ are not (0 if not found)
+  // NSTARS, NMEAS, NMISS are required; NLENSING, NLENSOBJ, NSTARPAR are not (0 if not found)
   if (!gfits_scan (&catalog[0].header, "NSTARS",   OFF_T_FMT, 1,  &Naverage)) return (FALSE);
   if (!gfits_scan (&catalog[0].header, "NMEAS",    OFF_T_FMT, 1,  &Nmeasure)) return (FALSE);
@@ -203,4 +261,5 @@
   if (!gfits_scan (&catalog[0].header, "NLENSING", OFF_T_FMT, 1,  &Nlensing)) Nlensing = 0;
   if (!gfits_scan (&catalog[0].header, "NLENSOBJ", OFF_T_FMT, 1,  &Nlensobj)) Nlensobj = 0;
+  if (!gfits_scan (&catalog[0].header, "NSTARPAR", OFF_T_FMT, 1,  &Nstarpar)) Nstarpar = 0;
 
   /* the OBJID is a counter that uniquely defines an average entry and never changes.  if
@@ -223,4 +282,5 @@
   catalog[0].Nlensing_disk = Nlensing;
   catalog[0].Nlensobj_disk = Nlensobj;
+  catalog[0].Nstarpar_disk = Nstarpar;
 
   /**  Nsecfilt is unusual: it does not list the number of data items in the table
@@ -235,4 +295,5 @@
   catalog[0].lensing = NULL;
   catalog[0].lensobj = NULL;
+  catalog[0].starpar = NULL;
 
   /*** Average Table ***/
@@ -348,4 +409,5 @@
   /*** Secfilt Table ***/
   status = dvo_catalog_open_subcat (catalog, &catalog[0].secfilt_catalog, ftable.header, "SECFILT", VERBOSE);
+  // fprintf (stderr, "secfilt header %s : %zx\n", catalog[0].secfilt_catalog[0].filename, (size_t) catalog[0].secfilt_catalog[0].header.buffer);
   if (status == DVO_CAT_OPEN_FAIL) {
     return (FALSE);
@@ -465,4 +527,44 @@
   gfits_free_header (&header);
 
+  /*** StarPar Table ***/
+  if (!(catalog[0].catflags & SKIP_STARPAR)) {
+    // unless we specify 'skip', we still need to load the 
+    status = dvo_catalog_open_subcat (catalog, &catalog[0].starpar_catalog, ftable.header, "STARPAR", VERBOSE);
+    if (status == DVO_CAT_OPEN_FAIL) {
+      return (FALSE);
+    }
+    // the StarPar file need not exist, unless we expect data to exist
+    if ((status == DVO_CAT_OPEN_EMPTY) && (catalog[0].Nstarpar_disk > 0)) {
+      return (FALSE);
+    }
+  }
+  if ((status != DVO_CAT_OPEN_EMPTY) && (catalog[0].catflags & LOAD_STARPAR)) {
+    // only read the StarPar table if the file actually exists
+    /* read StarPar table data */
+    if (!gfits_fread_ftable_data (catalog[0].starpar_catalog[0].f, &ftable, FALSE)) {
+      if (VERBOSE) fprintf (stderr, "can't read table starpar data\n");
+      return (FALSE);
+    }
+    /* convert data format to internal : returns number of row read in Nstarpar */
+    catalog[0].starpar = FtableToStarPar (&ftable, &Nstarpar, &catalog[0].catformat);
+    if (Nstarpar != catalog[0].Nstarpar_disk) {
+      fprintf (stderr, "Warning: mismatch between Nstarpar in PHU and Table headers ("OFF_T_FMT" vs "OFF_T_FMT")\n",  Nstarpar,  catalog[0].Nstarpar_disk);
+    }
+    catalog[0].Nstarpar = catalog[0].Nstarpar_disk;
+    catalog[0].Nstarpar_off = 0;
+  } else {
+    if (catalog[0].starpar_catalog) {
+      gfits_free_header (&catalog[0].starpar_catalog[0].header);
+    } else {
+      ALLOCATE (catalog[0].starpar_catalog, Catalog, 1);
+      dvo_catalog_init (catalog[0].starpar_catalog, TRUE);
+    }
+    gfits_create_header (&catalog[0].starpar_catalog[0].header);
+    ALLOCATE (catalog[0].starpar, StarPar, 1);
+    catalog[0].Nstarpar = 0;
+    catalog[0].Nstarpar_off = catalog[0].Nstarpar_disk;
+  }
+  gfits_free_header (&header);
+
   return (TRUE);
 }
@@ -473,5 +575,5 @@
 
   off_t Nbytes;
-  off_t Naverage, Nexpect, Nitems, Nmeasure, Nmissing, Nlensing, Nlensobj;
+  off_t Naverage, Nexpect, Nitems, Nmeasure, Nmissing, Nlensing, Nlensobj, Nstarpar;
   Header header;
   FTable ftable;
@@ -497,5 +599,5 @@
     }
     /* read Average table data : format is irrelevant here */
-    if (!gfits_fread_ftable_range (catalog[0].f, &ftable, start, Nrows)) {
+    if (!gfits_fread_ftable_range (catalog[0].f, FALSE, &ftable, start, Nrows)) {
       if (VERBOSE) fprintf (stderr, "can't read table average data");
       return (FALSE);
@@ -527,5 +629,5 @@
     }
     /* read Secfilt table data : format is irrelevant here */
-    if (!gfits_fread_ftable_range (subcat[0].f, &ftable, start*catalog[0].Nsecfilt, catalog[0].Naverage*catalog[0].Nsecfilt)) {
+    if (!gfits_fread_ftable_range (subcat[0].f, FALSE, &ftable, start*catalog[0].Nsecfilt, catalog[0].Naverage*catalog[0].Nsecfilt)) {
       if (VERBOSE) fprintf (stderr, "can't read table measure data");
       return (FALSE);
@@ -563,5 +665,5 @@
     }
     /* read Measure table data : format is irrelevant here */
-    if (!gfits_fread_ftable_range (subcat[0].f, &ftable, start, Nrows)) {
+    if (!gfits_fread_ftable_range (subcat[0].f, FALSE, &ftable, start, Nrows)) {
       if (VERBOSE) fprintf (stderr, "can't read table measure data");
       return (FALSE);
@@ -594,5 +696,5 @@
     }
     /* read Missing table data : format is irrelevant here */
-    if (!gfits_fread_ftable_range (subcat[0].f, &ftable, start, Nrows)) {
+    if (!gfits_fread_ftable_range (subcat[0].f, FALSE, &ftable, start, Nrows)) {
       if (VERBOSE) fprintf (stderr, "can't read table missing data");
       return (FALSE);
@@ -628,5 +730,5 @@
     }
     /* read Lensing table data : format is irrelevant here */
-    if (!gfits_fread_ftable_range (subcat[0].f, &ftable, start, Nrows)) {
+    if (!gfits_fread_ftable_range (subcat[0].f, FALSE, &ftable, start, Nrows)) {
       if (VERBOSE) fprintf (stderr, "can't read table lensing data");
       return (FALSE);
@@ -659,5 +761,5 @@
     }
     /* read Lensobj table data : format is irrelevant here */
-    if (!gfits_fread_ftable_range (subcat[0].f, &ftable, start, Nrows)) {
+    if (!gfits_fread_ftable_range (subcat[0].f, FALSE, &ftable, start, Nrows)) {
       if (VERBOSE) fprintf (stderr, "can't read table lensobj data");
       return (FALSE);
@@ -675,4 +777,35 @@
   }
 
+  // XXX check the open status of the catalog
+  if (catalog[0].catflags & LOAD_STARPAR) {
+
+    Catalog *subcat = catalog[0].starpar_catalog;
+
+    /* move pointer past header -- must be already read (load_catalog) */
+    Nbytes = subcat[0].header.datasize + gfits_data_size (&subcat[0].header);
+    fseeko (subcat[0].f, Nbytes, SEEK_SET);
+
+    /* read StarPar table header */
+    if (!gfits_fread_header (subcat[0].f, &header)) {
+      if (VERBOSE) fprintf (stderr, "can't read table starpar header");
+      return (FALSE);
+    }
+    /* read StarPar table data : format is irrelevant here */
+    if (!gfits_fread_ftable_range (subcat[0].f, FALSE, &ftable, start, Nrows)) {
+      if (VERBOSE) fprintf (stderr, "can't read table starpar data");
+      return (FALSE);
+    }
+
+    /* convert data format to internal : returns number of row read in Nstarpar */
+    catalog[0].starpar = FtableToStarPar (&ftable, &Nstarpar, &catalog[0].catformat);
+    if (Nstarpar != Nrows) {
+      // XXX this condition denotes the eof has been reached; not an error or a warning
+      // fprintf (stderr, "Warning: mismatch between Nstarpar in PHU and Table headers ("OFF_T_FMT" vs "OFF_T_FMT")\n",  Nstarpar,  Nrows);
+    }
+    gfits_free_header (&header);
+    catalog[0].Nstarpar = Nstarpar;
+    catalog[0].Nstarpar_off = start;
+  }
+
   return (TRUE);
 }
@@ -686,5 +819,5 @@
   SecFilt *primary, *secfilt;
   int Nsecfilt;
-  off_t Naves_disk_new, Nmeas_disk_new, Nmiss_disk_new, Nsecf_disk_new, Nlensing_disk_new, Nlensobj_disk_new;
+  off_t Naves_disk_new, Nmeas_disk_new, Nmiss_disk_new, Nsecf_disk_new, Nlensing_disk_new, Nlensobj_disk_new, Nstarpar_disk_new;
   off_t first, start, Nrows;
 
@@ -719,4 +852,5 @@
   Nlensing_disk_new = MAX (catalog[0].Nlensing_disk, catalog[0].Nlensing + catalog[0].Nlensing_off);
   Nlensobj_disk_new = MAX (catalog[0].Nlensobj_disk, catalog[0].Nlensobj + catalog[0].Nlensobj_off);
+  Nstarpar_disk_new = MAX (catalog[0].Nstarpar_disk, catalog[0].Nstarpar + catalog[0].Nstarpar_off);
 
   /* make sure header is consistent with data */
@@ -727,4 +861,5 @@
   gfits_modify (&catalog[0].header, "NLENSING", OFF_T_FMT, 1,  Nlensing_disk_new);
   gfits_modify (&catalog[0].header, "NLENSOBJ", OFF_T_FMT, 1,  Nlensobj_disk_new);
+  gfits_modify (&catalog[0].header, "NSTARPAR", OFF_T_FMT, 1,  Nstarpar_disk_new);
   gfits_modify_alt (&catalog[0].header, "EXTEND",   "%t", 1, TRUE);
   gfits_modify (&catalog[0].header, "OBJID",    "%d", 1, catalog[0].objID);
@@ -845,6 +980,6 @@
   }
 
-  /*** Lensing Table ***/
-  if ((catalog[0].catflags & LOAD_LENSING) && (catalog[0].lensing != NULL)) {
+  /*** Lensing Table (optional, do not save if not loaded) ***/
+  if ((catalog[0].catflags & LOAD_LENSING) && catalog[0].lensing_catalog->f && catalog[0].lensing) {
 
     first  = 0;                    // first row in memory to write
@@ -872,6 +1007,6 @@
   }
 
-  /*** Lensobj Table ***/
-  if ((catalog[0].catflags & LOAD_LENSOBJ) && (catalog[0].lensobj != NULL)) {
+  /*** Lensobj Table (optional, do not save if not loaded) ***/
+  if ((catalog[0].catflags & LOAD_LENSOBJ) && catalog[0].lensobj_catalog->f && catalog[0].lensobj) {
 
     first  = 0;                    // first row in memory to write
@@ -893,4 +1028,31 @@
     if (!dvo_catalog_save_subcat (catalog[0].lensobj_catalog, &ftable, start, Nrows, catalog[0].Nlensobj_disk, Nlensobj_disk_new)) {
       fprintf (stderr, "trouble writing Lensobj table\n");
+      goto failure;
+    }
+    gfits_free_header (&header);
+    gfits_free_table (&ftable);
+  }
+
+  /*** StarPar Table (optional, do not save if not loaded) ***/
+    if ((catalog[0].catflags & LOAD_STARPAR) && catalog[0].starpar_catalog->f && catalog[0].starpar) {
+
+    first  = 0;                    // first row in memory to write
+    start  = catalog[0].Nstarpar_off; // first disk row to write
+    Nrows  = catalog[0].Nstarpar - first;
+
+    assert (Nrows >= 0);
+    assert (first >= 0);
+    assert (first <= catalog[0].Nstarpar);
+    assert (catalog[0].Nstarpar_disk >= catalog[0].Nstarpar_off);
+
+    // convert to external table format (note that the block above does not damage or free catalog.average) 
+    if (!StarParToFtable (&ftable, &catalog[0].starpar[first], Nrows, catalog[0].catformat)) {
+      fprintf (stderr, "trouble converting format\n");
+      goto failure;
+    }
+
+    // write out StarPar table
+    if (!dvo_catalog_save_subcat (catalog[0].starpar_catalog, &ftable, start, Nrows, catalog[0].Nstarpar_disk, Nstarpar_disk_new)) {
+      fprintf (stderr, "trouble writing StarPar table\n");
       goto failure;
     }
@@ -925,5 +1087,5 @@
   SecFilt *primary, *secfilt;
   int Nsecfilt;
-  off_t Naves_disk_new, Nmeas_disk_new, Nmiss_disk_new, Nsecf_disk_new, Nlensing_disk_new, Nlensobj_disk_new;
+  off_t Naves_disk_new, Nmeas_disk_new, Nmiss_disk_new, Nsecf_disk_new, Nlensing_disk_new, Nlensobj_disk_new, Nstarpar_disk_new;
   off_t first, start, Nrows;
 
@@ -956,4 +1118,8 @@
   if (catalog[0].Nlensobj_off > 0) {
     fprintf (stderr, "ERROR: only partial catalog (Lensobj) was loaded\n");
+    goto failure;
+  }
+  if (catalog[0].Nstarpar_off > 0) {
+    fprintf (stderr, "ERROR: only partial catalog (StarPar) was loaded\n");
     goto failure;
   }
@@ -984,4 +1150,5 @@
   Nlensing_disk_new = catalog[0].Nlensing;
   Nlensobj_disk_new = catalog[0].Nlensobj;
+  Nstarpar_disk_new = catalog[0].Nstarpar;
 
   /* make sure header is consistent with data */
@@ -992,4 +1159,5 @@
   gfits_modify (&catalog[0].header, "NLENSING", OFF_T_FMT, 1,  Nlensing_disk_new);
   gfits_modify (&catalog[0].header, "NLENSOBJ", OFF_T_FMT, 1,  Nlensobj_disk_new);
+  gfits_modify (&catalog[0].header, "NSTARPAR", OFF_T_FMT, 1,  Nstarpar_disk_new);
   gfits_modify_alt (&catalog[0].header, "EXTEND",   "%t", 1, TRUE);
   gfits_modify (&catalog[0].header, "OBJID",    "%d", 1, catalog[0].objID);
@@ -1158,4 +1326,31 @@
     if (!dvo_catalog_save_subcat (catalog[0].lensobj_catalog, &ftable, start, Nrows, catalog[0].Nlensobj_disk, Nlensobj_disk_new)) {
       fprintf (stderr, "trouble writing Lensobj table\n");
+      goto failure;
+    }
+    gfits_free_header (&header);
+    gfits_free_table (&ftable);
+  }
+
+  /*** StarPar Table ***/
+  if ((catalog[0].catflags & LOAD_STARPAR) && (catalog[0].starpar != NULL)) {
+
+    first  = 0;                    // first row in memory to write
+    start  = catalog[0].Nstarpar_off; // first disk row to write
+    Nrows  = catalog[0].Nstarpar - first;
+
+    assert (Nrows >= 0);
+    assert (first >= 0);
+    assert (first <= catalog[0].Nstarpar);
+    assert (catalog[0].Nstarpar_disk >= catalog[0].Nstarpar_off);
+
+    // convert to external table format
+    if (!StarParToFtable (&ftable, &catalog[0].starpar[first], Nrows, catalog[0].catformat)) {
+      fprintf (stderr, "trouble converting format\n");
+      goto failure;
+    }
+
+    // write out StarPar table
+    if (!dvo_catalog_save_subcat (catalog[0].starpar_catalog, &ftable, start, Nrows, catalog[0].Nstarpar_disk, Nstarpar_disk_new)) {
+      fprintf (stderr, "trouble writing StarPar table\n");
       goto failure;
     }
@@ -1189,5 +1384,5 @@
   SecFilt *primary, *secfilt;
   int Nsecfilt;
-  off_t Naves_disk_new, Nmeas_disk_new, Nmiss_disk_new, Nsecf_disk_new, Nlensing_disk_new, Nlensobj_disk_new;
+  off_t Naves_disk_new, Nmeas_disk_new, Nmiss_disk_new, Nsecf_disk_new, Nlensing_disk_new, Nlensobj_disk_new, Nstarpar_disk_new;
   off_t first, start, Nrows;
 
@@ -1221,4 +1416,5 @@
   Nlensing_disk_new = MAX (catalog[0].Nlensing_disk, catalog[0].Nlensing + catalog[0].Nlensing_off);
   Nlensobj_disk_new = MAX (catalog[0].Nlensobj_disk, catalog[0].Nlensobj + catalog[0].Nlensobj_off);
+  Nstarpar_disk_new = MAX (catalog[0].Nstarpar_disk, catalog[0].Nstarpar + catalog[0].Nstarpar_off);
 
   /* make sure header is consistent with data */
@@ -1229,4 +1425,5 @@
   gfits_modify (&catalog[0].header, "NLENSING", OFF_T_FMT, 1,  Nlensing_disk_new);
   gfits_modify (&catalog[0].header, "NLENSOBJ", OFF_T_FMT, 1,  Nlensobj_disk_new);
+  gfits_modify (&catalog[0].header, "NSTARPAR", OFF_T_FMT, 1,  Nstarpar_disk_new);
   gfits_modify_alt (&catalog[0].header, "EXTEND",   "%t", 1, TRUE);
   gfits_modify (&catalog[0].header, "OBJID",    "%d", 1, catalog[0].objID);
@@ -1399,4 +1596,32 @@
     if (!dvo_catalog_save_subcat (catalog[0].lensobj_catalog, &ftable, start, Nrows, catalog[0].Nlensobj_disk, Nlensobj_disk_new)) {
       fprintf (stderr, "trouble writing Lensobj table\n");
+      goto failure;
+    }
+    gfits_free_header (&header);
+    gfits_free_table (&ftable);
+  }
+
+  /*** StarPar Table ***/
+  if (catalog[0].starpar != NULL) {
+
+    first  = catalog[0].Nstarpar_disk - catalog[0].Nstarpar_off;  // first row in memory to write
+    start  = catalog[0].Nstarpar_disk; // first disk row to write
+    Nrows  = catalog[0].Nstarpar - first;
+
+    assert (Nrows >= 0);
+    assert (first >= 0);
+    assert (first <= catalog[0].Nstarpar);
+    assert (catalog[0].Nstarpar_disk >= catalog[0].Nstarpar_off);
+
+    // convert to external table format
+    // XXX does catalog.starpar have averef correctly set up?
+    if (!StarParToFtable (&ftable, &catalog[0].starpar[first], Nrows, catalog[0].catformat)) {
+      fprintf (stderr, "trouble converting format\n");
+      goto failure;
+    }
+
+    // write out StarPar table
+    if (!dvo_catalog_save_subcat (catalog[0].starpar_catalog, &ftable, start, Nrows, catalog[0].Nstarpar_disk, Nstarpar_disk_new)) {
+      fprintf (stderr, "trouble writing StarPar table\n");
       goto failure;
     }
Index: /trunk/Ohana/src/libdvo/src/dvo_convert.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert.c	(revision 37807)
@@ -43,4 +43,5 @@
   CONVERT_FORMAT ("DVO_AVERAGE_PS1_V5",          PS1_V5);
   CONVERT_FORMAT ("DVO_AVERAGE_PS1_REF",         PS1_REF);
+  CONVERT_FORMAT ("DVO_AVERAGE_PS1_SIM",         PS1_SIM);
 # undef CONVERT_FORMAT
 
@@ -129,4 +130,5 @@
   CONVERT_FORMAT ("DVO_AVERAGE_PS1_V5",          PS1_V5,          PS1_V5);
   CONVERT_FORMAT ("DVO_AVERAGE_PS1_REF",         PS1_REF,         PS1_REF);
+  CONVERT_FORMAT ("DVO_AVERAGE_PS1_SIM",         PS1_SIM,         PS1_SIM);
 # undef CONVERT_FORMAT
 
@@ -165,4 +167,5 @@
       FORMAT_CASE (PS1_V5,          PS1_V5);
       FORMAT_CASE (PS1_REF,         PS1_REF);
+      FORMAT_CASE (PS1_SIM,         PS1_SIM);
 # undef FORMAT_CASE
 
@@ -240,4 +243,5 @@
   CONVERT_FORMAT ("DVO_MEASURE_PS1_V5",          PS1_V5,          PS1_V5,          TRUE);
   CONVERT_FORMAT ("DVO_MEASURE_PS1_REF",         PS1_REF,         PS1_REF,         FALSE);
+  CONVERT_FORMAT ("DVO_MEASURE_PS1_SIM",         PS1_SIM,         PS1_SIM,         TRUE);
 # undef CONVERT_FORMAT
 
@@ -254,5 +258,5 @@
     case DVO_FORMAT_##FORMAT: { \
       Measure_##TYPE *tmpMeasure; \
-      myAssert (ABS_COORDS || average, "conversion to internal needs average table"); \
+      myAssert (ABS_COORDS || average, "conversion from internal needs average table"); \
       tmpMeasure = MeasureInternalTo_##TYPE (average, measure, Nmeasure); \
       gfits_table_set_Measure_##TYPE (ftable, tmpMeasure, Nmeasure); \
@@ -278,4 +282,5 @@
       FORMAT_CASE (PS1_V5,          PS1_V5,          TRUE);
       FORMAT_CASE (PS1_REF,         PS1_REF,         FALSE);
+      FORMAT_CASE (PS1_SIM,         PS1_SIM,         TRUE);
 # undef FORMAT_CASE
 
@@ -350,4 +355,5 @@
   CONVERT_FORMAT ("DVO_SECFILT_PS1_V5",          PS1_V5,          PS1_V5);
   CONVERT_FORMAT ("DVO_SECFILT_PS1_REF",         PS1_REF,         PS1_REF);
+  CONVERT_FORMAT ("DVO_SECFILT_PS1_SIM",         PS1_SIM,         PS1_SIM);
 # undef CONVERT_FORMAT
 
@@ -386,4 +392,5 @@
       FORMAT_CASE (PS1_V5,          PS1_V5);
       FORMAT_CASE (PS1_REF,         PS1_REF);
+      FORMAT_CASE (PS1_SIM,         PS1_SIM);
 # undef FORMAT_CASE
 
@@ -407,4 +414,16 @@
     return (FALSE);
   }
+
+  if (!strcmp (extname, "DVO_LENSING_PS1_V5") && (ftable[0].header[0].Naxis[0] == 128)) {
+    Lensing_PS1_V5_ALT *tmpLensing;						
+    tmpLensing = gfits_table_get_Lensing_PS1_V5_ALT (ftable, Nlensing, NULL); 
+    if (!tmpLensing) {							
+      fprintf (stderr, "ERROR: failed to read lensings\n");		
+      exit (2);								
+    }									
+    lensing = Lensing_PS1_V5_ALT_ToInternal (tmpLensing, *Nlensing); 
+    free (tmpLensing);							
+    *format = DVO_FORMAT_PS1_V5;					
+    return (lensing); }
 
 # define CONVERT_FORMAT(NAME, FORMAT, TYPE)		\
@@ -585,4 +604,102 @@
     default:
       fprintf (stderr, "table format unknown (lensobj)\n");
+      return (FALSE);
+  }
+  return (TRUE);
+}
+
+/*** StarPar / FTable conversion functions ***/
+
+StarPar *FtableToStarPar (FTable *ftable, off_t *Nstarpar, char *format) {
+
+  StarPar *starpar;
+  char extname[80];
+
+  /* convert to the internal format */
+  if (!gfits_scan (ftable[0].header, "EXTNAME", "%s", 1, extname)) {
+    fprintf (stderr, "EXTNAME missing for starpar table\n");
+    return (FALSE);
+  }
+
+# define CONVERT_FORMAT(NAME, FORMAT, TYPE)		\
+  if (!strcmp (extname, NAME)) {					\
+    StarPar_##TYPE *tmpStarPar;						\
+    tmpStarPar = gfits_table_get_StarPar_##TYPE (ftable, Nstarpar, NULL); \
+    if (!tmpStarPar) {							\
+      fprintf (stderr, "ERROR: failed to read starpar\n");		\
+      exit (2);								\
+    }									\
+    starpar = StarPar_##TYPE##_ToInternal (tmpStarPar, *Nstarpar); \
+    free (tmpStarPar);							\
+    *format = DVO_FORMAT_##FORMAT;					\
+    return (starpar); }
+
+  if (!strcmp (extname, "DVO_STARPAR")) {
+    starpar = gfits_table_get_StarPar (ftable, Nstarpar, NULL);
+    if (!starpar) {
+      fprintf (stderr, "ERROR: failed to read starpar\n");
+      exit (2);
+    }
+    *format = DVO_FORMAT_INTERNAL;
+    return (starpar);
+  }
+
+   CONVERT_FORMAT ("DVO_STARPAR_PS1_SIM",         PS1_SIM,         PS1_SIM);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_REF",         PS1_REF,         PS1_REF);
+// CONVERT_FORMAT ("DVO_STARPAR_ELIXIR", 	  ELIXIR,  	   Elixir);
+// CONVERT_FORMAT ("DVO_STARPAR_LONEOS", 	  LONEOS,          Loneos);
+// CONVERT_FORMAT ("DVO_STARPAR_PANSTARRS_DEV_0", PANSTARRS_DEV_0, Panstarrs_DEV_0);
+// CONVERT_FORMAT ("DVO_STARPAR_PANSTARRS_DEV_1", PANSTARRS_DEV_1, Panstarrs_DEV_1);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_DEV_1",       PS1_DEV_1,       PS1_DEV_1);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_DEV_2",       PS1_DEV_2,       PS1_DEV_2);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_V1",          PS1_V1,          PS1_V1);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_V2",          PS1_V2,          PS1_V2);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_V3",          PS1_V3,          PS1_V3);
+// CONVERT_FORMAT ("DVO_STARPAR_PS1_V4",          PS1_V4,          PS1_V4);
+  CONVERT_FORMAT ("DVO_STARPAR_PS1_V5",          PS1_V5,          PS1_V5);
+//  CONVERT_FORMAT ("DVO_STELLAR_PARAMS_PS1_V5",          PS1_V5,          PS1_V5);
+  // XXX fix in libauto: STELLAR_PARAMS to STARPAR
+# undef CONVERT_FORMAT
+
+  fprintf (stderr, "table format unknown: %s\n", extname);
+
+  *Nstarpar = 0;
+  return (NULL);
+}
+
+// StarParToFtable needs the Average since old formats stored starpar.dR,dD only
+int StarParToFtable (FTable *ftable, StarPar *starpar, off_t Nstarpar, char format) {
+
+# define FORMAT_CASE(FORMAT, TYPE)		\
+    case DVO_FORMAT_##FORMAT: { \
+      StarPar_##TYPE *tmpStarPar; \
+      tmpStarPar = StarParInternalTo_##TYPE (starpar, Nstarpar); \
+      gfits_table_set_StarPar_##TYPE (ftable, tmpStarPar, Nstarpar); \
+      free (tmpStarPar); \
+      break; }
+
+  /* convert from the internal format */
+  switch (format) {
+    case DVO_FORMAT_INTERNAL: {
+      gfits_table_set_StarPar (ftable, starpar, Nstarpar);
+      break; }
+
+      FORMAT_CASE (PS1_SIM,         PS1_SIM);
+//    FORMAT_CASE (PS1_REF,         PS1_REF);
+//    FORMAT_CASE (ELIXIR, 	    Elixir);
+//    FORMAT_CASE (LONEOS, 	    Loneos);
+//    FORMAT_CASE (PANSTARRS_DEV_0, Panstarrs_DEV_0);
+//    FORMAT_CASE (PANSTARRS_DEV_1, Panstarrs_DEV_1);
+//    FORMAT_CASE (PS1_DEV_1,       PS1_DEV_1);
+//    FORMAT_CASE (PS1_DEV_2,       PS1_DEV_2);
+//    FORMAT_CASE (PS1_V1,          PS1_V1);
+//    FORMAT_CASE (PS1_V2,          PS1_V2);
+//    FORMAT_CASE (PS1_V3,          PS1_V3);
+//    FORMAT_CASE (PS1_V4,          PS1_V4);
+      FORMAT_CASE (PS1_V5,          PS1_V5);
+# undef FORMAT_CASE
+
+    default:
+      fprintf (stderr, "table format unknown (starpar)\n");
       return (FALSE);
   }
@@ -643,15 +760,4 @@
     return (TRUE); }
 
-  if (!strcmp (extname, "DVO_IMAGE")) {
-    Image *image;
-    image = gfits_table_get_Image (ftable, &Nimage, NULL);
-    if (!image) {
-      fprintf (stderr, "ERROR: failed to read images\n");
-      exit (2);
-    }
-    *format = DVO_FORMAT_INTERNAL;
-    return (TRUE);
-  }
-
   CONVERT_FORMAT ("DVO_IMAGE_ELIXIR", 	       ELIXIR,          Elixir);
   CONVERT_FORMAT ("DVO_IMAGE_LONEOS", 	       LONEOS,          Loneos);
@@ -666,4 +772,5 @@
   CONVERT_FORMAT ("DVO_IMAGE_PS1_V5",          PS1_V5,          PS1_V5);
   CONVERT_FORMAT ("DVO_IMAGE_PS1_REF",         PS1_REF,         PS1_REF);
+  CONVERT_FORMAT ("DVO_IMAGE_PS1_SIM",         PS1_SIM,         PS1_SIM);
 
 # undef CONVERT_FORMAT
@@ -690,8 +797,4 @@
   /* convert from the internal format */
   switch (format) {
-    case DVO_FORMAT_INTERNAL: {
-      gfits_convert_Image ((Image *) ftable[0].buffer, sizeof(Image), Nimage);
-      break; }
-
       FORMAT_CASE (ELIXIR, 	    Elixir);
       FORMAT_CASE (LONEOS, 	    Loneos);
@@ -706,4 +809,5 @@
       FORMAT_CASE (PS1_V5,          PS1_V5);
       FORMAT_CASE (PS1_REF,         PS1_REF);
+      FORMAT_CASE (PS1_SIM,         PS1_SIM);
 
 # undef FORMAT_CASE
@@ -744,10 +848,4 @@
   /* convert from the internal format */
   switch (format) {
-    case DVO_FORMAT_INTERNAL: {
-      for (i = 0; i < Nrow; i++) {
-	gfits_convert_Image ((Image *) vtable[0].buffer[i], sizeof(Image), 1);
-      }
-      return (TRUE); }
-
       FORMAT_CASE (ELIXIR, 	    Elixir);
       FORMAT_CASE (LONEOS, 	    Loneos);
@@ -762,4 +860,5 @@
       FORMAT_CASE (PS1_V5,          PS1_V5);
       FORMAT_CASE (PS1_REF,         PS1_REF);
+      FORMAT_CASE (PS1_SIM,         PS1_SIM);
 
 # undef FORMAT_CASE
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_1.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_1.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_1.c	(revision 37807)
@@ -219,5 +219,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
@@ -287,5 +290,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_2.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_2.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_2.c	(revision 37807)
@@ -214,5 +214,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
@@ -279,5 +282,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_3.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_3.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_DEV_3.c	(revision 37807)
@@ -18,5 +18,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
@@ -78,5 +81,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_REF.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_REF.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_REF.c	(revision 37807)
@@ -145,5 +145,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
@@ -208,5 +211,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_SIM.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_SIM.c	(revision 37807)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_SIM.c	(revision 37807)
@@ -0,0 +1,429 @@
+# include <dvo.h>
+
+/* convert PS1_SIM formats to internal formats */
+
+Measure *Measure_PS1_SIM_ToInternal (Average *ave, Measure_PS1_SIM *in, off_t Nvalues) {
+
+  off_t i;
+  Measure *out;
+
+  ALLOCATE_ZERO (out, Measure, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_measure_init (&out[i]);
+
+    out[i].R          = in[i].R;
+    out[i].D          = in[i].D;
+    out[i].M          = in[i].M;
+    out[i].dM         = in[i].dM;
+    out[i].Mcal       = in[i].Mcal;
+    out[i].dt         = in[i].dt;
+    out[i].airmass    = in[i].airmass;
+    out[i].az         = in[i].az;
+    out[i].Xccd       = in[i].Xccd;
+    out[i].Yccd       = in[i].Yccd;
+    out[i].Xfix       = in[i].Xfix;
+    out[i].Yfix       = in[i].Yfix;
+    out[i].XoffKH     = in[i].XoffKH;
+    out[i].YoffKH     = in[i].YoffKH;
+    out[i].XoffDCR    = in[i].XoffDCR;
+    out[i].YoffDCR    = in[i].YoffDCR;
+    out[i].RoffGAL    = in[i].RoffGAL;
+    out[i].DoffGAL    = in[i].DoffGAL;
+    out[i].t          = in[i].t;
+    out[i].averef     = in[i].averef;
+    out[i].detID      = in[i].detID;
+    out[i].objID      = in[i].objID;
+    out[i].catID      = in[i].catID;
+    out[i].imageID    = in[i].imageID;
+    out[i].psfQF      = in[i].psfQF;
+    out[i].psfQFperf  = in[i].psfQFperf;
+    out[i].photcode   = in[i].photcode;
+    out[i].dXccd      = in[i].dXccd;
+    out[i].dYccd      = in[i].dYccd;
+    out[i].dRsys      = in[i].dRsys;
+    out[i].posangle   = in[i].posangle;
+    out[i].pltscale   = in[i].pltscale;
+    out[i].dbFlags    = in[i].dbFlags;
+    out[i].photFlags  = in[i].photFlags;
+  }
+  return (out);
+}
+
+Measure_PS1_SIM *MeasureInternalTo_PS1_SIM (Average *ave, Measure *in, off_t Nvalues) {
+
+  off_t i;
+  Measure_PS1_SIM *out;
+
+  ALLOCATE_ZERO (out, Measure_PS1_SIM, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    out[i].R          = in[i].R;
+    out[i].D          = in[i].D;
+    out[i].M          = in[i].M;
+    out[i].dM         = in[i].dM;
+    out[i].Mcal       = in[i].Mcal;
+    out[i].dt         = in[i].dt;
+    out[i].airmass    = in[i].airmass;
+    out[i].az         = in[i].az;
+    out[i].Xccd       = in[i].Xccd;
+    out[i].Yccd       = in[i].Yccd;
+    out[i].Xfix       = in[i].Xfix;
+    out[i].Yfix       = in[i].Yfix;
+    out[i].XoffKH     = in[i].XoffKH;
+    out[i].YoffKH     = in[i].YoffKH;
+    out[i].XoffDCR    = in[i].XoffDCR;
+    out[i].YoffDCR    = in[i].YoffDCR;
+    out[i].RoffGAL    = in[i].RoffGAL;
+    out[i].DoffGAL    = in[i].DoffGAL;
+    out[i].t          = in[i].t;
+    out[i].averef     = in[i].averef;
+    out[i].detID      = in[i].detID;
+    out[i].objID      = in[i].objID;
+    out[i].catID      = in[i].catID;
+    out[i].imageID    = in[i].imageID;
+    out[i].psfQF      = in[i].psfQF;
+    out[i].psfQFperf  = in[i].psfQFperf;
+    out[i].photcode   = in[i].photcode;
+    out[i].dXccd      = in[i].dXccd;
+    out[i].dYccd      = in[i].dYccd;
+    out[i].dRsys      = in[i].dRsys;
+    out[i].posangle   = in[i].posangle;
+    out[i].pltscale   = in[i].pltscale;
+    out[i].dbFlags    = in[i].dbFlags;
+    out[i].photFlags  = in[i].photFlags;
+  }
+  return (out);
+}
+
+// 'primary' is needed to conform with the API for Loneos and Elixir, but is not used
+Average *Average_PS1_SIM_ToInternal (Average_PS1_SIM *in, off_t Nvalues, SecFilt **primary) {
+
+  off_t i;
+  Average *out;
+
+  ALLOCATE_ZERO (out, Average, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_average_init (&out[i]);
+
+    out[i].R        	 = in[i].R;      
+    out[i].D        	 = in[i].D;      
+    out[i].dR       	 = in[i].dR;
+    out[i].dD       	 = in[i].dD;
+    out[i].uR       	 = in[i].uR;
+    out[i].uD       	 = in[i].uD;
+    out[i].duR      	 = in[i].duR;
+    out[i].duD      	 = in[i].duD;
+    out[i].P        	 = in[i].P;
+    out[i].dP       	 = in[i].dP;
+
+    out[i].ChiSqAve    	 = in[i].ChiSqAve;     
+    out[i].ChiSqPM    	 = in[i].ChiSqPM;     
+    out[i].ChiSqPar    	 = in[i].ChiSqPar;     
+    out[i].Tmean    	 = in[i].Tmean;     
+    out[i].Trange   	 = in[i].Trange;     
+
+    out[i].Npos       	 = in[i].Npos;     
+
+    out[i].Nmeasure      = in[i].Nmeasure;     
+    out[i].Nstarpar      = in[i].Nstarpar;     
+
+    out[i].measureOffset = in[i].measureOffset; 
+    out[i].starparOffset = in[i].starparOffset;
+
+    out[i].refColorBlue  = in[i].refColorBlue;
+    out[i].refColorRed   = in[i].refColorRed;
+
+    out[i].flags     	 = in[i].flags;   
+    out[i].objID 	 = in[i].objID;
+    out[i].catID 	 = in[i].catID;
+  }
+  return (out);
+}
+
+// 'primary' is needed to conform with the API for Loneos and Elixir, but is not used
+Average_PS1_SIM *AverageInternalTo_PS1_SIM (Average *in, off_t Nvalues, SecFilt *primary) {
+
+  off_t i;
+  Average_PS1_SIM *out;
+
+  ALLOCATE_ZERO (out, Average_PS1_SIM, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    out[i].R        	 = in[i].R;      
+    out[i].D        	 = in[i].D;      
+    out[i].dR       	 = in[i].dR;
+    out[i].dD       	 = in[i].dD;
+    out[i].uR       	 = in[i].uR;
+    out[i].uD       	 = in[i].uD;
+    out[i].duR      	 = in[i].duR;
+    out[i].duD      	 = in[i].duD;
+    out[i].P        	 = in[i].P;
+    out[i].dP       	 = in[i].dP;
+
+    out[i].ChiSqAve    	 = in[i].ChiSqAve;     
+    out[i].ChiSqPM    	 = in[i].ChiSqPM;     
+    out[i].ChiSqPar    	 = in[i].ChiSqPar;     
+    out[i].Tmean    	 = in[i].Tmean;     
+    out[i].Trange   	 = in[i].Trange;     
+
+    out[i].Npos       	 = in[i].Npos;     
+
+    out[i].Nmeasure      = in[i].Nmeasure;     
+    out[i].Nstarpar      = in[i].Nstarpar;     
+
+    out[i].measureOffset = in[i].measureOffset; 
+    out[i].starparOffset = in[i].starparOffset;
+
+    out[i].refColorBlue  = in[i].refColorBlue;
+    out[i].refColorRed   = in[i].refColorRed;
+
+    out[i].flags     	 = in[i].flags;   
+    out[i].objID 	 = in[i].objID;
+    out[i].catID 	 = in[i].catID;
+  }
+  return (out);
+}
+
+SecFilt *SecFilt_PS1_SIM_ToInternal (SecFilt_PS1_SIM *in, off_t Nvalues) {
+
+  off_t i;
+  SecFilt *out;
+
+  ALLOCATE_ZERO (out, SecFilt, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_secfilt_init (&out[i]);
+
+    out[i].M             = in[i].M;      
+    out[i].dM            = in[i].dM;      
+    out[i].Ncode         = in[i].Ncode;
+    out[i].Nused         = in[i].Nused;
+    out[i].flags         = in[i].flags;     
+  }
+  return (out);
+}
+
+SecFilt_PS1_SIM *SecFiltInternalTo_PS1_SIM (SecFilt *in, off_t Nvalues) {
+
+  off_t i;
+  SecFilt_PS1_SIM *out;
+
+  ALLOCATE_ZERO (out, SecFilt_PS1_SIM, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+
+    out[i].M             = in[i].M;      
+    out[i].dM            = in[i].dM;      
+    out[i].Ncode         = in[i].Ncode;
+    out[i].Nused         = in[i].Nused;
+    out[i].flags         = in[i].flags;     
+  }
+  return (out);
+}
+
+StarPar *StarPar_PS1_SIM_ToInternal (StarPar_PS1_SIM *in, off_t Nvalues) {
+
+  off_t i;
+  StarPar *out;
+
+  ALLOCATE_ZERO (out, StarPar, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_starpar_init (&out[i]);
+
+    out[i].R  	     = in[i].R;      
+    out[i].D  	     = in[i].D;      
+    out[i].galLat    = in[i].galLat;      
+    out[i].galLon    = in[i].galLon;      
+
+    out[i].Ebv       = in[i].Ebv     ;      
+    out[i].dEbv      = in[i].dEbv    ;      
+    out[i].DistMag   = in[i].DistMag ;      
+    out[i].dDistMag  = in[i].dDistMag;      
+    out[i].M_r       = in[i].M_r     ;      
+    out[i].dM_r      = in[i].dM_r    ;      
+    out[i].FeH       = in[i].FeH     ;      
+    out[i].dFeH      = in[i].dFeH    ;      
+    out[i].uRA       = in[i].uRA     ;      
+    out[i].uDEC      = in[i].uDEC    ;      
+
+    out[i].averef  = in[i].averef;
+    out[i].objID   = in[i].objID ;
+    out[i].catID   = in[i].catID ;
+  }
+  return (out);
+}
+
+StarPar_PS1_SIM *StarParInternalTo_PS1_SIM (StarPar *in, off_t Nvalues) {
+
+  off_t i;
+  StarPar_PS1_SIM *out;
+
+  ALLOCATE_ZERO (out, StarPar_PS1_SIM, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+
+    out[i].R  	     = in[i].R;      
+    out[i].D  	     = in[i].D;      
+    out[i].galLat    = in[i].galLat;      
+    out[i].galLon    = in[i].galLon;      
+
+    out[i].Ebv       = in[i].Ebv     ;      
+    out[i].dEbv      = in[i].dEbv    ;      
+    out[i].DistMag   = in[i].DistMag ;      
+    out[i].dDistMag  = in[i].dDistMag;      
+    out[i].M_r       = in[i].M_r     ;      
+    out[i].dM_r      = in[i].dM_r    ;      
+    out[i].FeH       = in[i].FeH     ;      
+    out[i].dFeH      = in[i].dFeH    ;      
+    out[i].uRA       = in[i].uRA     ;      
+    out[i].uDEC      = in[i].uDEC    ;      
+
+    out[i].averef  = in[i].averef;
+    out[i].objID   = in[i].objID ;
+    out[i].catID   = in[i].catID ;
+  }
+  return (out);
+}
+
+# define RAW_IMAGE_NAME_LEN 117
+
+Image *Image_PS1_SIM_ToInternal (Image_PS1_SIM *in, off_t Nvalues, off_t Nalloc) {
+
+  off_t i;
+  Image *out;
+
+  char *buffer;
+  ALLOCATE_ZERO (buffer, char, Nalloc);
+  out = (Image *) buffer;
+  // ALLOCATE_ZERO (out, Image, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
+
+    // RAW_IMAGE_NAME_LEN == DVO_IMAGE_NAME_LEN
+    strncpy (out[i].name, in[i].name, RAW_IMAGE_NAME_LEN - 1);
+    out[i].name[RAW_IMAGE_NAME_LEN - 1] = 0; // force termination
+
+    out[i].tzero    	    = in[i].tzero;
+    out[i].nstar    	    = in[i].nstar;
+    out[i].secz	    	    = in[i].secz;
+    out[i].NX	    	    = in[i].NX;
+    out[i].NY	    	    = in[i].NY;
+    out[i].apmifit  	    = in[i].apmifit;
+    out[i].dapmifit 	    = in[i].dapmifit;
+    out[i].Mcal	    	    = in[i].Mcal;
+    out[i].dMcal    	    = in[i].dMcal;
+    out[i].Xm	    	    = in[i].Xm;
+    out[i].photcode   	    = in[i].photcode;
+    out[i].exptime  	    = in[i].exptime;
+    out[i].sidtime  	    = in[i].sidtime;
+    out[i].latitude  	    = in[i].latitude;
+
+    out[i].RAo  	    = in[i].RAo;
+    out[i].DECo  	    = in[i].DECo;
+    out[i].Radius  	    = in[i].Radius;
+    out[i].refColorBlue	    = in[i].refColorBlue;
+    out[i].refColorRed 	    = in[i].refColorRed;
+
+    out[i].detection_limit  = in[i].detection_limit;
+    out[i].saturation_limit = in[i].saturation_limit;
+    out[i].cerror	    = in[i].cerror;
+    out[i].fwhm_x	    = in[i].fwhm_x;
+    out[i].fwhm_y	    = in[i].fwhm_y;
+    out[i].trate	    = in[i].trate;
+    out[i].ccdnum	    = in[i].ccdnum;
+    out[i].flags	    = in[i].flags;
+    out[i].imageID	    = in[i].imageID;
+    out[i].parentID	    = in[i].parentID;
+    out[i].externID	    = in[i].externID;
+    out[i].sourceID	    = in[i].sourceID;
+
+    // as of 2011.02.03, the old Mx,My,..., Mxxxx,Myyyy have been deprecated and replaced
+    // with the following.  (no real databases used those values -- see
+    // libdvo/doc/dvo-images.txt)
+    out[i].nLinkAstrom	    = in[i].nLinkAstrom;
+    out[i].nLinkPhotom	    = in[i].nLinkPhotom;
+    out[i].ubercalDist	    = in[i].ubercalDist;
+    out[i].dXpixSys	    = in[i].dXpixSys;
+    out[i].dYpixSys	    = in[i].dYpixSys;
+    out[i].dMagSys	    = in[i].dMagSys;
+    out[i].nFitAstrom       = in[i].nFitAstrom;
+    out[i].nFitPhotom       = in[i].nFitPhotom;
+    out[i].photom_map_id    = in[i].photom_map_id;
+    out[i].astrom_map_id    = in[i].astrom_map_id;
+  }
+  return (out);
+}
+
+Image_PS1_SIM *ImageInternalTo_PS1_SIM (Image *in, off_t Nvalues) {
+
+  off_t i;
+  Image_PS1_SIM *out;
+
+  ALLOCATE_ZERO (out, Image_PS1_SIM, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    // this is only save because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+
+    // RAW_IMAGE_NAME_LEN == DVO_IMAGE_NAME_LEN
+    strncpy (out[i].name, in[i].name, RAW_IMAGE_NAME_LEN - 1);
+    out[i].name[RAW_IMAGE_NAME_LEN - 1] = 0; // force termination
+
+    out[i].tzero    	    = in[i].tzero;
+    out[i].nstar    	    = in[i].nstar;
+    out[i].secz	    	    = in[i].secz;
+    out[i].NX	    	    = in[i].NX;
+    out[i].NY	    	    = in[i].NY;
+    out[i].apmifit  	    = in[i].apmifit;
+    out[i].dapmifit 	    = in[i].dapmifit;
+    out[i].Mcal	    	    = in[i].Mcal;
+    out[i].dMcal    	    = in[i].dMcal;
+    out[i].Xm	    	    = in[i].Xm;
+    out[i].photcode   	    = in[i].photcode;
+    out[i].exptime  	    = in[i].exptime;
+    out[i].sidtime  	    = in[i].sidtime;
+    out[i].latitude  	    = in[i].latitude;
+
+    out[i].RAo  	    = in[i].RAo;
+    out[i].DECo  	    = in[i].DECo;
+    out[i].Radius  	    = in[i].Radius;
+    out[i].refColorBlue	    = in[i].refColorBlue;
+    out[i].refColorRed 	    = in[i].refColorRed;
+
+    out[i].detection_limit  = in[i].detection_limit;
+    out[i].saturation_limit = in[i].saturation_limit;
+    out[i].cerror	    = in[i].cerror;
+    out[i].fwhm_x	    = in[i].fwhm_x;
+    out[i].fwhm_y	    = in[i].fwhm_y;
+    out[i].trate	    = in[i].trate;
+    out[i].ccdnum	    = in[i].ccdnum;
+    out[i].flags	    = in[i].flags;
+    out[i].imageID	    = in[i].imageID;
+    out[i].parentID	    = in[i].parentID;
+    out[i].externID	    = in[i].externID;
+    out[i].sourceID	    = in[i].sourceID;
+
+    // as of 2011.02.03, the old Mx,My,..., Mxxxx,Myyyy have been deprecated and replaced
+    // with the following.  (no real databases used those values -- see
+    // libdvo/doc/dvo-images.txt)
+    out[i].nLinkAstrom	    = in[i].nLinkAstrom;
+    out[i].nLinkPhotom	    = in[i].nLinkPhotom;
+    out[i].ubercalDist	    = in[i].ubercalDist;
+    out[i].dXpixSys	    = in[i].dXpixSys;
+    out[i].dYpixSys	    = in[i].dYpixSys;
+    out[i].dMagSys	    = in[i].dMagSys;
+    out[i].nFitAstrom       = in[i].nFitAstrom;
+    out[i].nFitPhotom       = in[i].nFitPhotom;
+    out[i].photom_map_id    = in[i].photom_map_id;
+    out[i].astrom_map_id    = in[i].astrom_map_id;
+  }
+  return (out);
+}
+
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V1.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V1.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V1.c	(revision 37807)
@@ -238,5 +238,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
@@ -302,5 +305,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V2.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V2.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V2.c	(revision 37807)
@@ -249,5 +249,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
@@ -313,5 +316,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V3.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V3.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V3.c	(revision 37807)
@@ -259,5 +259,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
@@ -323,5 +326,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V4.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V4.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V4.c	(revision 37807)
@@ -299,5 +299,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only save because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic    = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
@@ -363,5 +366,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only save because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN > DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V5.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V5.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V5.c	(revision 37807)
@@ -191,4 +191,5 @@
     out[i].Nlensing      = in[i].Nlensing;     
     out[i].Nlensobj      = in[i].Nlensobj;     
+    out[i].Nstarpar      = in[i].Nstarpar;     
     out[i].Nextend       = in[i].Nextend;     
 
@@ -197,6 +198,6 @@
     out[i].lensingOffset = in[i].lensingOffset;
     out[i].lensobjOffset = in[i].lensobjOffset;
+    out[i].starparOffset = in[i].starparOffset;
     out[i].extendOffset  = in[i].extendOffset;
-    out[i].paramsOffset  = in[i].paramsOffset;
 
     out[i].refColorBlue  = in[i].refColorBlue;
@@ -258,4 +259,5 @@
     out[i].Nlensing       = in[i].Nlensing;     
     out[i].Nlensobj       = in[i].Nlensobj;     
+    out[i].Nstarpar       = in[i].Nstarpar;     
     out[i].Nextend        = in[i].Nextend;     
 			  
@@ -264,6 +266,6 @@
     out[i].lensingOffset  = in[i].lensingOffset;
     out[i].lensobjOffset  = in[i].lensobjOffset;
+    out[i].starparOffset  = in[i].starparOffset;
     out[i].extendOffset   = in[i].extendOffset;
-    out[i].paramsOffset   = in[i].paramsOffset;
 			  
     out[i].refColorBlue   = in[i].refColorBlue;
@@ -663,4 +665,69 @@
 }
 
+StarPar *StarPar_PS1_V5_ToInternal (StarPar_PS1_V5 *in, off_t Nvalues) {
+
+  off_t i;
+  StarPar *out;
+
+  ALLOCATE_ZERO (out, StarPar, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_starpar_init (&out[i]);
+
+    out[i].R  	     = in[i].R;      
+    out[i].D  	     = in[i].D;      
+    out[i].galLat    = in[i].galLat;      
+    out[i].galLon    = in[i].galLon;      
+
+    out[i].Ebv       = in[i].Ebv     ;      
+    out[i].dEbv      = in[i].dEbv    ;      
+    out[i].DistMag   = in[i].DistMag ;      
+    out[i].dDistMag  = in[i].dDistMag;      
+    out[i].M_r       = in[i].M_r     ;      
+    out[i].dM_r      = in[i].dM_r    ;      
+    out[i].FeH       = in[i].FeH     ;      
+    out[i].dFeH      = in[i].dFeH    ;      
+    out[i].uRA       = in[i].uRA     ;      
+    out[i].uDEC      = in[i].uDEC    ;      
+
+    out[i].averef  = in[i].averef;
+    out[i].objID   = in[i].objID ;
+    out[i].catID   = in[i].catID ;
+  }
+  return (out);
+}
+
+StarPar_PS1_V5 *StarParInternalTo_PS1_V5 (StarPar *in, off_t Nvalues) {
+
+  off_t i;
+  StarPar_PS1_V5 *out;
+
+  ALLOCATE_ZERO (out, StarPar_PS1_V5, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+
+    out[i].R  	     = in[i].R;      
+    out[i].D  	     = in[i].D;      
+    out[i].galLat    = in[i].galLat;      
+    out[i].galLon    = in[i].galLon;      
+
+    out[i].Ebv       = in[i].Ebv     ;      
+    out[i].dEbv      = in[i].dEbv    ;      
+    out[i].DistMag   = in[i].DistMag ;      
+    out[i].dDistMag  = in[i].dDistMag;      
+    out[i].M_r       = in[i].M_r     ;      
+    out[i].dM_r      = in[i].dM_r    ;      
+    out[i].FeH       = in[i].FeH     ;      
+    out[i].dFeH      = in[i].dFeH    ;      
+    out[i].uRA       = in[i].uRA     ;      
+    out[i].uDEC      = in[i].uDEC    ;      
+
+    out[i].averef  = in[i].averef;
+    out[i].objID   = in[i].objID ;
+    out[i].catID   = in[i].catID ;
+  }
+  return (out);
+}
+
 # define RAW_IMAGE_NAME_LEN 117
 
@@ -676,5 +743,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN == DVO_IMAGE_NAME_LEN
@@ -741,5 +811,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only save because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN == DVO_IMAGE_NAME_LEN
@@ -920,6 +991,6 @@
     out[i].lensingOffset  = in[i].lensingOffset;
     out[i].lensobjOffset  = in[i].lensobjOffset;
+    out[i].starparOffset  = in[i].starparOffset;
     out[i].extendOffset   = in[i].extendOffset;
-    out[i].paramsOffset   = in[i].paramsOffset;
 			  
     out[i].refColorBlue   = in[i].refColorBlue;
@@ -1194,2 +1265,55 @@
 
 
+Lensing *Lensing_PS1_V5_ALT_ToInternal (Lensing_PS1_V5_ALT *in, off_t Nvalues) {
+
+  off_t i;
+  Lensing *out;
+
+  ALLOCATE_ZERO (out, Lensing, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_lensing_init (&out[i]);
+
+    out[i].X11_sm_obj  = in[i].X11_sm_obj;      
+    out[i].X12_sm_obj  = in[i].X12_sm_obj;      
+    out[i].X22_sm_obj  = in[i].X22_sm_obj;      
+    out[i].E1_sm_obj   = in[i].E1_sm_obj;      
+    out[i].E2_sm_obj   = in[i].E2_sm_obj;      
+	             	                 
+    out[i].X11_sh_obj  = in[i].X11_sh_obj;      
+    out[i].X12_sh_obj  = in[i].X12_sh_obj;      
+    out[i].X22_sh_obj  = in[i].X22_sh_obj;      
+    out[i].E1_sh_obj   = in[i].E1_sh_obj;      
+    out[i].E2_sh_obj   = in[i].E2_sh_obj;     
+	             	                 
+    out[i].X11_sm_psf  = in[i].X11_sm_psf;
+    out[i].X12_sm_psf  = in[i].X12_sm_psf;
+    out[i].X22_sm_psf  = in[i].X22_sm_psf;
+    out[i].E1_sm_psf   = in[i].E1_sm_psf;
+    out[i].E2_sm_psf   = in[i].E2_sm_psf;     
+	             	                 
+    out[i].X11_sh_psf  = in[i].X11_sh_psf;
+    out[i].X12_sh_psf  = in[i].X12_sh_psf;
+    out[i].X22_sh_psf  = in[i].X22_sh_psf;
+    out[i].E1_sh_psf   = in[i].E1_sh_psf;
+    out[i].E2_sh_psf   = in[i].E2_sh_psf;
+
+    out[i].F_ApR5      = in[i].F_ApR5;
+    out[i].dF_ApR5     = in[i].dF_ApR5;
+    out[i].sF_ApR5     = in[i].sF_ApR5;
+    out[i].fF_ApR5     = in[i].fF_ApR5;
+	               	            
+    out[i].F_ApR6      = in[i].F_ApR6;
+    out[i].dF_ApR6     = in[i].dF_ApR6;
+    out[i].sF_ApR6     = in[i].sF_ApR6;
+    out[i].fF_ApR6     = in[i].fF_ApR6;
+	          	            
+    out[i].detID       = in[i].detID;
+    out[i].objID       = in[i].objID;
+    out[i].catID       = in[i].catID;
+
+    out[i].averef      = in[i].averef;
+  }
+  return (out);
+}
+
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_elixir.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_elixir.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_elixir.c	(revision 37807)
@@ -210,5 +210,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
@@ -280,5 +283,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_loneos.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_loneos.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_loneos.c	(revision 37807)
@@ -188,5 +188,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
@@ -259,5 +262,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_panstarrs_DEV_0.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_panstarrs_DEV_0.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_panstarrs_DEV_0.c	(revision 37807)
@@ -227,5 +227,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
@@ -297,5 +300,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_convert_panstarrs_DEV_1.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_convert_panstarrs_DEV_1.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_convert_panstarrs_DEV_1.c	(revision 37807)
@@ -227,5 +227,8 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
+    out[i].coords.mosaic   = NULL;
+    out[i].coords.offsetMap = NULL;
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
@@ -297,5 +300,6 @@
 
   for (i = 0; i < Nvalues; i++) {
-    memcpy (&out[i].coords, &in[i].coords, sizeof(Coords));
+    // this is only safe because the initial 120 bytes in Coords match CoordsDisk
+    memcpy (&out[i].coords, &in[i].coords, sizeof(CoordsDisk));
 
     // RAW_IMAGE_NAME_LEN < DVO_IMAGE_NAME_LEN
Index: /trunk/Ohana/src/libdvo/src/dvo_image.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_image.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_image.c	(revision 37807)
@@ -200,5 +200,5 @@
 
   gfits_create_matrix (&db[0].header, &db[0].matrix);
-  gfits_table_set_Image (&db[0].ftable, NULL, 0);
+  gfits_table_set_Image (&db[0].ftable);
 
   gfits_modify (&db[0].header, "NIMAGES", "%d", 1, 0);
@@ -207,5 +207,5 @@
   dvo_image_createID (&db[0].header);
 
-  if (db[0].format == DVO_FORMAT_INTERNAL)  	  gfits_modify (&db[0].header, "FORMAT", "%s", 1, "INTERNAL");
+  // if (db[0].format == DVO_FORMAT_INTERNAL)  	  gfits_modify (&db[0].header, "FORMAT", "%s", 1, "INTERNAL");
   if (db[0].format == DVO_FORMAT_LONEOS)    	  gfits_modify (&db[0].header, "FORMAT", "%s", 1, "LONEOS");
   if (db[0].format == DVO_FORMAT_ELIXIR)    	  gfits_modify (&db[0].header, "FORMAT", "%s", 1, "ELIXIR");
@@ -221,4 +221,5 @@
   if (db[0].format == DVO_FORMAT_PS1_V5)          gfits_modify (&db[0].header, "FORMAT", "%s", 1, "PS1_V5");
   if (db[0].format == DVO_FORMAT_PS1_REF)         gfits_modify (&db[0].header, "FORMAT", "%s", 1, "PS1_REF");
+  if (db[0].format == DVO_FORMAT_PS1_SIM)         gfits_modify (&db[0].header, "FORMAT", "%s", 1, "PS1_SIM");
   
   return;
@@ -267,2 +268,121 @@
 }
 
+int gfits_table_set_Image (FTable *ftable) {
+
+  Header *header;
+
+  header = ftable[0].header;
+
+  gfits_table_mkheader_Image (header);
+  
+  /* create table */
+  if (!gfits_create_table (header, ftable)) return (FALSE);
+
+  return (TRUE);
+}
+
+int gfits_table_mkheader_Image (Header *header) {
+
+  /* create table header */
+  if (!gfits_create_table_header (header, "BINTABLE", "DVO_IMAGE")) return (FALSE);
+
+  /* define table layout */
+  /** TABLE DEFINITION **/
+  gfits_define_bintable_column (header, "D",    "CRVAL1",           "coordinate at reference pixel",   "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "D",    "CRVAL2",           "coordinate at reference pixel",   "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "CRPIX1",           "coordinate of reference pixel",   "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "CRPIX2",           "coordinate of reference pixel",   "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "CDELT1",           "degrees per pixel",               "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "CDELT2",           "degrees per pixel",               "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "PC1_1",            "rotation matrix",                 "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "PC1_2",            "rotation matrix",                 "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "PC2_1",            "rotation matrix",                 "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "PC2_2",            "rotation matrix",                 "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "14E",  "POLYTERMS",        "higher order warping terms",      "",                  1.0, 0.0);
+  if (sizeof(void *) == 4) {
+    gfits_define_bintable_column (header, "J",    "POINTER1",       "",  "",                  1.0, 0.0);
+    gfits_define_bintable_column (header, "J",    "POINTER2",       "",  "",                  1.0, 0.0);
+  }
+  if (sizeof(void *) == 8) {
+    gfits_define_bintable_column (header, "K",    "POINTER1",       "",  "",                  1.0, 0.0);
+    gfits_define_bintable_column (header, "K",    "POINTER2",       "",  "",                  1.0, 0.0);
+  }
+  gfits_define_bintable_column (header, "15A",  "CTYPE",            "coordinate type",                 "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "A",    "NPOLYTERMS",       "order of polynomial",             "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "TZERO",            "readout time (row 0)",            "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "NSTAR",            "number of stars on image",        "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "SECZ",             "airmass",                         "mag",               1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "NX",               "image width",                     "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "NY",               "image height",                    "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "APMIFIT",          "aperture correction",             "mag",               1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "DAPMIFIT",         "apmifit error",                   "mag",               1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "MCAL",             "calibration mag",                 "mag",               1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "DMCAL",            "error on Mcal",                   "mag",               1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "XM",               "image chisq",                     "10*log(value)",     1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "PHOTCODE",         "identifier for CCD,",             "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "EXPTIME",          "exposure time",                   "seconds",           1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "ST",               "sidereal time of exposure",       "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "LAT",              "observatory latitude",            "degrees",           1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "RA_CENTER",        "image center",                    "degrees",           1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "DEC_CENTER",       "image center",                    "degrees",           1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "RADIUS",           "image radius",                    "degrees",           1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "REF_COLOR_BLUE",   "median astrometry ref color",     "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "REF_COLOR_RED",    "median astrometry ref color",     "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "117A", "NAME",             "name of original image ",         "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "DETECTION_LIMIT",  "detection limit",                 "10*mag",            1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "SATURATION_LIMIT", "saturation limit",                "10*mag",            1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "CERROR",           "astrometric error",               "50*arcsec",         1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "FWHM_X",           "PSF x width",                     "25*arcsec",         1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "FWHM_Y",           "PSF y width",                     "25*arcsec",         1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "TRATE",            "scan rate",                       "100 usec/pixel",    1.0, 0.0);
+  gfits_define_bintable_column (header, "B",    "CCDNUM",           "CCD ID number",                   "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "FLAGS",            "image quality flags",             "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "IMAGE_ID",         "internal image ID",               "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "PARENT_ID",        "associated ref image",            "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "EXTERN_ID",        "external image ID",               "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "SOURCE_ID",        "analysis source ID",              "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "NLINK_ASTROM",     "mean number of matched measurements for astrometry", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "NLINK_PHOTOM",     "mean number of matched measurements for astrometry", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "UBERCAL_DIST",     "distance to nearest ubercal image", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "XPIX_SYS_ERR",     "systematic astrometry error in X", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "YPIX_SYS_ERR",     "systematic astrometry error in Y", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "E",    "MAG_SYS_ERR",      "systematic photometry error",     "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "N_FIT_ASTROM",     "number of stars used for astrometry cal", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "I",    "N_FIT_PHOTOM",     "number of stars used for photometry cal", "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "PHOTOM_MAP_ID",    "reference to 2D zero point map",  "",                  1.0, 0.0);
+  gfits_define_bintable_column (header, "J",    "ASTROM_MAP_ID",    "reference to 2D astrometry map",  "",                  1.0, 0.0);
+  if (sizeof(void *) == 4) {
+    gfits_define_bintable_column (header, "J",    "POINTER3",       "",  "",                  1.0, 0.0);
+  }
+  if (sizeof(void *) == 8) {
+    gfits_define_bintable_column (header, "K",    "POINTER3",       "",  "",                  1.0, 0.0);
+  }
+
+  return (TRUE);
+}
+
+/* return internal structure representation */
+Image *gfits_table_get_Image (FTable *ftable, off_t *Ndata, char *swapped) {
+
+  int Ncols;
+  Image *data;
+
+  Ncols = ftable[0].header[0].Naxis[0];
+  if (Ncols != sizeof(Image)) {
+    fprintf (stderr, "ERROR: mis-match in table size: width is %d but should be %d bytes\n", Ncols, (int) sizeof(Image));
+    return NULL;
+  }
+
+  *Ndata = ftable[0].header[0].Naxis[1];
+  data = (Image *) ftable[0].buffer;
+
+  if (!swapped) {
+    myAbort ("invalid to call this without suppying 'swapped'");
+  }
+  if (*swapped == FALSE) {
+    myAbort ("invalid for table to be swapped");
+  }
+    
+  return (data);
+}
+
Index: /trunk/Ohana/src/libdvo/src/dvo_image_raw.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_image_raw.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_image_raw.c	(revision 37807)
@@ -61,4 +61,5 @@
   if (db[0].format == DVO_FORMAT_PS1_V5)          ImageSize = sizeof(Image_PS1_V5);
   if (db[0].format == DVO_FORMAT_PS1_REF)         ImageSize = sizeof(Image_PS1_REF);
+  if (db[0].format == DVO_FORMAT_PS1_SIM)         ImageSize = sizeof(Image_PS1_SIM);
 
   /* check that filesize makes sense */
@@ -92,4 +93,5 @@
   if (db[0].format == DVO_FORMAT_PS1_V5)          gfits_table_mkheader_Image_PS1_V5 (&db[0].theader);
   if (db[0].format == DVO_FORMAT_PS1_REF)         gfits_table_mkheader_Image_PS1_REF (&db[0].theader);
+  if (db[0].format == DVO_FORMAT_PS1_SIM)         gfits_table_mkheader_Image_PS1_SIM (&db[0].theader);
     
   /* read data from file */
Index: /trunk/Ohana/src/libdvo/src/dvo_photcode_ops.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_photcode_ops.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_photcode_ops.c	(revision 37807)
@@ -1788,2 +1788,36 @@
   m2 = (Ns2 == -1) ? average[0].M : secfilt[Ns2].M;
 */
+
+/*** lensing *********************************************************************************/
+
+# define LENSFIELD(NAME) \
+  float LensValue_##NAME (PhotCode *code, Lensobj *lensobj) { \
+  if (code == NULL) return NAN; \
+  int Ns = photcodes->hashNsec[code->code]; \
+  float value = (Ns == -1) ? NAN : lensobj[Ns].NAME; \
+  return (value); \
+}
+
+LENSFIELD(X11_sm_obj);
+LENSFIELD(X12_sm_obj);
+LENSFIELD(X22_sm_obj);
+LENSFIELD(E1_sm_obj);
+LENSFIELD(E2_sm_obj);
+
+LENSFIELD(X11_sh_obj);
+LENSFIELD(X12_sh_obj);
+LENSFIELD(X22_sh_obj);
+LENSFIELD(E1_sh_obj);
+LENSFIELD(E2_sh_obj);
+
+LENSFIELD(X11_sm_psf);
+LENSFIELD(X12_sm_psf);
+LENSFIELD(X22_sm_psf);
+LENSFIELD(E1_sm_psf);
+LENSFIELD(E2_sm_psf);
+
+LENSFIELD(X11_sh_psf);
+LENSFIELD(X12_sh_psf);
+LENSFIELD(X22_sh_psf);
+LENSFIELD(E1_sh_psf);
+LENSFIELD(E2_sh_psf);
Index: /trunk/Ohana/src/libdvo/src/dvo_tiny_values.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_tiny_values.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_tiny_values.c	(revision 37807)
@@ -20,4 +20,6 @@
   measureT[0].dM         = measure[0].dM;
   measureT[0].airmass    = measure[0].airmass;
+  measureT[0].RoffGAL    = measure[0].RoffGAL;
+  measureT[0].DoffGAL    = measure[0].DoffGAL;
   measureT[0].Xccd       = measure[0].Xccd;
   measureT[0].Yccd       = measure[0].Yccd;
Index: /trunk/Ohana/src/libdvo/src/dvo_util.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_util.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/dvo_util.c	(revision 37807)
@@ -145,10 +145,5 @@
         if ((image->externID == externID) && (image->sourceID == sourceID)) {
             BuildChipMatch(dvoConfig->images, dvoConfig->nImages);
-            if (FindMosaicForImage(dvoConfig->images, dvoConfig->nImages, i)) {
-                return image;
-            } else {
-                fprintf(stderr, "FindMosaicForImage failed\n");
-                return NULL;
-            }
+	    return image;
         }
     }
Index: /trunk/Ohana/src/libdvo/src/flatcorr_io.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/flatcorr_io.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/flatcorr_io.c	(revision 37807)
@@ -183,4 +183,11 @@
 }
 
+// eddie uses cell_x = (int) (X + X_PAD) / (base + X_PAD)
+// where base = 600, X_PAD = 8, Y_PAD = 10.  denominator is thus (608,610), whic is (4880/8),(4864/8)
+// these match Eddie's NCELL_X,Y / CHIP_DX,DY, so I think we are good with this:
+
+# define X_PAD 8
+# define Y_PAD 10
+
 float FlatCorrectionOffset (FlatCorrectionTable *flatcorr, int ID, int X, int Y) {
 
@@ -191,6 +198,6 @@
 
   // convert X,Y to Xbin, Ybin:
-  int Xbin = MAX(MIN(X * flatcorr->image[seq].Nx / flatcorr->image[seq].DX, flatcorr->image[seq].Nx - 1), 0);
-  int Ybin = MAX(MIN(Y * flatcorr->image[seq].Ny / flatcorr->image[seq].DY, flatcorr->image[seq].Ny - 1), 0);
+  int Xbin = MAX(MIN((X + X_PAD) * flatcorr->image[seq].Nx / flatcorr->image[seq].DX, flatcorr->image[seq].Nx - 1), 0);
+  int Ybin = MAX(MIN((Y + Y_PAD) * flatcorr->image[seq].Ny / flatcorr->image[seq].DY, flatcorr->image[seq].Ny - 1), 0);
 
   // XXX warn if X,Y are out of range? not super important unless *way* out of range
Index: /trunk/Ohana/src/libdvo/src/galaxy_model.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/galaxy_model.c	(revision 37807)
+++ /trunk/Ohana/src/libdvo/src/galaxy_model.c	(revision 37807)
@@ -0,0 +1,148 @@
+# include <dvo.h>
+
+// velocity / distance / proper-motion scale:
+static double iFkap = 0.001 / 4.74047; // (km/sec/kpc) / (arcsec/ year)
+
+double A_oort = 0.0; // km/sec/kpc
+double B_oort = 0.0; // km/sec/kpc
+double U_sol  = 0.0; // km/sec
+double V_sol  = 0.0; // km/sec
+double W_sol  = 0.0; // km/sec
+
+int InitGalaxyModel (char *version) {
+
+  // galaxy model parameters from Siegfreid Roeser:
+  if (!strcmp(version, "ROESER")) {
+    A_oort = +14.50; // km/sec/kpc
+    B_oort = -13.00; // km/sec/kpc
+    U_sol  =   9.44; // km/sec
+    V_sol  =  11.90; // km/sec
+    W_sol  =   7.20; // km/sec
+    return TRUE;
+  }
+  if (!strcmp(version, "FEAST-HIPPARCOS")) {
+    // http://arxiv.org/pdf/astro-ph/9706293v1.pdf
+    A_oort = +14.82; // km/sec/kpc
+    B_oort = -12.37; // km/sec/kpc
+    U_sol  =   9.32; // km/sec
+    V_sol  =  11.18; // km/sec
+    W_sol  =   7.61; // km/sec
+    return TRUE;
+  }
+  return FALSE;
+}
+
+int GalaxyMotionModel (double *uL_gal, double *uB_gal, double L, double B) {
+  double Lrad = L * RAD_DEG;
+  double Brad = B * RAD_DEG;
+  *uL_gal =     (A_oort * cos(2.0*Lrad) + B_oort) * cos(Brad)     * iFkap;
+  *uB_gal = -0.5*A_oort * sin(2.0*Lrad)           * sin(Brad*2.0) * iFkap;
+  return TRUE;
+}
+
+int GalaxyMotionModel_radians (double *uL_gal, double *uB_gal, double Lrad, double Brad) {
+  *uL_gal =     (A_oort * cos(2.0*Lrad) + B_oort) * cos(Brad)     * iFkap;
+  *uB_gal = -0.5*A_oort * sin(2.0*Lrad) *           sin(Brad*2.0) * iFkap;
+  return TRUE;
+}
+
+int SolarMotionModel (double *uL_sol, double *uB_sol, double L, double B, double distance) {
+  double Lrad = L * RAD_DEG;
+  double Brad = B * RAD_DEG;
+  *uL_sol =  (U_sol * sin(Lrad) - V_sol * cos(Lrad))                              * iFkap / distance;
+  *uB_sol = ((U_sol * cos(Lrad) + V_sol * sin(Lrad))*sin(Brad) - W_sol*cos(Brad)) * iFkap / distance;
+  return TRUE;
+}
+
+int SolarMotionModel_radians (double *uL_sol, double *uB_sol, double Lrad, double Brad, double distance) {
+  *uL_sol =  (U_sol * sin(Lrad) - V_sol * cos(Lrad))                              * iFkap / distance;
+  *uB_sol = ((U_sol * cos(Lrad) + V_sol * sin(Lrad))*sin(Brad) - W_sol*cos(Brad)) * iFkap / distance;
+  return TRUE;
+}
+
+// Use the inverse transform: CELESTIAL->GALACTIC for uL,uB -> uR,uD
+int TransformProperMotion (double *uR, double *uD, double uL, double uB, double R, double D, CoordTransform *transform) {
+
+  double Rrad = R*RAD_DEG;
+  double Drad = D*RAD_DEG;
+
+  // C1, C2 are from http://arxiv.org/pdf/1306.2945v2.pdf
+  double C1 = 
+    cos(Drad)*transform->cos_phi +
+    sin(Drad)*cos(Rrad)*transform->sin_phi_sin_Xo - 
+    sin(Drad)*sin(Rrad)*transform->sin_phi_cos_Xo;
+
+  double C2 =
+    - cos(Rrad)*transform->sin_phi_cos_Xo
+    - sin(Rrad)*transform->sin_phi_sin_Xo;
+
+  double cosBinv = 1.0 / sqrt(C1*C1 + C2*C2);
+
+  // XXX add errors : I need to be able to choose the stars based on the error distribution
+  *uR = cosBinv * (C1 * uL - C2 * uB);
+  *uD = cosBinv * (C1 * uB + C2 * uL);
+
+  return TRUE;
+}
+int TransformProperMotion_radians (double *uR, double *uD, double uL, double uB, double Rrad, double Drad, CoordTransform *transform) {
+
+  // C1, C2 are from http://arxiv.org/pdf/1306.2945v2.pdf
+  double C1 = 
+    cos(Drad)*transform->cos_phi +
+    sin(Drad)*cos(Rrad)*transform->sin_phi_sin_Xo - 
+    sin(Drad)*sin(Rrad)*transform->sin_phi_cos_Xo;
+
+  double C2 =
+    - cos(Rrad)*transform->sin_phi_cos_Xo
+    - sin(Rrad)*transform->sin_phi_sin_Xo;
+
+  double cosBinv = 1.0 / sqrt(C1*C1 + C2*C2);
+
+  // XXX add errors : I need to be able to choose the stars based on the error distribution
+  *uR = cosBinv * (C1 * uL - C2 * uB);
+  *uD = cosBinv * (C1 * uB + C2 * uL);
+
+  return TRUE;
+}
+
+// I am using the galactic rotation and solar motion model to predict the reflex proper
+// motion of stars in the database. 
+
+/*****
+
+From Siegfreid Roeser:
+
+Dear Gene,
+
+ please find attached our approach for galactic rotation and
+ solar motion that we assumed in our approach last fall.
+(Hope I understood correctly what you requested at the
+telecon). If not, please come back.
+
+      fkap = 4.74047d-0   {km/s/kpc}/{mas/y}
+
+      gl,gb  is galactic longitude resp. latitude
+
+
+      PARAMETER (distance= 1.d0,Aoort=14.5d0 ,Boort=-13.d0  )
+                      kpc           km/s/kpc   km/s/kpc
+      PARAMETER (Usol = 9.44d0, Vsol =11.90d0, Wsol = 7.20d0)
+                                   km/s
+C
+C      Gal. Rotation
+C
+      proper motions in longitude/latitude
+
+      emulgal = (AOORT*dcos(2.d0*gl)+BOORT)*dcos(gb)/fkap
+      emubgal = -0.5d0*AOORT*dsin(2.d0*gl)*dsin(2.d0*gb)/fkap
+c
+c     Solar Motion
+cc
+      emulsol = Usol*dsin(gl) - Vsol*dcos(gl)
+      emulsol = emulsol/fkap/distance
+      emubsol = Usol*dsin(gb)*dcos(gl)+Vsol*dsin(gb)*dsin(gl)
+      emubsol = emubsol -Wsol*dcos(gb)
+      emubsol = emubsol/fkap/distance
+
+
+******/
Index: /trunk/Ohana/src/libdvo/src/mosaic_astrom.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/mosaic_astrom.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/mosaic_astrom.c	(revision 37807)
@@ -1,6 +1,11 @@
 # include <dvo.h>
+
+//  the block of code below was used to define the chip->mosaic relationship before this
+//  was assigned to the coords value
 
 off_t getDISentry (e_time start, e_time stop, e_time *startMos, off_t *indexMos, off_t Nmosaic);
 void SortDISindex (e_time *S, off_t *I, off_t N);
+
+# if (0)
 
 /* chip-match table: j = ChipMatch[i], images[j] is DIS for images[i] WRP */
@@ -93,5 +98,6 @@
 }
 
-int BuildChipMatch_old (Image *images, off_t Nimages) {
+// this is a very old version of BuildChipMatch which does not use bisection
+int BuildChipMatch_nosort (Image *images, off_t Nimages) {
 
   off_t i, j, NDIS;
@@ -140,5 +146,6 @@
 }
 
-int BuildChipMatch (Image *images, off_t Nimages) {
+// this is an old version of BuildChipMatch which stores the results here in a local static variable
+int BuildChipMatch_static (Image *images, off_t Nimages) {
 
   off_t i, j, NDIS;
@@ -198,8 +205,61 @@
   return (TRUE);
 }
-
+# endif
+
+// XXX Note that this links (images[i].coords.mosaic, images[i].parent) will break
+// if we reallocate the Image array in the middle of program
+int BuildChipMatch (Image *images, off_t Nimages) {
+
+  off_t i, j, NDIS;
+
+  off_t  Ndis = 0;
+  off_t  *DISentry = NULL;
+  e_time *DIStzero = NULL;
+
+  if (DISentry != NULL) free (DISentry);
+  if (DIStzero != NULL) free (DIStzero);
+
+  // allocate containers for DIS indexing
+  Ndis = 0;
+  NDIS = 100;
+  ALLOCATE (DISentry, off_t, NDIS);
+  ALLOCATE (DIStzero, e_time, NDIS);
+
+  // find all DIS images, save tzero (& photcode?) 
+  for (i = 0; i < Nimages; i++) {
+    if (strcmp(&images[i].coords.ctype[4], "-DIS")) continue;
+    DISentry[Ndis] = i;
+    DIStzero[Ndis] = images[i].tzero;
+    Ndis ++;
+    if (Ndis >= NDIS) {
+      NDIS += 100;
+      REALLOCATE (DISentry, off_t, NDIS);
+      REALLOCATE (DIStzero, e_time, NDIS);
+    }
+  }
+
+  // sort the index, start, and stop by the start times:
+  SortDISindex (DIStzero, DISentry, Ndis);
+
+  /* find all matched WRP images */
+  for (i = 0; i < Nimages; i++) {
+    images[i].parent        = NULL; // reset to NULL
+    images[i].coords.mosaic = NULL; // reset to NULL
+    if (strcmp(&images[i].coords.ctype[4], "-WRP")) continue; // only define link for WRP coords
+
+    j = getDISentry (images[i].tzero, images[i].tzero + (int) images[i].exptime, DIStzero, DISentry, Ndis);
+    if (j == -1) {
+      fprintf (stderr, "WARNING: can't find matching mosaic \n");
+      continue;
+    }
+    if (j >= Nimages) myAbort("invalid DIS entry");
+    images[i].parent = &images[j];
+    images[i].coords.mosaic = &images[j].coords;
+  }
+  return (TRUE);
+}
+
+// use bisection to find the overlapping mosaic
 off_t getDISentry (e_time start, e_time stop, e_time *startMos, off_t *indexMos, off_t Nmosaic) {
-
-  // use bisection to find the overlapping mosaic
 
   off_t Nlo, Nhi, N;
Index: /trunk/Ohana/src/libdvo/src/skyregion_gsc.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/skyregion_gsc.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/skyregion_gsc.c	(revision 37807)
@@ -204,5 +204,5 @@
   for (i = 0; i < Nregions; i++) {
     strncpy (temp, &buffer[i*48], 48);
-    temp[49] = 0;
+    temp[48] = 0;
     hstgsc_hms_to_deg (&Rmin, &Rmax, &Dmin, &Dmax, &temp[7]);
     if (Dmax < Dmin) SWAP (Dmin, Dmax);
Index: /trunk/Ohana/src/libdvo/src/skyregion_ops.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/skyregion_ops.c	(revision 37806)
+++ /trunk/Ohana/src/libdvo/src/skyregion_ops.c	(revision 37807)
@@ -16,4 +16,22 @@
   if (c) return (c+1);
   return name;
+}
+
+/* find region which corresponds to the given index */
+SkyList *SkyRegionByIndex (SkyTable *table, int index) {
+  
+  SkyList *list;
+
+  ALLOCATE (list, SkyList, 1);
+  ALLOCATE (list[0].regions,  SkyRegion *, 1);
+  ALLOCATE (list[0].filename,  char *, 1);
+  list[0].Nregions = 0;
+  list[0].ownElements = FALSE; // this list is only holding a view to the elements
+  strcpy (list[0].hosts, table[0].hosts);
+
+  list[0].regions[0] = &table[0].regions[index];
+  list[0].filename[0] = table[0].filename[index];
+  list[0].Nregions = 1;
+  return (list);
 }
 
@@ -250,15 +268,13 @@
   }
 
-  if( Rmax > (Rmin+180.)) {
-    
-      double temp = Rmax;
-      Rmax = Rmin;
-      Rmin = temp;
-  }
-
   list = SkyListByBounds (table, depth, Rmin, Rmax, Dmin, Dmax);
-  return (list);
-}
-
+
+  // fprintf (stderr, "%s : %f - %f, %f - %f : %d\n", 
+  // image->name, Rmin, Rmax, Dmin, Dmax, (int) list->Nregions);
+
+  return (list);
+}
+
+// a region like -10 10 0 20 will become 350 10 0 20 and return all regions on both sides of 0,360
 SkyList *SkyListByBounds (SkyTable *table, int depth, double Rmin, double Rmax, double Dmin, double Dmax) {
 
Index: /trunk/Ohana/src/libfits/Makefile
===================================================================
--- /trunk/Ohana/src/libfits/Makefile	(revision 37806)
+++ /trunk/Ohana/src/libfits/Makefile	(revision 37807)
@@ -32,6 +32,7 @@
 $(HEAD)/F_scan.$(ARCH).o			$(HEAD)/F_print.$(ARCH).o     \
 $(HEAD)/F_copy_H.$(ARCH).o			$(HEAD)/F_delete.$(ARCH).o    \
-$(HEAD)/F_read_XH.$(ARCH).o			$(HEAD)/F_init_H.$(ARCH).o    \
-$(HEAD)/F_convert_H.$(ARCH).o                   $(HEAD)/version.$(ARCH).o
+$(HEAD)/F_find_H.$(ARCH).o			$(HEAD)/F_init_H.$(ARCH).o    \
+$(HEAD)/F_convert_H.$(ARCH).o                   $(HEAD)/F_read_XH.$(ARCH).o   \
+$(HEAD)/version.$(ARCH).o
 
 MATRIX_OBJ = \
Index: /trunk/Ohana/src/libfits/header/F_find_H.c
===================================================================
--- /trunk/Ohana/src/libfits/header/F_find_H.c	(revision 37807)
+++ /trunk/Ohana/src/libfits/header/F_find_H.c	(revision 37807)
@@ -0,0 +1,39 @@
+# include <ohana.h>
+# include <gfitsio.h>
+
+/*********************** fits find (and read) named header, leave file pointer and end of header *******/
+int gfits_find_Xheader (FILE *f, Header *header, char *extname) {
+
+  char tname[80];
+
+  // start at the very beginning (no other info)
+  fseeko (f, 0, SEEK_SET);
+
+  // read the PHU header
+  if (!gfits_load_header (f, header)) return FALSE;
+
+  // skip to next header 
+  off_t Nbytes = gfits_data_size (header);
+  fseeko (f, Nbytes, SEEK_CUR);
+  gfits_free_header (header);
+
+  while (TRUE) {
+    // load data for this header 
+    if (!gfits_load_header (f, header)) return FALSE;
+
+    // grab the EXTNAME field
+    bzero (tname, 80);
+    if (!gfits_scan (header, "EXTNAME", "%s", 1, tname)) goto next_header;
+
+    // check if this is the correct extension or not 
+    if (strcmp (tname, extname)) goto next_header;
+    return TRUE;
+
+  next_header:
+    /* skip to next header */
+    Nbytes = gfits_data_size (header);
+    fseeko (f, Nbytes, SEEK_CUR);
+    gfits_free_header (header);
+  }
+  myAbort("impossible");
+}	
Index: /trunk/Ohana/src/libfits/header/F_free_H.c
===================================================================
--- /trunk/Ohana/src/libfits/header/F_free_H.c	(revision 37806)
+++ /trunk/Ohana/src/libfits/header/F_free_H.c	(revision 37807)
@@ -4,4 +4,6 @@
 /*********************** fits free header ***********************************/
 void gfits_free_header (Header *header) {
+
+  // fprintf (stderr, "free data for %zx\n", (size_t) header->buffer);
 
   if (header[0].buffer == (char *) NULL) return;
Index: /trunk/Ohana/src/libfits/include/gfitsio.h
===================================================================
--- /trunk/Ohana/src/libfits/include/gfitsio.h	(revision 37806)
+++ /trunk/Ohana/src/libfits/include/gfitsio.h	(revision 37807)
@@ -17,4 +17,7 @@
 # define NEWLINE                 10  /* UNIX RETURN character */
 # endif /* NEWLINE */
+
+/* gfits_bintable_format returns 'byte' as the type associated with one-byte logical (non-char) values */
+typedef char byte;
 
 /********** FITS Constants *********/
@@ -116,4 +119,5 @@
 int     gfits_modify_alt               PROTO((Header *header, char *field, char *mode, int N,...)); 
 int     gfits_print_alt                PROTO((Header *header, char *field, char *mode, int N,...)); 
+int     gfits_find_Xheader             PROTO((FILE *f, Header *header, char *extname));
 int     gfits_read_Xheader             PROTO((char *filename, Header *header, int N));
 int     gfits_read_header              PROTO((char *filename, Header *header));
@@ -184,5 +188,5 @@
 int   	gfits_fread_ftable             PROTO((FILE *f, FTable *ftable, char *extname)); 
 int   	gfits_fread_ftable_data        PROTO((FILE *f, FTable *ftable, int padIfShort));
-int   	gfits_fread_ftable_range       PROTO((FILE *f, FTable *ftable, off_t start, off_t Nrows));
+int   	gfits_fread_ftable_range       PROTO((FILE *f, int padIfShort, FTable *ftable, off_t start, off_t Nrows));
 int   	gfits_fread_vtable             PROTO((FILE *f, VTable *vtable, char *extname, off_t Nrow, off_t *row));
 int   	gfits_fread_vtable_range       PROTO((FILE *f, VTable *vtable, off_t start, off_t Nrows));
Index: /trunk/Ohana/src/libfits/table/F_read_T.c
===================================================================
--- /trunk/Ohana/src/libfits/table/F_read_T.c	(revision 37806)
+++ /trunk/Ohana/src/libfits/table/F_read_T.c	(revision 37807)
@@ -19,33 +19,13 @@
 int gfits_fread_ftable (FILE *f, FTable *table, char *extname) {
 
-  int j, found;
-  off_t Nbytes;
   Header *header;
-  char tname[80];
 
   header = table[0].header;
-  fseeko (f, 0, SEEK_SET);
-
-  found = FALSE;
-  for (j = -1; !found; j++) {
-    /* load data for this header */
-    if (!gfits_load_header (f, header)) return (FALSE);
-
-    /* check if this is the correct extension or not */
-    bzero (tname, 80);
-    gfits_scan (header, "EXTNAME", "%s", 1, tname);
-    if (!strcmp (tname, extname)) {
-      if (gfits_fread_ftable_data (f, table, FALSE)) return (TRUE);
-      gfits_free_header (header);
-      return (FALSE);
-    }
-
-    /* skip to next header */
-    Nbytes = gfits_data_size (header);
-    fseeko (f, Nbytes, SEEK_CUR);
-    gfits_free_header (header);
-  }
-  return (TRUE);
-}	
+  if (!gfits_find_Xheader (f, header, extname)) return FALSE;
+
+  if (gfits_fread_ftable_data (f, table, FALSE)) return (TRUE);
+  gfits_free_header (header);
+  return (FALSE);
+}
 
 /*********************** fits read ftable data ***********************************/
@@ -84,5 +64,5 @@
 
 /*********************** fits read ftable data ***********************************/
-int gfits_fread_ftable_range (FILE *f, FTable *table, off_t start, off_t Nrows) {
+int gfits_fread_ftable_range (FILE *f, int padIfShort, FTable *table, off_t start, off_t Nrows) {
 
   off_t Nbytes, Nread, Nskip, Nx, Ny;
@@ -109,9 +89,17 @@
     if (ferror (f)) {
       perror ("fits read error");
+      free (table[0].buffer);
+      return (FALSE);
+    } 
+    fprintf (stderr, "error: fits read error in %s, read "OFF_T_FMT", need "OFF_T_FMT"\n", __func__,  Nread,  Nbytes);
+    if (padIfShort) {
+      memset (&table[0].buffer[Nread], 0, Nbytes - Nread);
+      fprintf (stderr, "warning: file missing data, padding with zeros: USE AT YOUR OWN RISK!\n");
+      table[0].validsize = Nread;
+      table[0].datasize = Nbytes;
     } else {
-      fprintf (stderr, "unexpected eof\n");
-    }      
-    free (table[0].buffer);
-    return (FALSE);
+      free (table[0].buffer);
+      return (FALSE);
+    }
   }
 
Index: /trunk/Ohana/src/libfits/table/F_write_T.c
===================================================================
--- /trunk/Ohana/src/libfits/table/F_write_T.c	(revision 37806)
+++ /trunk/Ohana/src/libfits/table/F_write_T.c	(revision 37807)
@@ -95,5 +95,9 @@
 
   // cursor must be at start of the table (after table header)
-  fseeko (f, Nskip, SEEK_CUR);
+  if (fseeko (f, Nskip, SEEK_CUR)) {
+    fprintf (stderr, "can't seek table start\n");
+    return (FALSE);
+  }
+
   Nwrite = fwrite (ftable[0].buffer, sizeof (char), Nbytes, f);
   if (Nwrite != Nbytes) {
Index: /trunk/Ohana/src/libkapa/src/KapaWindow.c
===================================================================
--- /trunk/Ohana/src/libkapa/src/KapaWindow.c	(revision 37806)
+++ /trunk/Ohana/src/libkapa/src/KapaWindow.c	(revision 37807)
@@ -98,13 +98,6 @@
   graphdata[0].lweight = graphdata[0].size = 1.0;
     
-  graphdata[0].coords.pc1_1 = graphdata[0].coords.pc2_2 = 1.0;
-  graphdata[0].coords.pc1_2 = graphdata[0].coords.pc2_1 = 0.0;
-  strcpy (graphdata[0].coords.ctype, "DEC--LIN");
-  graphdata[0].coords.crval1 = 0.0;
-  graphdata[0].coords.crval2 = 0.0;
-  graphdata[0].coords.crpix1 = 0.0;
-  graphdata[0].coords.crpix2 = 0.0;
-  graphdata[0].coords.cdelt1 = graphdata[0].coords.cdelt2 = 1.0;
-  graphdata[0].flipeast = TRUE;
+  InitCoords (&graphdata[0].coords, "DEC--LIN");
+  graphdata[0].flipeast = TRUE; // default coords has EW flipped relative to sky
   graphdata[0].flipnorth = FALSE;
 
Index: /trunk/Ohana/src/libohana/Makefile
===================================================================
--- /trunk/Ohana/src/libohana/Makefile	(revision 37806)
+++ /trunk/Ohana/src/libohana/Makefile	(revision 37807)
@@ -35,4 +35,6 @@
 $(SRC)/time.$(ARCH).o		 \
 $(SRC)/gaussj.$(ARCH).o		 \
+$(SRC)/spherical.$(ARCH).o       \
+$(SRC)/vstats.$(ARCH).o		 \
 $(SRC)/config.$(ARCH).o		 \
 $(SRC)/Fread.$(ARCH).o		 \
@@ -45,4 +47,7 @@
 $(SRC)/gprint.$(ARCH).o	 \
 $(SRC)/version.$(ARCH).o
+
+# $(SRC)/legendre.$(ARCH).o	 \
+# $(SRC)/gsl_utils.$(ARCH).o	 \
 
 TYPETEST = \
Index: /trunk/Ohana/src/libohana/doc/gsl.txt
===================================================================
--- /trunk/Ohana/src/libohana/doc/gsl.txt	(revision 37807)
+++ /trunk/Ohana/src/libohana/doc/gsl.txt	(revision 37807)
@@ -0,0 +1,26 @@
+
+I have used and modified code from gnu scientific library in certain
+cases (libohana/src/legendre.c).  here is the corresponding liscense.
+My versions of the gsl code may be distributed under the GSL.
+
+/* specfunc/legendre_poly.c
+ * 
+ * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 Gerard Jungman
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/* Author:  G. Jungman */
+
Index: /trunk/Ohana/src/libohana/include/gsl_utils.h
===================================================================
--- /trunk/Ohana/src/libohana/include/gsl_utils.h	(revision 37807)
+++ /trunk/Ohana/src/libohana/include/gsl_utils.h	(revision 37807)
@@ -0,0 +1,26 @@
+# define GSL_DBL_EPSILON   2.2204460492503131e-16
+# define GSL_LOG_DBL_MIN (-7.0839641853226408e+02)
+
+# define GSL_ROOT6_DBL_EPSILON  2.4607833005759251e-03
+
+# define GSL_SF_GAMMA_XMAX  171.0
+
+#define GSL_SQRT_DBL_MIN   1.4916681462400413e-154
+#define GSL_LOG_DBL_EPSILON   (-3.6043653389117154e+01)
+#ifndef M_SQRT3
+#define M_SQRT3    1.73205080756887729352744634151      /* sqrt(3) */
+#endif
+#ifndef M_LNPI
+#define M_LNPI     1.14472988584940017414342735135      /* ln(pi) */
+#endif
+
+# define GSL_IS_ODD(n) ((n) & 1)
+
+typedef struct {
+  double *c;    /* coefficients                */
+  int order;    /* order of expansion          */
+  double a;     /* lower interval point        */
+  double b;     /* upper interval point        */
+  int order_sp; /* effective single precision order */
+} cheb_series;
+
Index: /trunk/Ohana/src/libohana/include/ohana.h
===================================================================
--- /trunk/Ohana/src/libohana/include/ohana.h	(revision 37806)
+++ /trunk/Ohana/src/libohana/include/ohana.h	(revision 37807)
@@ -242,4 +242,31 @@
 // sorting is now defined as a macro call
 # include <ohana_sort.h>
+
+// vector statistics options
+typedef enum {
+  VSTATS_NONE, 
+  VSTATS_MEAN, 
+  VSTATS_MEDIAN, 
+  VSTATS_WT_MEAN, 
+  VSTATS_INNER_MEAN, 
+  VSTATS_INNER_WTMEAN, 
+  VSTATS_CHI_INNER_MEAN, 
+  VSTATS_CHI_INNER_WTMEAN
+} VStatsMode;
+
+typedef struct {
+  double median;
+  double mean;
+  double sigma;
+  double error;
+  double chisq;
+  double min;
+  double max;
+  double Upper80;
+  double Lower20;
+  double total;
+  int    Nmeas;
+  VStatsMode statmode;
+} VStatsType;
 
 /* socket / pipe communication buffer */
@@ -314,4 +341,5 @@
 char   *ohana_version          PROTO((void));
 
+int     dgaussjordan_pivot     PROTO((double **A, double **B, int N, int M, double minPivot));
 int     dgaussjordan           PROTO((double **A, double **B, int N, int M));
 int     fgaussjordan           PROTO((float **A, float **B, int n, int m));
@@ -388,4 +416,8 @@
 int rconnect (char *command, char *hostname, char *shell, int *stdio, int *errorInfo, int doHandshake);
 
+int vstats_setmode (VStatsType *stats, char *mode);
+int vstats_getstats (double *value, double *dvalue, double *weight, int N, VStatsType *stats);
+int vstats_getstats_f (float *value, float *dvalue, float *weight, int N, VStatsType *stats);
+
 /*
 #   define F_SETFL         4   
@@ -404,3 +436,45 @@
 */
 
-# endif
+
+/** gsl-based functions for legendre and related polynomials **/
+
+double ohana_gsl_sf_log_1plusx (double x, double *err);
+double ohana_gsl_sf_lnpoch(const double a, const double x, double *err);
+double ohana_gsl_lnpoch_pos (const double a, const double x, double *err);
+double ohana_gsl_pochrel_smallx(const double a, const double x, double *err);
+
+double legendre_Pl (int l, double x, double *err);
+double legendre_Plm (int l, int m, double x, double *err);
+double legendre_Plm_sphere(int l, int m, double x, double *err);
+
+typedef struct {
+  double *Fr;
+  double *Fi;
+  int    *l;
+  int    *m;
+  int lmax;
+  int Nterms;
+} SHterms;
+
+SHterms *SHtermsInit (int lmax);
+void SHtermsFree (SHterms *terms);
+void SHtermsForRD (SHterms *terms, double R, double D);
+void SHtermsForLM (SHterms *terms, double R, double D, int l, int m);
+
+typedef struct {
+  double *dR_B;
+  double *dR_E;
+  double *dD_B;
+  double *dD_E;
+  int    *l;
+  int    *m;
+  int lmax;
+  int Nterms;
+} VSHterms;
+
+VSHterms *VSHtermsInit (int lmax);
+void VSHtermsFree (VSHterms *terms);
+void VSHtermsForRD (VSHterms *terms, double R, double D);
+void VSHtermsForLM (VSHterms *terms, double R, double D, int l, int m);
+
+# endif
Index: /trunk/Ohana/src/libohana/src/gaussj.c
===================================================================
--- /trunk/Ohana/src/libohana/src/gaussj.c	(revision 37806)
+++ /trunk/Ohana/src/libohana/src/gaussj.c	(revision 37807)
@@ -1,5 +1,6 @@
 # include <ohana.h>
 # define GROWTHTEST 0
-# define MAX_RANGE 1.0e9
+# define VERY_VERBOSE 0
+# define MAX_RANGE 1.0e16
 
 // Gauss-Jordan elimination using full pivots based on Press et al's description.  Substantially
@@ -8,8 +9,15 @@
 // than their implementation.  (largely based on version by William Kahan)
 
+int dgaussjordan (double **A, double **B, int N, int M) {
+
+# define myMIN_PIVOT 1.0e-7
+  int status = dgaussjordan_pivot (A, B, N, M, myMIN_PIVOT);
+  return status;
+}
+
 // MAX_RANGE is used to test for ill-conditioned input matrices.  For an ill-conditioned
 // matrix, one or more of the pivots trends towards zero.  Rather than allow this to go to the
 // numerical precision, I am raising an error if |growth| > 1e8
-int dgaussjordan (double **A, double **B, int N, int M) {
+int dgaussjordan_pivot (double **A, double **B, int N, int M, double MIN_PIVOT) {
 
   int *colIndex;
@@ -48,5 +56,8 @@
     // search for the next pivot
     for (row = 0; row < N; row++) {
-      if (!finite(A[row][diag])) goto escape;
+      if (!finite(A[row][diag])) { 
+	fprintf (stderr, "infinity\n"); 
+	goto escape; 
+      }
 
       // if we have already operated on this row (pivot[row] is true), skip it
@@ -65,16 +76,21 @@
 # if (GROWTHTEST)
     fprintf (stderr, "maxcol: %d\n", maxcol);
+# if (VERY_VERBOSE)
     fprintf (stderr, "full A matrix:\n");
     for (row = 0; row < N; row++) {
-	for (col = 0; col < N; col++) {
-	    fprintf (stderr, "%10.3e ", A[row][col]);
-	}
-	fprintf (stderr, "\n");
+      for (col = 0; col < N; col++) {
+    	fprintf (stderr, "%10.3e ", A[row][col]);
+      }
+      fprintf (stderr, "\n");
     }
     fprintf (stderr, "\n");
 # endif
+# endif
 
     // if pivot[maxcol] is set, we have already done this row: this implies a singular matrix
-    if (pivot[maxcol]) goto escape;
+    if (pivot[maxcol]) { 
+      fprintf (stderr, "singular\n"); 
+      goto escape; 
+    }
     pivot[maxcol] = TRUE;
 
@@ -86,9 +102,26 @@
     rowIndex[diag] = maxrow;
     colIndex[diag] = maxcol;
-    if (A[maxcol][maxcol] == 0.0) goto escape;
+    if (A[maxcol][maxcol] == 0.0) { 
+      fprintf (stderr, "zero pivot\n"); 
+      goto escape;
+    }
+
     // XXX Kahan replaces the 0.0 pivot with epsilon*(largest element in column) + underFlow
 
     /* rescale by pivot reciprocal */
     double tmpval = 1.0 / A[maxcol][maxcol];
+
+# if (0)
+    if (fabs(A[maxcol][maxcol]) < MIN_PIVOT) {
+      // we are ill-conditioned.  set this row & col to 0.0, 1.0 on pivot 
+      // fprintf (stderr, "WARNING: eliminating degenerate pivot %lf @ A[%d][%d]\n", A[maxcol][maxcol], maxcol, maxcol);
+      for (col = 0; col < N; col++) A[maxcol][col] = 0.0;
+      for (col = 0; col < M; col++) B[maxcol][col] = 0.0;
+      for (row = 0; row < N; row++) A[row][maxcol] = 0.0;
+      A[maxcol][maxcol] = 1.0;
+      continue;
+    }
+# endif
+
     // XXX why is this here (don't I double count this element?) A[maxcol][maxcol] = 1.0;
     A[maxcol][maxcol] = 1.0;
@@ -102,10 +135,15 @@
 #   if (GROWTHTEST)
     fprintf (stderr, "column: %d, maxval : %f, growth: %e, epsilon: %e\n", maxcol, tmpval, growth, epsilon);
+# if (VERY_VERBOSE)
     fprintf (stderr, "A diagonal: ");
     for (col = 0; col < N; col++) fprintf (stderr, "%f ", A[col][col]);
     fprintf (stderr, "\n");
 # endif
-
-    if (fabs(growth) > MAX_RANGE) goto escape;
+# endif
+
+    if (fabs(growth) > MAX_RANGE) { 
+      fprintf (stderr, "max range\n"); 
+      goto escape;
+    }
 
     /* adjust the elements above the pivot */
@@ -119,13 +157,13 @@
   }
 
-# if (GROWTHTEST)
-    fprintf (stderr, "final A matrix:\n");
-    for (row = 0; row < N; row++) {
-	for (col = 0; col < N; col++) {
-	    fprintf (stderr, "%10.3e ", A[row][col]);
-	}
-	fprintf (stderr, "\n");
+# if (GROWTHTEST && VERY_VERBOSE)
+  fprintf (stderr, "final A matrix:\n");
+  for (row = 0; row < N; row++) {
+    for (col = 0; col < N; col++) {
+      fprintf (stderr, "%10.3e ", A[row][col]);
     }
     fprintf (stderr, "\n");
+  }
+  fprintf (stderr, "\n");
 # endif
 
Index: /trunk/Ohana/src/libohana/src/glockfile.c
===================================================================
--- /trunk/Ohana/src/libohana/src/glockfile.c	(revision 37806)
+++ /trunk/Ohana/src/libohana/src/glockfile.c	(revision 37807)
@@ -288,5 +288,9 @@
 
     /* set value to IDLE */
-    fseeko (flock, 0LL, SEEK_SET);
+    if (fseeko (flock, 0LL, SEEK_SET)) {
+      *state = LCK_HARDCLOSE;
+      goto failure;
+    }
+
     nbytes = fprintf (flock, "IDLE\n");
     if (nbytes != 5) {
@@ -303,5 +307,8 @@
   
   /* now unlock the file */
-  fclose (f);
+  if (fclose (f)) {
+    *state = LCK_UNKNOWN;
+    goto failure;
+  }
 
   *state = LCK_UNLOCK;
Index: /trunk/Ohana/src/libohana/src/gsl_gamma.c
===================================================================
--- /trunk/Ohana/src/libohana/src/gsl_gamma.c	(revision 37807)
+++ /trunk/Ohana/src/libohana/src/gsl_gamma.c	(revision 37807)
@@ -0,0 +1,1660 @@
+# include <ohana.h>
+# include <gsl_utils.h>
+
+/* specfunc/gamma.c
+ * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
+ */
+
+#define LogRootTwoPi_  0.9189385332046727418
+
+/*-*-*-*-*-*-*-*-*-*-*-* Private Section *-*-*-*-*-*-*-*-*-*-*-*/
+
+static struct {int n; double f; long i; } fact_table[GSL_SF_FACT_NMAX + 1] = {
+    { 0,  1.0,     1L     },
+    { 1,  1.0,     1L     },
+    { 2,  2.0,     2L     },
+    { 3,  6.0,     6L     },
+    { 4,  24.0,    24L    },
+    { 5,  120.0,   120L   },
+    { 6,  720.0,   720L   },
+    { 7,  5040.0,  5040L  },
+    { 8,  40320.0, 40320L },
+
+    { 9,  362880.0,     362880L    },
+    { 10, 3628800.0,    3628800L   },
+    { 11, 39916800.0,   39916800L  },
+    { 12, 479001600.0,  479001600L },
+
+    { 13, 6227020800.0,                               0 },
+    { 14, 87178291200.0,                              0 },
+    { 15, 1307674368000.0,                            0 },
+    { 16, 20922789888000.0,                           0 },
+    { 17, 355687428096000.0,                          0 },
+    { 18, 6402373705728000.0,                         0 },
+    { 19, 121645100408832000.0,                       0 },
+    { 20, 2432902008176640000.0,                      0 },
+    { 21, 51090942171709440000.0,                     0 },
+    { 22, 1124000727777607680000.0,                   0 },
+    { 23, 25852016738884976640000.0,                  0 },
+    { 24, 620448401733239439360000.0,                 0 },
+    { 25, 15511210043330985984000000.0,               0 },
+    { 26, 403291461126605635584000000.0,              0 },
+    { 27, 10888869450418352160768000000.0,            0 },
+    { 28, 304888344611713860501504000000.0,           0 },
+    { 29, 8841761993739701954543616000000.0,          0 },
+    { 30, 265252859812191058636308480000000.0,        0 },
+    { 31, 8222838654177922817725562880000000.0,       0 },
+    { 32, 263130836933693530167218012160000000.0,     0 },
+    { 33, 8683317618811886495518194401280000000.0,    0 },
+    { 34, 2.95232799039604140847618609644e38,  0 },
+    { 35, 1.03331479663861449296666513375e40,  0 },
+    { 36, 3.71993326789901217467999448151e41,  0 },
+    { 37, 1.37637530912263450463159795816e43,  0 },
+    { 38, 5.23022617466601111760007224100e44,  0 },
+    { 39, 2.03978820811974433586402817399e46,  0 },
+    { 40, 8.15915283247897734345611269600e47,  0 },
+    { 41, 3.34525266131638071081700620534e49,  0 },
+    { 42, 1.40500611775287989854314260624e51,  0 },
+    { 43, 6.04152630633738356373551320685e52,  0 },
+    { 44, 2.65827157478844876804362581101e54,  0 },
+    { 45, 1.19622220865480194561963161496e56,  0 },
+    { 46, 5.50262215981208894985030542880e57,  0 },
+    { 47, 2.58623241511168180642964355154e59,  0 },
+    { 48, 1.24139155925360726708622890474e61,  0 },
+    { 49, 6.08281864034267560872252163321e62,  0 },
+    { 50, 3.04140932017133780436126081661e64,  0 },
+    { 51, 1.55111875328738228022424301647e66,  0 },
+    { 52, 8.06581751709438785716606368564e67,  0 },
+    { 53, 4.27488328406002556429801375339e69,  0 },
+    { 54, 2.30843697339241380472092742683e71,  0 },
+    { 55, 1.26964033536582759259651008476e73,  0 },
+    { 56, 7.10998587804863451854045647464e74,  0 },
+    { 57, 4.05269195048772167556806019054e76,  0 },
+    { 58, 2.35056133128287857182947491052e78,  0 },
+    { 59, 1.38683118545689835737939019720e80,  0 },
+    { 60, 8.32098711274139014427634118320e81,  0 },
+    { 61, 5.07580213877224798800856812177e83,  0 },
+    { 62, 3.14699732603879375256531223550e85,  0 },
+    { 63, 1.982608315404440064116146708360e87,  0 },
+    { 64, 1.268869321858841641034333893350e89,  0 },
+    { 65, 8.247650592082470666723170306800e90,  0 },
+    { 66, 5.443449390774430640037292402480e92,  0 },
+    { 67, 3.647111091818868528824985909660e94,  0 },
+    { 68, 2.480035542436830599600990418570e96,  0 },
+    { 69, 1.711224524281413113724683388810e98,  0 },
+    { 70, 1.197857166996989179607278372170e100,  0 },
+    { 71, 8.504785885678623175211676442400e101,  0 },
+    { 72, 6.123445837688608686152407038530e103,  0 },
+    { 73, 4.470115461512684340891257138130e105,  0 },
+    { 74, 3.307885441519386412259530282210e107,  0 },
+    { 75, 2.480914081139539809194647711660e109,  0 },
+    { 76, 1.885494701666050254987932260860e111,  0 },
+    { 77, 1.451830920282858696340707840860e113,  0 },
+    { 78, 1.132428117820629783145752115870e115,  0 },
+    { 79, 8.946182130782975286851441715400e116,  0 },
+    { 80, 7.156945704626380229481153372320e118,  0 },
+    { 81, 5.797126020747367985879734231580e120,  0 },
+    { 82, 4.753643337012841748421382069890e122,  0 },
+    { 83, 3.945523969720658651189747118010e124,  0 },
+    { 84, 3.314240134565353266999387579130e126,  0 },
+    { 85, 2.817104114380550276949479442260e128,  0 },
+    { 86, 2.422709538367273238176552320340e130,  0 },
+    { 87, 2.107757298379527717213600518700e132,  0 },
+    { 88, 1.854826422573984391147968456460e134,  0 },
+    { 89, 1.650795516090846108121691926250e136,  0 },
+    { 90, 1.485715964481761497309522733620e138,  0 },
+    { 91, 1.352001527678402962551665687590e140,  0 },
+    { 92, 1.243841405464130725547532432590e142,  0 },
+    { 93, 1.156772507081641574759205162310e144,  0 },
+    { 94, 1.087366156656743080273652852570e146,  0 },
+    { 95, 1.032997848823905926259970209940e148,  0 },
+    { 96, 9.916779348709496892095714015400e149,  0 },
+    { 97, 9.619275968248211985332842594960e151,  0 },
+    { 98, 9.426890448883247745626185743100e153,  0 },
+    { 99, 9.332621544394415268169923885600e155,  0 },
+    { 100, 9.33262154439441526816992388563e157,  0 },
+    { 101, 9.42594775983835942085162312450e159,  0 },
+    { 102, 9.61446671503512660926865558700e161,  0 },
+    { 103, 9.90290071648618040754671525458e163,  0 },
+    { 104, 1.02990167451456276238485838648e166,  0 },
+    { 105, 1.08139675824029090050410130580e168,  0 },
+    { 106, 1.146280563734708354534347384148e170,  0 },
+    { 107, 1.226520203196137939351751701040e172,  0 },
+    { 108, 1.324641819451828974499891837120e174,  0 },
+    { 109, 1.443859583202493582204882102460e176,  0 },
+    { 110, 1.588245541522742940425370312710e178,  0 },
+    { 111, 1.762952551090244663872161047110e180,  0 },
+    { 112, 1.974506857221074023536820372760e182,  0 },
+    { 113, 2.231192748659813646596607021220e184,  0 },
+    { 114, 2.543559733472187557120132004190e186,  0 },
+    { 115, 2.925093693493015690688151804820e188,  0 },
+    { 116, 3.393108684451898201198256093590e190,  0 },
+    { 117, 3.96993716080872089540195962950e192,  0 },
+    { 118, 4.68452584975429065657431236281e194,  0 },
+    { 119, 5.57458576120760588132343171174e196,  0 },
+    { 120, 6.68950291344912705758811805409e198,  0 },
+    { 121, 8.09429852527344373968162284545e200,  0 },
+    { 122, 9.87504420083360136241157987140e202,  0 },
+    { 123, 1.21463043670253296757662432419e205,  0 },
+    { 124, 1.50614174151114087979501416199e207,  0 },
+    { 125, 1.88267717688892609974376770249e209,  0 },
+    { 126, 2.37217324288004688567714730514e211,  0 },
+    { 127, 3.01266001845765954480997707753e213,  0 },
+    { 128, 3.85620482362580421735677065923e215,  0 },
+    { 129, 4.97450422247728744039023415041e217,  0 },
+    { 130, 6.46685548922047367250730439554e219,  0 },
+    { 131, 8.47158069087882051098456875820e221,  0 },
+    { 132, 1.11824865119600430744996307608e224,  0 },
+    { 133, 1.48727070609068572890845089118e226,  0 },
+    { 134, 1.99294274616151887673732419418e228,  0 },
+    { 135, 2.69047270731805048359538766215e230,  0 },
+    { 136, 3.65904288195254865768972722052e232,  0 },
+    { 137, 5.01288874827499166103492629211e234,  0 },
+    { 138, 6.91778647261948849222819828311e236,  0 },
+    { 139, 9.61572319694108900419719561353e238,  0 },
+    { 140, 1.34620124757175246058760738589e241,  0 },
+    { 141, 1.89814375907617096942852641411e243,  0 },
+    { 142, 2.69536413788816277658850750804e245,  0 },
+    { 143, 3.85437071718007277052156573649e247,  0 },
+    { 144, 5.55029383273930478955105466055e249,  0 },
+    { 145, 8.04792605747199194484902925780e251,  0 },
+    { 146, 1.17499720439091082394795827164e254,  0 },
+    { 147, 1.72724589045463891120349865931e256,  0 },
+    { 148, 2.55632391787286558858117801578e258,  0 },
+    { 149, 3.80892263763056972698595524351e260,  0 },
+    { 150, 5.71338395644585459047893286526e262,  0 },
+    { 151, 8.62720977423324043162318862650e264,  0 },
+    { 152, 1.31133588568345254560672467123e267,  0 },
+    { 153, 2.00634390509568239477828874699e269,  0 },
+    { 154, 3.08976961384735088795856467036e271,  0 },
+    { 155, 4.78914290146339387633577523906e273,  0 },
+    { 156, 7.47106292628289444708380937294e275,  0 },
+    { 157, 1.17295687942641442819215807155e278,  0 },
+    { 158, 1.85327186949373479654360975305e280,  0 },
+    { 159, 2.94670227249503832650433950735e282,  0 },
+    { 160, 4.71472363599206132240694321176e284,  0 },
+    { 161, 7.59070505394721872907517857094e286,  0 },
+    { 162, 1.22969421873944943411017892849e289,  0 },
+    { 163, 2.00440157654530257759959165344e291,  0 },
+    { 164, 3.28721858553429622726333031164e293,  0 },
+    { 165, 5.42391066613158877498449501421e295,  0 },
+    { 166, 9.00369170577843736647426172359e297,  0 },
+    { 167, 1.50361651486499904020120170784e300,  0 },
+    { 168, 2.52607574497319838753801886917e302,  0 },
+    { 169, 4.26906800900470527493925188890e304,  0 },
+    { 170, 7.25741561530799896739672821113e306,  0 },
+
+    /*
+    { 171, 1.24101807021766782342484052410e309,  0 },
+    { 172, 2.13455108077438865629072570146e311,  0 },
+    { 173, 3.69277336973969237538295546352e313,  0 },
+    { 174, 6.42542566334706473316634250653e315,  0 },
+    { 175, 1.12444949108573632830410993864e318,  0 },
+    { 176, 1.97903110431089593781523349201e320,  0 },
+    { 177, 3.50288505463028580993296328086e322,  0 },
+    { 178, 6.23513539724190874168067463993e324,  0 },
+    { 179, 1.11608923610630166476084076055e327,  0 },
+    { 180, 2.00896062499134299656951336898e329,  0 },
+    { 181, 3.63621873123433082379081919786e331,  0 },
+    { 182, 6.61791809084648209929929094011e333,  0 },
+    { 183, 1.21107901062490622417177024204e336,  0 },
+    { 184, 2.22838537954982745247605724535e338,  0 },
+    { 185, 4.12251295216718078708070590390e340,  0 },
+    { 186, 7.66787409103095626397011298130e342,  0 },
+    { 187, 1.43389245502278882136241112750e345,  0 },
+    { 188, 2.69571781544284298416133291969e347,  0 },
+    { 189, 5.09490667118697324006491921822e349,  0 },
+    { 190, 9.68032267525524915612334651460e351,  0 },
+    { 191, 1.84894163097375258881955918429e354,  0 },
+    { 192, 3.54996793146960497053355363384e356,  0 },
+    { 193, 6.85143810773633759312975851330e358,  0 },
+    { 194, 1.32917899290084949306717315158e361,  0 },
+    { 195, 2.59189903615665651148098764559e363,  0 },
+    { 196, 5.08012211086704676250273578535e365,  0 },
+    { 197, 1.00078405584080821221303894971e368,  0 },
+    { 198, 1.98155243056480026018181712043e370,  0 },
+    { 199, 3.94328933682395251776181606966e372,  0 },
+    { 200, 7.88657867364790503552363213932e374,  0 }
+    */
+};
+
+static struct {int n; double f; long i; } doub_fact_table[GSL_SF_DOUBLEFACT_NMAX + 1] = {
+  { 0,  1.000000000000000000000000000,    1L    },
+  { 1,  1.000000000000000000000000000,    1L    },
+  { 2,  2.000000000000000000000000000,    2L    },
+  { 3,  3.000000000000000000000000000,    3L    },
+  { 4,  8.000000000000000000000000000,    8L    },
+  { 5,  15.00000000000000000000000000,    15L   },
+  { 6,  48.00000000000000000000000000,    48L   },
+  { 7,  105.0000000000000000000000000,    105L  },
+  { 8,  384.0000000000000000000000000,    384L  },
+  { 9,  945.0000000000000000000000000,    945L  },
+  { 10, 3840.000000000000000000000000,    3840L   },
+  { 11, 10395.00000000000000000000000,    10395L  },
+  { 12, 46080.00000000000000000000000,       46080L       },
+  { 13, 135135.0000000000000000000000,       135135L      },
+  { 14, 645120.00000000000000000000000,      645120L      },
+  { 15, 2.02702500000000000000000000000e6,   2027025L     },
+  { 16, 1.03219200000000000000000000000e7,   10321920L    },
+  { 17, 3.4459425000000000000000000000e7,    34459425L    },
+  { 18, 1.85794560000000000000000000000e8,   185794560L   },
+  { 19, 6.5472907500000000000000000000e8,            0 },
+  { 20, 3.7158912000000000000000000000e9,            0 },
+  { 21, 1.37493105750000000000000000000e10,          0 },
+  { 22, 8.1749606400000000000000000000e10,           0 },
+  { 23, 3.1623414322500000000000000000e11,           0 },
+  { 24, 1.96199055360000000000000000000e12,          0 },
+  { 25, 7.9058535806250000000000000000e12,           0 },
+  { 26, 5.1011754393600000000000000000e13,           0 },
+  { 27, 2.13458046676875000000000000000e14,          0 },
+  { 28, 1.42832912302080000000000000000e15,          0 },
+  { 29, 6.1902833536293750000000000000e15,           0 },
+  { 30, 4.2849873690624000000000000000e16,           0 },
+  { 31, 1.91898783962510625000000000000e17,          0 },
+  { 32, 1.37119595809996800000000000000e18,          0 },
+  { 33, 6.3326598707628506250000000000e18,           0 },
+  { 34, 4.6620662575398912000000000000e19,           0 },
+  { 35, 2.21643095476699771875000000000e20,          0 },
+  { 36, 1.67834385271436083200000000000e21,          0 },
+  { 37, 8.2007945326378915593750000000e21,           0 },
+  { 38, 6.3777066403145711616000000000e22,           0 },
+  { 39, 3.1983098677287777081562500000e23,           0 },
+  { 40, 2.55108265612582846464000000000e24,          0 },
+  { 41, 1.31130704576879886034406250000e25,          0 },
+  { 42, 1.07145471557284795514880000000e26,          0 },
+  { 43, 5.6386202968058350994794687500e26,           0 },
+  { 44, 4.7144007485205310026547200000e27,           0 },
+  { 45, 2.53737913356262579476576093750e28,          0 },
+  { 46, 2.16862434431944426122117120000e29,          0 },
+  { 47, 1.19256819277443412353990764062e30,          0 },
+  { 48, 1.04093968527333324538616217600e31,          0 },
+  { 49, 5.8435841445947272053455474391e31,           0 },
+  { 50, 5.2046984263666662269308108800e32,           0 },
+  { 51, 2.98022791374331087472622919392e33,          0 },
+  { 52, 2.70644318171066643800402165760e34,          0 },
+  { 53, 1.57952079428395476360490147278e35,          0 },
+  { 54, 1.46147931812375987652217169510e36,          0 },
+  { 55, 8.6873643685617511998269581003e36,           0 },
+  { 56, 8.1842841814930553085241614926e37,           0 },
+  { 57, 4.9517976900801981839013661172e38,           0 },
+  { 58, 4.7468848252659720789440136657e39,           0 },
+  { 59, 2.92156063714731692850180600912e40,       0 },
+  { 60, 2.84813089515958324736640819942e41,       0 },
+  { 61, 1.78215198865986332638610166557e42,       0 },
+  { 62, 1.76584115499894161336717308364e43,       0 },
+  { 63, 1.12275575285571389562324404931e44,       0 },
+  { 64, 1.13013833919932263255499077353e45,       0 },
+  { 65, 7.2979123935621403215510863205e45,        0 },
+  { 66, 7.4589130387155293748629391053e46,        0 },
+  { 67, 4.8896013036866340154392278347e47,        0 },
+  { 68, 5.0720608663265599749067985916e48,        0 },
+  { 69, 3.3738248995437774706530672060e49,        0 },
+  { 70, 3.5504426064285919824347590141e50,        0 },
+  { 71, 2.39541567867608200416367771623e51,       0 },
+  { 72, 2.55631867662858622735302649017e52,       0 },
+  { 73, 1.74865344543353986303948473285e53,       0 },
+  { 74, 1.89167582070515380824123960272e54,       0 },
+  { 75, 1.31149008407515489727961354964e55,       0 },
+  { 76, 1.43767362373591689426334209807e56,       0 },
+  { 77, 1.00984736473786927090530243322e57,       0 },
+  { 78, 1.12138542651401517752540683649e58,       0 },
+  { 79, 7.9777941814291672401518892225e58,        0 },
+  { 80, 8.9710834121121214202032546920e59,        0 },
+  { 81, 6.4620132869576254645230302702e60,        0 },
+  { 82, 7.3562883979319395645666688474e61,        0 },
+  { 83, 5.3634710281748291355541151243e62,        0 },
+  { 84, 6.1792822542628292342360018318e63,        0 },
+  { 85, 4.5589503739486047652209978556e64,        0 },
+  { 86, 5.3141827386660331414429615754e65,        0 },
+  { 87, 3.9662868253352861457422681344e66,        0 },
+  { 88, 4.6764808100261091644698061863e67,        0 },
+  { 89, 3.5299952745484046697106186396e68,        0 },
+  { 90, 4.2088327290234982480228255677e69,        0 },
+  { 91, 3.2122956998390482494366629620e70,        0 },
+  { 92, 3.8721261107016183881809995223e71,        0 },
+  { 93, 2.98743500085031487197609655470e72,       0 },
+  { 94, 3.6397985440595212848901395509e73,        0 },
+  { 95, 2.83806325080779912837729172696e74,       0 },
+  { 96, 3.4942066022971404334945339689e75,        0 },
+  { 97, 2.75292135328356515452597297515e76,       0 },
+  { 98, 3.4243224702511976248246432895e77,        0 },
+  { 99, 2.72539213975072950298071324540e78,       0 },
+  { 100, 3.4243224702511976248246432895e79,       0 },
+  { 101, 2.75264606114823679801052037785e80,      0 },
+  { 102, 3.4928089196562215773211361553e81,       0 },
+  { 103, 2.83522544298268390195083598919e82,      0 },
+  { 104, 3.6325212764424704404139816015e83,       0 },
+  { 105, 2.97698671513181809704837778865e84,      0 },
+  { 106, 3.8504725530290186668388204976e85,       0 },
+  { 107, 3.1853757851910453638417642339e86,       0 },
+  { 108, 4.1585103572713401601859261374e87,       0 },
+  { 109, 3.4720596058582394465875230149e88,       0 },
+  { 110, 4.5743613929984741762045187512e89,       0 },
+  { 111, 3.8539861625026457857121505465e90,       0 },
+  { 112, 5.1232847601582910773490610013e91,       0 },
+  { 113, 4.3550043636279897378547301176e92,       0 },
+  { 114, 5.8405446265804518281779295415e93,       0 },
+  { 115, 5.0082550181721881985329396352e94,       0 },
+  { 116, 6.7750317668333241206863982681e95,       0 },
+  { 117, 5.8596583712614601922835393732e96,       0 },
+  { 118, 7.9945374848633224624099499564e97,       0 },
+  { 119, 6.9729934618011376288174118541e98,       0 },
+  { 120, 9.5934449818359869548919399477e99,       0 },
+  { 121, 8.4373220887793765308690683435e100,      0 },
+  { 122, 1.17040028778399040849681667362e102,       0 },
+  { 123, 1.03779061691986331329689540625e103,       0 },
+  { 124, 1.45129635685214810653605267528e104,       0 },
+  { 125, 1.29723827114982914162111925781e105,       0 },
+  { 126, 1.82863340963370661423542637086e106,       0 },
+  { 127, 1.64749260436028300985882145742e107,       0 },
+  { 128, 2.34065076433114446622134575470e108,       0 },
+  { 129, 2.12526545962476508271787968008e109,       0 },
+  { 130, 3.04284599363048780608774948111e110,       0 },
+  { 131, 2.78409775210844225836042238090e111,       0 },
+  { 132, 4.0165567115922439040358293151e112,        0 },
+  { 133, 3.7028500103042282036193617666e113,        0 },
+  { 134, 5.3821859935336068314080112822e114,        0 },
+  { 135, 4.9988475139107080748861383849e115,        0 },
+  { 136, 7.3197729512057052907148953438e116,        0 },
+  { 137, 6.8484210940576700625940095873e117,        0 },
+  { 138, 1.01012866726638733011865555744e119,       0 },
+  { 139, 9.5193053207401613870056733264e119,        0 },
+  { 140, 1.41418013417294226216611778042e121,       0 },
+  { 141, 1.34222205022436275556779993902e122,       0 },
+  { 142, 2.00813579052557801227588724819e123,       0 },
+  { 143, 1.91937753182083874046195391280e124,       0 },
+  { 144, 2.89171553835683233767727763739e125,       0 },
+  { 145, 2.78309742114021617366983317355e126,       0 },
+  { 146, 4.2219046860009752130088253506e127,        0 },
+  { 147, 4.0911532090761177752946547651e128,        0 },
+  { 148, 6.2484189352814433152530615189e129,        0 },
+  { 149, 6.0958182815234154851890356000e130,        0 },
+  { 150, 9.3726284029221649728795922783e131,        0 },
+  { 151, 9.2046856051003573826354437561e132,        0 },
+  { 152, 1.42463951724416907587769802630e134,       0 },
+  { 153, 1.40831689758035467954322289468e135,       0 },
+  { 154, 2.19394485655602037685165496051e136,       0 },
+  { 155, 2.18289119124954975329199548675e137,       0 },
+  { 156, 3.4225539762273917878885817384e138,        0 },
+  { 157, 3.4271391702617931126684329142e139,        0 },
+  { 158, 5.4076352824392790248639591467e140,        0 },
+  { 159, 5.4491512807162510491428083336e141,        0 },
+  { 160, 8.6522164519028464397823346347e142,        0 },
+  { 161, 8.7731335619531641891199214170e143,        0 },
+  { 162, 1.40165906520826112324473821082e145,       0 },
+  { 163, 1.43002077059836576282654719098e146,       0 },
+  { 164, 2.29872086694154824212137066574e147,       0 },
+  { 165, 2.35953427148730350866380286512e148,       0 },
+  { 166, 3.8158766391229700819214753051e149,        0 },
+  { 167, 3.9404222333837968594685507847e150,        0 },
+  { 168, 6.4106727537265897376280785126e151,        0 },
+  { 169, 6.6593135744186166925018508262e152,        0 },
+  { 170, 1.08981436813352025539677334714e154,       0 },
+  { 171, 1.13874262122558345441781649128e155,       0 },
+  { 172, 1.87448071318965483928245015709e156,       0 },
+  { 173, 1.97002473472025937614282252992e157,       0 },
+  { 174, 3.2615964409499994203514632733e158,        0 },
+  { 175, 3.4475432857604539082499394274e159,        0 },
+  { 176, 5.7404097360719989798185753611e160,        0 },
+  { 177, 6.1021516157960034176023927864e161,        0 },
+  { 178, 1.02179293302081581840770641427e163,       0 },
+  { 179, 1.09228513922748461175082830877e164,       0 },
+  { 180, 1.83922727943746847313387154568e165,       0 },
+  { 181, 1.97703610200174714726899923887e166,       0 },
+  { 182, 3.3473936485761926211036462131e167,        0 },
+  { 183, 3.6179760666631972795022686071e168,        0 },
+  { 184, 6.1592043133801944228307090322e169,        0 },
+  { 185, 6.6932557233269149670791969232e170,        0 },
+  { 186, 1.14561200228871616264651187999e172,       0 },
+  { 187, 1.25163882026213309884380982464e173,       0 },
+  { 188, 2.15375056430278638577544233437e174,       0 },
+  { 189, 2.36559737029543155681480056857e175,       0 },
+  { 190, 4.0921260721752941329733404353e176,        0 },
+  { 191, 4.5182909772642742735162690860e177,        0 },
+  { 192, 7.8568820585765647353088136358e178,        0 },
+  { 193, 8.7203015861200493478863993359e179,        0 },
+  { 194, 1.52423511936385355864990984535e181,       0 },
+  { 195, 1.70045880929340962283784787050e182,       0 },
+  { 196, 2.98750083395315297495382329688e183,       0 },
+  { 197, 3.3499038543080169569905603049e184,        0 },
+  { 198, 5.9152516512272428904085701278e185,        0 },
+  { 199, 6.6663086700729537444112150067e186,        0 },
+  { 200, 1.18305033024544857808171402556e188,       0 },
+  { 201, 1.33992804268466370262665421635e189,       0 },
+  { 202, 2.38976166709580612772506233164e190,       0 },
+  { 203, 2.72005392664986731633210805920e191,       0 },
+  { 204, 4.8751138008754445005591271565e192,        0 },
+  { 205, 5.5761105496322279984808215214e193,        0 },
+  { 206, 1.00427344298034156711518019425e195,       0 },
+  { 207, 1.15425488377387119568553005492e196,       0 },
+  { 208, 2.08888876139911045959957480403e197,       0 },
+  { 209, 2.41239270708739079898275781478e198,       0 },
+  { 210, 4.3866663989381319651591070885e199,        0 },
+  { 211, 5.0901486119543945858536189892e200,        0 },
+  { 212, 9.2997327657488397661373070276e201,        0 },
+  { 213, 1.08420165434628604678682084470e203,       0 },
+  { 214, 1.99014281187025170995338370390e204,       0 },
+  { 215, 2.33103355684451500059166481610e205,       0 },
+  { 216, 4.2987084736397436934993088004e206,        0 },
+  { 217, 5.0583428183525975512839126509e207,        0 },
+  { 218, 9.3711844725346412518284931849e208,        0 },
+  { 219, 1.10777707721921886373117687056e210,       0 },
+  { 220, 2.06166058395762107540226850068e211,       0 },
+  { 221, 2.44818734065447368884590088393e212,       0 },
+  { 222, 4.5768864963859187873930360715e213,        0 },
+  { 223, 5.4594577696594763261263589712e214,        0 },
+  { 224, 1.02522257519044580837604008002e216,       0 },
+  { 225, 1.22837799817338217337843076851e217,       0 },
+  { 226, 2.31700301993040752692985058084e218,       0 },
+  { 227, 2.78841805585357753356903784452e219,       0 },
+  { 228, 5.2827668854413291614000593243e220,        0 },
+  { 229, 6.3854773479046925518730966640e221,        0 },
+  { 230, 1.21503638365150570712201364459e223,       0 },
+  { 231, 1.47504526736598397948268532937e224,       0 },
+  { 232, 2.81888441007149324052307165546e225,       0 },
+  { 233, 3.4368554729627426721946568174e226,        0 },
+  { 234, 6.5961895195672941828239876738e227,        0 },
+  { 235, 8.0766103614624452796574435210e228,        0 },
+  { 236, 1.55670072661788142714646109101e230,       0 },
+  { 237, 1.91415665566659953127881411447e231,       0 },
+  { 238, 3.7049477293505577966085773966e232,        0 },
+  { 239, 4.5748344070431728797563657336e233,        0 },
+  { 240, 8.8918745504413387118605857518e234,        0 },
+  { 241, 1.10253509209740466402128414180e236,       0 },
+  { 242, 2.15183364120680396827026175195e237,       0 },
+  { 243, 2.67916027379669333357172046456e238,       0 },
+  { 244, 5.2504740845446016825794386748e239,        0 },
+  { 245, 6.5639426708018986672507151382e240,        0 },
+  { 246, 1.29161662479797201391454191399e242,       0 },
+  { 247, 1.62129383968806897081092663913e243,       0 },
+  { 248, 3.2032092294989705945080639467e244,        0 },
+  { 249, 4.0370216608232917373192073314e245,        0 },
+  { 250, 8.0080230737474264862701598667e246,        0 },
+  { 251, 1.01329243686664622606712104019e248,       0 },
+  { 252, 2.01802181458435147454008028642e249,       0 },
+  { 253, 2.56362986527261495194981623168e250,       0 },
+  { 254, 5.1257754090442527453318039275e251,        0 },
+  { 255, 6.5372561564451681274720313908e252,        0 },
+  { 256, 1.31219850471532870280494180544e254,       0 },
+  { 257, 1.68007483220640820876031206743e255,       0 },
+  { 258, 3.3854721421655480532367498580e256,        0 },
+  { 259, 4.3513938154145972606892082546e257,        0 },
+  { 260, 8.8022275696304249384155496309e258,        0 },
+  { 261, 1.13571378582320988503988335446e260,       0 },
+  { 262, 2.30618362324317133386487400329e261,       0 },
+  { 263, 2.98692725671504199765489322224e262,       0 },
+  { 264, 6.0883247653619723214032673687e263,        0 },
+  { 265, 7.9153572302948612937854670389e264,        0 },
+  { 266, 1.61949438758628463749326912007e266,       0 },
+  { 267, 2.11340038048872796544071969939e267,       0 },
+  { 268, 4.3402449587312428284819612418e268,        0 },
+  { 269, 5.6850470235146782270355359914e269,        0 },
+  { 270, 1.17186613885743556369012953528e271,       0 },
+  { 271, 1.54064774337247779952663025366e272,       0 },
+  { 272, 3.1874758976922247332371523360e273,        0 },
+  { 273, 4.2059683394068643927077005925e274,        0 },
+  { 274, 8.7336839596766957690697974006e275,        0 },
+  { 275, 1.15664129333688770799461766294e277,       0 },
+  { 276, 2.41049677287076803226326408256e278,       0 },
+  { 277, 3.2038963825431789511450909263e279,        0 },
+  { 278, 6.7011810285807351296918741495e280,        0 },
+  { 279, 8.9388709072954692736948036845e281,        0 },
+  { 280, 1.87633068800260583631372476186e283,       0 },
+  { 281, 2.51182272495002686590823983534e284,       0 },
+  { 282, 5.2912525401673484584047038284e285,        0 },
+  { 283, 7.1084583116085760305203187340e286,        0 },
+  { 284, 1.50271572140752696218693588728e288,       0 },
+  { 285, 2.02591061880844416869829083919e289,       0 },
+  { 286, 4.2977669632255271118546366376e290,        0 },
+  { 287, 5.8143634759802347641640947085e291,        0 },
+  { 288, 1.23775688540895180821413535163e293,       0 },
+  { 289, 1.68035104455828784684342337075e294,       0 },
+  { 290, 3.5894949676859602438209925197e295,        0 },
+  { 291, 4.8898215396646176343143620089e296,        0 },
+  { 292, 1.04813253056430039119572981576e298,       0 },
+  { 293, 1.43271771112173296685410806860e299,       0 },
+  { 294, 3.08150963985904315011544565835e300,       0 },
+  { 295, 4.2265172478091122522196188024e301,        0 },
+  { 296, 9.1212685339827677243417191487e302,        0 },
+  { 297, 1.25527562259930633890922678431e304,       0 },
+  /*
+  { 298, 2.71813802312686478185383230631e305,       0 },
+  { 299, 3.7532741115719259533385880851e306,        0 },
+  { 300, 8.1544140693805943455614969189e307,  }
+  */
+};
+
+
+/* Chebyshev coefficients for Gamma*(3/4(t+1)+1/2), -1<t<1
+ */
+static double gstar_a_data[30] = {
+  2.16786447866463034423060819465,
+ -0.05533249018745584258035832802,
+  0.01800392431460719960888319748,
+ -0.00580919269468937714480019814,
+  0.00186523689488400339978881560,
+ -0.00059746524113955531852595159,
+  0.00019125169907783353925426722,
+ -0.00006124996546944685735909697,
+  0.00001963889633130842586440945,
+ -6.3067741254637180272515795142e-06,
+  2.0288698405861392526872789863e-06,
+ -6.5384896660838465981983750582e-07,
+  2.1108698058908865476480734911e-07,
+ -6.8260714912274941677892994580e-08,
+  2.2108560875880560555583978510e-08,
+ -7.1710331930255456643627187187e-09,
+  2.3290892983985406754602564745e-09,
+ -7.5740371598505586754890405359e-10,
+  2.4658267222594334398525312084e-10,
+ -8.0362243171659883803428749516e-11,
+  2.6215616826341594653521346229e-11,
+ -8.5596155025948750540420068109e-12,
+  2.7970831499487963614315315444e-12,
+ -9.1471771211886202805502562414e-13,
+  2.9934720198063397094916415927e-13,
+ -9.8026575909753445931073620469e-14,
+  3.2116773667767153777571410671e-14,
+ -1.0518035333878147029650507254e-14,
+  3.4144405720185253938994854173e-15,
+ -1.0115153943081187052322643819e-15
+};
+static cheb_series gstar_a_cs = {
+  gstar_a_data,
+  29,
+  -1, 1,
+  17
+};
+
+
+/* Chebyshev coefficients for
+ * x^2(Gamma*(x) - 1 - 1/(12x)), x = 4(t+1)+2, -1 < t < 1
+ */
+static double gstar_b_data[] = {
+  0.0057502277273114339831606096782,
+  0.0004496689534965685038254147807,
+ -0.0001672763153188717308905047405,
+  0.0000615137014913154794776670946,
+ -0.0000223726551711525016380862195,
+  8.0507405356647954540694800545e-06,
+ -2.8671077107583395569766746448e-06,
+  1.0106727053742747568362254106e-06,
+ -3.5265558477595061262310873482e-07,
+  1.2179216046419401193247254591e-07,
+ -4.1619640180795366971160162267e-08,
+  1.4066283500795206892487241294e-08,
+ -4.6982570380537099016106141654e-09,
+  1.5491248664620612686423108936e-09,
+ -5.0340936319394885789686867772e-10,
+  1.6084448673736032249959475006e-10,
+ -5.0349733196835456497619787559e-11,
+  1.5357154939762136997591808461e-11,
+ -4.5233809655775649997667176224e-12,
+  1.2664429179254447281068538964e-12,
+ -3.2648287937449326771785041692e-13,
+  7.1528272726086133795579071407e-14,
+ -9.4831735252566034505739531258e-15,
+ -2.3124001991413207293120906691e-15,
+  2.8406613277170391482590129474e-15,
+ -1.7245370321618816421281770927e-15,
+  8.6507923128671112154695006592e-16,
+ -3.9506563665427555895391869919e-16,
+  1.6779342132074761078792361165e-16,
+ -6.0483153034414765129837716260e-17
+};
+static cheb_series gstar_b_cs = {
+  gstar_b_data,
+  29,
+  -1, 1,
+  18
+};
+
+
+/* coefficients for gamma=7, kmax=8  Lanczos method */
+static double lanczos_7_c[9] = {
+  0.99999999999980993227684700473478,
+  676.520368121885098567009190444019,
+ -1259.13921672240287047156078755283,
+  771.3234287776530788486528258894,
+ -176.61502916214059906584551354,
+  12.507343278686904814458936853,
+ -0.13857109526572011689554707,
+  9.984369578019570859563e-6,
+  1.50563273514931155834e-7
+};
+
+# if (0)
+/* complex version of Lanczos method; this is not safe for export
+ * since it becomes bad in the left half-plane
+ */
+static
+int
+lngamma_lanczos_complex(double zr, double zi, gsl_sf_result * yr, gsl_sf_result * yi)
+{
+  int k;
+  gsl_sf_result log1_r,    log1_i;
+  gsl_sf_result logAg_r,   logAg_i;
+  double Ag_r, Ag_i;
+  double yi_tmp_val, yi_tmp_err;
+
+  zr -= 1.0; /* Lanczos writes z! instead of Gamma(z) */
+
+  Ag_r = lanczos_7_c[0];
+  Ag_i = 0.0;
+  for(k=1; k<=8; k++) {
+    double R = zr + k;
+    double I = zi;
+    double a = lanczos_7_c[k] / (R*R + I*I);
+    Ag_r +=  a * R;
+    Ag_i -=  a * I;
+  }
+
+  gsl_sf_complex_log_e(zr + 7.5, zi, &log1_r,  &log1_i);
+  gsl_sf_complex_log_e(Ag_r, Ag_i,   &logAg_r, &logAg_i);
+
+  /* (z+0.5)*log(z+7.5) - (z+7.5) + LogRootTwoPi_ + log(Ag(z)) */
+  yr->val = (zr+0.5)*log1_r.val - zi*log1_i.val - (zr+7.5) + LogRootTwoPi_ + logAg_r.val;
+  yi->val = zi*log1_r.val + (zr+0.5)*log1_i.val - zi + logAg_i.val;
+  yr->err = 4.0 * GSL_DBL_EPSILON * fabs(yr->val);
+  yi->err = 4.0 * GSL_DBL_EPSILON * fabs(yi->val);
+  yi_tmp_val = yi->val;
+  yi_tmp_err = yi->err;
+  gsl_sf_angle_restrict_symm_err_e(yi_tmp_val, yi);
+  yi->err += yi_tmp_err;
+  return GSL_SUCCESS;
+}
+# endif
+
+// NEED THIS
+/* Lanczos method for real x > 0;
+ * gamma=7, truncated at 1/(z+8) 
+ * [J. SIAM Numer. Anal, Ser. B, 1 (1964) 86]
+ */
+double lngamma_lanczos(double x, double *err) {
+  int k;
+  double Ag;
+  double term1, term2;
+
+  x -= 1.0; /* Lanczos writes z! instead of Gamma(z) */
+
+  Ag = lanczos_7_c[0];
+  for (k=1; k<=8; k++) { Ag += lanczos_7_c[k]/(x+k); }
+
+  /* (x+0.5)*log(x+7.5) - (x+7.5) + LogRootTwoPi_ + log(Ag(x)) */
+  term1 = (x+0.5)*log((x+7.5)/M_E);
+  term2 = LogRootTwoPi_ + log(Ag);
+  double value = term1 + (term2 - 7.0);
+  if (err) {
+    result->err  = 2.0 * GSL_DBL_EPSILON * (fabs(term1) + fabs(term2) + 7.0);
+    result->err += GSL_DBL_EPSILON * fabs(result->val);
+  }
+
+  return GSL_SUCCESS;
+}
+
+// NEED THIS
+/* x = eps near zero
+ * gives double-precision for |eps| < 0.02
+ */
+static
+int
+lngamma_sgn_0(double eps, gsl_sf_result * lng, double * sgn)
+{
+  /* calculate series for g(eps) = Gamma(eps) eps - 1/(1+eps) - eps/2 */
+  const double c1  = -0.07721566490153286061;
+  const double c2  = -0.01094400467202744461;
+  const double c3  =  0.09252092391911371098;
+  const double c4  = -0.01827191316559981266;
+  const double c5  =  0.01800493109685479790;
+  const double c6  = -0.00685088537872380685;
+  const double c7  =  0.00399823955756846603;
+  const double c8  = -0.00189430621687107802;
+  const double c9  =  0.00097473237804513221;
+  const double c10 = -0.00048434392722255893;
+  const double g6  = c6+eps*(c7+eps*(c8 + eps*(c9 + eps*c10)));
+  const double g   = eps*(c1+eps*(c2+eps*(c3+eps*(c4+eps*(c5+eps*g6)))));
+
+  /* calculate Gamma(eps) eps, a positive quantity */
+  const double gee = g + 1.0/(1.0+eps) + 0.5*eps;
+
+  lng->val = log(gee/fabs(eps));
+  lng->err = 4.0 * GSL_DBL_EPSILON * fabs(lng->val);
+  *sgn = GSL_SIGN(eps);
+
+  return GSL_SUCCESS;
+}
+
+// NEED THIS
+/* x near a negative integer
+ * Calculates sign as well as log(|gamma(x)|).
+ * x = -N + eps
+ * assumes N >= 1
+ */
+static
+int
+lngamma_sgn_sing(int N, double eps, gsl_sf_result * lng, double * sgn)
+{
+  if(eps == 0.0) {
+    lng->val = 0.0;
+    lng->err = 0.0;
+    *sgn = 0.0;
+    GSL_ERROR ("error", GSL_EDOM);
+  }
+  else if(N == 1) {
+    /* calculate series for
+     * g = eps gamma(-1+eps) + 1 + eps/2 (1+3eps)/(1-eps^2)
+     * double-precision for |eps| < 0.02
+     */
+    const double c0 =  0.07721566490153286061;
+    const double c1 =  0.08815966957356030521;
+    const double c2 = -0.00436125434555340577;
+    const double c3 =  0.01391065882004640689;
+    const double c4 = -0.00409427227680839100;
+    const double c5 =  0.00275661310191541584;
+    const double c6 = -0.00124162645565305019;
+    const double c7 =  0.00065267976121802783;
+    const double c8 = -0.00032205261682710437;
+    const double c9 =  0.00016229131039545456;
+    const double g5 = c5 + eps*(c6 + eps*(c7 + eps*(c8 + eps*c9)));
+    const double g  = eps*(c0 + eps*(c1 + eps*(c2 + eps*(c3 + eps*(c4 + eps*g5)))));
+
+    /* calculate eps gamma(-1+eps), a negative quantity */
+    const double gam_e = g - 1.0 - 0.5*eps*(1.0+3.0*eps)/(1.0 - eps*eps);
+
+    lng->val = log(fabs(gam_e)/fabs(eps));
+    lng->err = 2.0 * GSL_DBL_EPSILON * fabs(lng->val);
+    *sgn = ( eps > 0.0 ? -1.0 : 1.0 );
+    return GSL_SUCCESS;
+  }
+  else {
+    double g;
+
+    /* series for sin(Pi(N+1-eps))/(Pi eps) modulo the sign
+     * double-precision for |eps| < 0.02
+     */
+    const double cs1 = -1.6449340668482264365;
+    const double cs2 =  0.8117424252833536436;
+    const double cs3 = -0.1907518241220842137;
+    const double cs4 =  0.0261478478176548005;
+    const double cs5 = -0.0023460810354558236;
+    const double e2  = eps*eps;
+    const double sin_ser = 1.0 + e2*(cs1+e2*(cs2+e2*(cs3+e2*(cs4+e2*cs5))));
+
+    /* calculate series for ln(gamma(1+N-eps))
+     * double-precision for |eps| < 0.02
+     */
+    double aeps = fabs(eps);
+    double c1, c2, c3, c4, c5, c6, c7;
+    double lng_ser;
+    gsl_sf_result c0;
+    gsl_sf_result psi_0;
+    gsl_sf_result psi_1;
+    gsl_sf_result psi_2;
+    gsl_sf_result psi_3;
+    gsl_sf_result psi_4;
+    gsl_sf_result psi_5;
+    gsl_sf_result psi_6;
+    psi_2.val = 0.0;
+    psi_3.val = 0.0;
+    psi_4.val = 0.0;
+    psi_5.val = 0.0;
+    psi_6.val = 0.0;
+    gsl_sf_lnfact_e(N, &c0);
+    gsl_sf_psi_int_e(N+1, &psi_0);
+    gsl_sf_psi_1_int_e(N+1, &psi_1);
+    if(aeps > 0.00001) gsl_sf_psi_n_e(2, N+1.0, &psi_2);
+    if(aeps > 0.0002)  gsl_sf_psi_n_e(3, N+1.0, &psi_3);
+    if(aeps > 0.001)   gsl_sf_psi_n_e(4, N+1.0, &psi_4);
+    if(aeps > 0.005)   gsl_sf_psi_n_e(5, N+1.0, &psi_5);
+    if(aeps > 0.01)    gsl_sf_psi_n_e(6, N+1.0, &psi_6);
+    c1 = psi_0.val;
+    c2 = psi_1.val/2.0;
+    c3 = psi_2.val/6.0;
+    c4 = psi_3.val/24.0;
+    c5 = psi_4.val/120.0;
+    c6 = psi_5.val/720.0;
+    c7 = psi_6.val/5040.0;
+    lng_ser = c0.val-eps*(c1-eps*(c2-eps*(c3-eps*(c4-eps*(c5-eps*(c6-eps*c7))))));
+
+    /* calculate
+     * g = ln(|eps gamma(-N+eps)|)
+     *   = -ln(gamma(1+N-eps)) + ln(|eps Pi/sin(Pi(N+1+eps))|)
+     */
+    g = -lng_ser - log(sin_ser);
+
+    lng->val = g - log(fabs(eps));
+    lng->err = c0.err + 2.0 * GSL_DBL_EPSILON * (fabs(g) + fabs(lng->val));
+
+    *sgn = ( GSL_IS_ODD(N) ? -1.0 : 1.0 ) * ( eps > 0.0 ? 1.0 : -1.0 );
+
+    return GSL_SUCCESS;
+  }
+}
+
+
+/* This gets bad near the negative half axis. However, this
+ * region can be avoided by use of the reflection formula, as usual.
+ * Only the first two terms of the series are kept.
+ */
+#if 0
+static
+int
+lngamma_complex_stirling(const double zr, const double zi, double * lg_r, double * arg)
+{
+  double re_zinv,  im_zinv;
+  double re_zinv2, im_zinv2;
+  double re_zinv3, im_zinv3;
+  double re_zhlnz, im_zhlnz;
+  double r, lnr, theta;
+  gsl_sf_complex_log_e(zr, zi, &lnr, &theta);  /* z = r e^{i theta} */
+  r = exp(lnr);
+  re_zinv =  (zr/r)/r;
+  im_zinv = -(zi/r)/r;
+  re_zinv2 = re_zinv*re_zinv - im_zinv*im_zinv;
+  re_zinv2 = 2.0*re_zinv*im_zinv;
+  re_zinv3 = re_zinv2*re_zinv - im_zinv2*im_zinv;
+  re_zinv3 = re_zinv2*im_zinv + im_zinv2*re_zinv;
+  re_zhlnz = (zr - 0.5)*lnr - zi*theta;
+  im_zhlnz = zi*lnr + zr*theta;
+  *lg_r = re_zhlnz - zr + 0.5*(M_LN2+M_LNPI) + re_zinv/12.0 - re_zinv3/360.0;
+  *arg  = im_zhlnz - zi + 1.0/12.0*im_zinv - im_zinv3/360.0;
+  return GSL_SUCCESS;
+}
+#endif /* 0 */
+
+// NEED THIS
+inline
+static
+int
+lngamma_1_pade(const double eps, gsl_sf_result * result)
+{
+  /* Use (2,2) Pade for Log[Gamma[1+eps]]/eps
+   * plus a correction series.
+   */
+  const double n1 = -1.0017419282349508699871138440;
+  const double n2 =  1.7364839209922879823280541733;
+  const double d1 =  1.2433006018858751556055436011;
+  const double d2 =  5.0456274100274010152489597514;
+  const double num = (eps + n1) * (eps + n2);
+  const double den = (eps + d1) * (eps + d2);
+  const double pade = 2.0816265188662692474880210318 * num / den;
+  const double c0 =  0.004785324257581753;
+  const double c1 = -0.01192457083645441;
+  const double c2 =  0.01931961413960498;
+  const double c3 = -0.02594027398725020;
+  const double c4 =  0.03141928755021455;
+  const double eps5 = eps*eps*eps*eps*eps;
+  const double corr = eps5 * (c0 + eps*(c1 + eps*(c2 + eps*(c3 + c4*eps))));
+  result->val = eps * (pade + corr);
+  result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+  return GSL_SUCCESS;
+}
+
+// NEED THIS
+inline
+static
+int
+lngamma_2_pade(const double eps, gsl_sf_result * result)
+{
+  /* Use (2,2) Pade for Log[Gamma[2+eps]]/eps
+   * plus a correction series.
+   */
+  const double n1 = 1.000895834786669227164446568;
+  const double n2 = 4.209376735287755081642901277;
+  const double d1 = 2.618851904903217274682578255;
+  const double d2 = 10.85766559900983515322922936;
+  const double num = (eps + n1) * (eps + n2);
+  const double den = (eps + d1) * (eps + d2);
+  const double pade = 2.85337998765781918463568869 * num/den;
+  const double c0 =  0.0001139406357036744;
+  const double c1 = -0.0001365435269792533;
+  const double c2 =  0.0001067287169183665;
+  const double c3 = -0.0000693271800931282;
+  const double c4 =  0.0000407220927867950;
+  const double eps5 = eps*eps*eps*eps*eps;
+  const double corr = eps5 * (c0 + eps*(c1 + eps*(c2 + eps*(c3 + c4*eps))));
+  result->val = eps * (pade + corr);
+  result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+  return GSL_SUCCESS;
+}
+
+
+/* series for gammastar(x)
+ * double-precision for x > 10.0
+ */
+static
+int
+gammastar_ser(const double x, gsl_sf_result * result)
+{
+  /* Use the Stirling series for the correction to Log(Gamma(x)),
+   * which is better behaved and easier to compute than the
+   * regular Stirling series for Gamma(x). 
+   */
+  const double y = 1.0/(x*x);
+  const double c0 =  1.0/12.0;
+  const double c1 = -1.0/360.0;
+  const double c2 =  1.0/1260.0;
+  const double c3 = -1.0/1680.0;
+  const double c4 =  1.0/1188.0;
+  const double c5 = -691.0/360360.0;
+  const double c6 =  1.0/156.0;
+  const double c7 = -3617.0/122400.0;
+  const double ser = c0 + y*(c1 + y*(c2 + y*(c3 + y*(c4 + y*(c5 + y*(c6 + y*c7))))));
+  result->val = exp(ser/x);
+  result->err = 2.0 * GSL_DBL_EPSILON * result->val * GSL_MAX_DBL(1.0, ser/x);
+  return GSL_SUCCESS;
+}
+
+
+/* Chebyshev expansion for log(gamma(x)/gamma(8))
+ * 5 < x < 10
+ * -1 < t < 1
+ */
+static double gamma_5_10_data[24] = {
+ -1.5285594096661578881275075214,
+  4.8259152300595906319768555035,
+  0.2277712320977614992970601978,
+ -0.0138867665685617873604917300,
+  0.0012704876495201082588139723,
+ -0.0001393841240254993658962470,
+  0.0000169709242992322702260663,
+ -2.2108528820210580075775889168e-06,
+  3.0196602854202309805163918716e-07,
+ -4.2705675000079118380587357358e-08,
+  6.2026423818051402794663551945e-09,
+ -9.1993973208880910416311405656e-10,
+  1.3875551258028145778301211638e-10,
+ -2.1218861491906788718519522978e-11,
+  3.2821736040381439555133562600e-12,
+ -5.1260001009953791220611135264e-13,
+  8.0713532554874636696982146610e-14,
+ -1.2798522376569209083811628061e-14,
+  2.0417711600852502310258808643e-15,
+ -3.2745239502992355776882614137e-16,
+  5.2759418422036579482120897453e-17,
+ -8.5354147151695233960425725513e-18,
+  1.3858639703888078291599886143e-18,
+ -2.2574398807738626571560124396e-19
+};
+static const cheb_series gamma_5_10_cs = {
+  gamma_5_10_data,
+  23,
+  -1, 1,
+  11
+};
+
+
+/* gamma(x) for x >= 1/2
+ * assumes x >= 1/2
+ */
+static
+int
+gamma_xgthalf(const double x, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(x == 0.5) {
+    result->val = 1.77245385090551602729817;
+    result->err = GSL_DBL_EPSILON * result->val;
+    return GSL_SUCCESS;
+  } else if (x <= (GSL_SF_FACT_NMAX + 1.0) && x == floor(x)) {
+    int n = (int) floor (x);
+    result->val = fact_table[n - 1].f;
+    result->err = GSL_DBL_EPSILON * result->val;
+    return GSL_SUCCESS;
+  }    
+  else if(fabs(x - 1.0) < 0.01) {
+    /* Use series for Gamma[1+eps] - 1/(1+eps).
+     */
+    const double eps = x - 1.0;
+    const double c1 =  0.4227843350984671394;
+    const double c2 = -0.01094400467202744461;
+    const double c3 =  0.09252092391911371098;
+    const double c4 = -0.018271913165599812664;
+    const double c5 =  0.018004931096854797895;
+    const double c6 = -0.006850885378723806846;
+    const double c7 =  0.003998239557568466030;
+    result->val = 1.0/x + eps*(c1+eps*(c2+eps*(c3+eps*(c4+eps*(c5+eps*(c6+eps*c7))))));
+    result->err = GSL_DBL_EPSILON;
+    return GSL_SUCCESS;
+  }
+  else if(fabs(x - 2.0) < 0.01) {
+    /* Use series for Gamma[1 + eps].
+     */
+    const double eps = x - 2.0;
+    const double c1 =  0.4227843350984671394;
+    const double c2 =  0.4118403304264396948;
+    const double c3 =  0.08157691924708626638;
+    const double c4 =  0.07424901075351389832;
+    const double c5 = -0.00026698206874501476832;
+    const double c6 =  0.011154045718130991049;
+    const double c7 = -0.002852645821155340816;
+    const double c8 =  0.0021039333406973880085;
+    result->val = 1.0 + eps*(c1+eps*(c2+eps*(c3+eps*(c4+eps*(c5+eps*(c6+eps*(c7+eps*c8)))))));
+    result->err = GSL_DBL_EPSILON;
+    return GSL_SUCCESS;
+  }
+  else if(x < 5.0) {
+    /* Exponentiating the logarithm is fine, as
+     * long as the exponential is not so large
+     * that it greatly amplifies the error.
+     */
+    gsl_sf_result lg;
+    lngamma_lanczos(x, &lg);
+    result->val = exp(lg.val);
+    result->err = result->val * (lg.err + 2.0 * GSL_DBL_EPSILON);
+    return GSL_SUCCESS;
+  }
+  else if(x < 10.0) {
+    /* This is a sticky area. The logarithm
+     * is too large and the gammastar series
+     * is not good.
+     */
+    const double gamma_8 = 5040.0;
+    const double t = (2.0*x - 15.0)/5.0;
+    gsl_sf_result c;
+    cheb_eval_e(&gamma_5_10_cs, t, &c);
+    result->val  = exp(c.val) * gamma_8;
+    result->err  = result->val * c.err;
+    result->err += 2.0 * GSL_DBL_EPSILON * result->val;
+    return GSL_SUCCESS;
+  }
+  else if(x < GSL_SF_GAMMA_XMAX) {
+    /* We do not want to exponentiate the logarithm
+     * if x is large because of the inevitable
+     * inflation of the error. So we carefully
+     * use pow() and exp() with exact quantities.
+     */
+    double p = pow(x, 0.5*x);
+    double e = exp(-x);
+    double q = (p * e) * p;
+    double pre = M_SQRT2 * M_SQRTPI * q/sqrt(x);
+    gsl_sf_result gstar;
+    int stat_gs = gammastar_ser(x, &gstar);
+    result->val = pre * gstar.val;
+    result->err = (x + 2.5) * GSL_DBL_EPSILON * result->val;
+    return stat_gs;
+  }
+  else {
+    OVERFLOW_ERROR(result);
+  }
+}
+# endif
+
+/*-*-*-*-*-*-*-*-*-*-*-* Functions with Error Codes *-*-*-*-*-*-*-*-*-*-*-*/
+
+// NEED THIS
+double ohana_gsl_sf_lngamma(double x, double *err) {
+
+  if (fabs(x - 1.0) < 0.01) {
+    /* Note that we must amplify the errors
+     * from the Pade evaluations because of
+     * the way we must pass the argument, i.e.
+     * writing (1-x) is a loss of precision
+     * when x is near 1.
+     */
+    double value = lngamma_1_pade(x - 1.0, err);
+    if (err) *err *= 1.0/(GSL_DBL_EPSILON + fabs(x - 1.0));
+    return value;
+  }
+
+  if (fabs(x - 2.0) < 0.01) {
+    double value = lngamma_2_pade(x - 2.0, err);
+    if (err) *err *= 1.0/(GSL_DBL_EPSILON + fabs(x - 2.0));
+    return value;
+  }
+
+  if (x >= 0.5) {
+    double value = lngamma_lanczos(x, err);
+    return value;
+  }
+
+  if (x == 0.0) return NAN;
+
+  if (fabs(x) < 0.02) {
+    double value = lngamma_sgn_0 (x, err, NULL);
+    return value;
+  }
+
+  if (x > -0.5/(GSL_DBL_EPSILON*M_PI)) {
+    /* Try to extract a fractional
+     * part from x.
+     */
+    double z  = 1.0 - x;
+    double s  = sin(M_PI*z);
+    double as = fabs(s);
+    if (s == 0.0) return NAN;
+
+    if (as < M_PI*0.015) {
+      /* x is near a negative integer, -N */
+      if (x < INT_MIN + 2.0) {
+        if (err) *err = NAN;
+	return NAN;
+      }
+
+      int N = -(int)(x - 0.5);
+      double eps = x + N;
+      double value = lngamma_sgn_sing(N, eps, err, NULL);
+      return value;
+    }
+
+    double lg_z_err;
+    double ptr = err ? &lg_z_err : NULL;
+    double lg_z_val = lngamma_lanczos(z, ptr);
+    double value = M_LNPI - (log(as) + lg_z_val);
+    if (err) *err = 2.0 * GSL_DBL_EPSILON * fabs(value) + lg_z_err;
+    return value;
+  }
+
+  /* |x| was too large to extract any fractional part */
+  if (err) *err = NAN;
+  return NAN;
+}
+
+# if (0)
+
+int gsl_sf_lngamma_sgn_e(double x, gsl_sf_result * result_lg, double * sgn)
+{
+  if(fabs(x - 1.0) < 0.01) {
+    int stat = lngamma_1_pade(x - 1.0, result_lg);
+    result_lg->err *= 1.0/(GSL_DBL_EPSILON + fabs(x - 1.0));
+    *sgn = 1.0;
+    return stat;
+  }
+  else if(fabs(x - 2.0) < 0.01) {
+   int stat = lngamma_2_pade(x - 2.0, result_lg);
+    result_lg->err *= 1.0/(GSL_DBL_EPSILON + fabs(x - 2.0));
+    *sgn = 1.0;
+    return stat;
+  }
+  else if(x >= 0.5) {
+    *sgn = 1.0;
+    return lngamma_lanczos(x, result_lg);
+  }
+  else if(x == 0.0) {
+    *sgn = 0.0;
+    DOMAIN_ERROR(result_lg);
+  }
+  else if(fabs(x) < 0.02) {
+    return lngamma_sgn_0(x, result_lg, sgn);
+  }
+  else if(x > -0.5/(GSL_DBL_EPSILON*M_PI)) {
+   /* Try to extract a fractional
+     * part from x.
+     */
+    double z = 1.0 - x;
+    double s = sin(M_PI*x);
+    double as = fabs(s);
+    if(s == 0.0) {
+      *sgn = 0.0;
+      DOMAIN_ERROR(result_lg);
+    }
+    else if(as < M_PI*0.015) {
+      /* x is near a negative integer, -N */
+      if(x < INT_MIN + 2.0) {
+        result_lg->val = 0.0;
+        result_lg->err = 0.0;
+        *sgn = 0.0;
+        GSL_ERROR ("error", GSL_EROUND);
+      }
+      else {
+        int N = -(int)(x - 0.5);
+        double eps = x + N;
+        return lngamma_sgn_sing(N, eps, result_lg, sgn);
+      }
+    }
+    else {
+      gsl_sf_result lg_z;
+      lngamma_lanczos(z, &lg_z);
+      *sgn = (s > 0.0 ? 1.0 : -1.0);
+      result_lg->val = M_LNPI - (log(as) + lg_z.val);
+      result_lg->err = 2.0 * GSL_DBL_EPSILON * fabs(result_lg->val) + lg_z.err;
+      return GSL_SUCCESS;
+    }
+  }
+  else {
+    /* |x| was too large to extract any fractional part */
+    result_lg->val = 0.0;
+    result_lg->err = 0.0;
+    *sgn = 0.0;
+    GSL_ERROR ("x too large to extract fraction part", GSL_EROUND);
+  }
+}
+
+
+int
+gsl_sf_gamma_e(const double x, gsl_sf_result * result)
+{
+  if(x < 0.5) {
+    int rint_x = (int)floor(x+0.5);
+    double f_x = x - rint_x;
+    double sgn_gamma = ( GSL_IS_EVEN(rint_x) ? 1.0 : -1.0 );
+    double sin_term = sgn_gamma * sin(M_PI * f_x) / M_PI;
+
+    if(sin_term == 0.0) {
+      DOMAIN_ERROR(result);
+    }
+    else if(x > -169.0) {
+      gsl_sf_result g;
+      gamma_xgthalf(1.0-x, &g);
+      if(fabs(sin_term) * g.val * GSL_DBL_MIN < 1.0) {
+        result->val  = 1.0/(sin_term * g.val);
+        result->err  = fabs(g.err/g.val) * fabs(result->val);
+        result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+        return GSL_SUCCESS;
+      }
+      else {
+        UNDERFLOW_ERROR(result);
+      }
+    }
+    else {
+      /* It is hard to control it here.
+       * We can only exponentiate the
+       * logarithm and eat the loss of
+       * precision.
+       */
+      gsl_sf_result lng;
+      double sgn;
+      int stat_lng = gsl_sf_lngamma_sgn_e(x, &lng, &sgn);
+      int stat_e   = gsl_sf_exp_mult_err_e(lng.val, lng.err, sgn, 0.0, result);
+      return GSL_ERROR_SELECT_2(stat_e, stat_lng);
+    }
+  }
+  else {
+    return gamma_xgthalf(x, result);
+  }
+}
+
+
+int
+gsl_sf_gammastar_e(const double x, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(x <= 0.0) {
+    DOMAIN_ERROR(result);
+  }
+  else if(x < 0.5) {
+    gsl_sf_result lg;
+    const int stat_lg = gsl_sf_lngamma_e(x, &lg);
+    const double lx = log(x);
+    const double c  = 0.5*(M_LN2+M_LNPI);
+    const double lnr_val = lg.val - (x-0.5)*lx + x - c;
+    const double lnr_err = lg.err + 2.0 * GSL_DBL_EPSILON *((x+0.5)*fabs(lx) + c);
+    const int stat_e  = gsl_sf_exp_err_e(lnr_val, lnr_err, result);
+    return GSL_ERROR_SELECT_2(stat_lg, stat_e);
+  }
+  else if(x < 2.0) {
+    const double t = 4.0/3.0*(x-0.5) - 1.0;
+    return cheb_eval_e(&gstar_a_cs, t, result);
+  }
+  else if(x < 10.0) {
+    const double t = 0.25*(x-2.0) - 1.0;
+    gsl_sf_result c;
+    cheb_eval_e(&gstar_b_cs, t, &c);
+    result->val  = c.val/(x*x) + 1.0 + 1.0/(12.0*x);
+    result->err  = c.err/(x*x);
+    result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+  else if(x < 1.0/GSL_ROOT4_DBL_EPSILON) {
+    return gammastar_ser(x, result);
+  }
+  else if(x < 1.0/GSL_DBL_EPSILON) {
+    /* Use Stirling formula for Gamma(x).
+     */
+    const double xi = 1.0/x;
+    result->val = 1.0 + xi/12.0*(1.0 + xi/24.0*(1.0 - xi*(139.0/180.0 + 571.0/8640.0*xi)));
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+  else {
+    result->val = 1.0;
+    result->err = 1.0/x;
+    return GSL_SUCCESS;
+  }
+}
+
+# endif
+
+// NEED THIS
+int
+gsl_sf_gammainv_e(const double x, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if (x <= 0.0 && x == floor(x)) { /* negative integer */
+    result->val = 0.0;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  } else if(x < 0.5) {
+    gsl_sf_result lng;
+    double sgn;
+    int stat_lng = gsl_sf_lngamma_sgn_e(x, &lng, &sgn);
+    if(stat_lng == GSL_EDOM) {
+      result->val = 0.0;
+      result->err = 0.0;
+      return GSL_SUCCESS;
+    }
+    else if(stat_lng != GSL_SUCCESS) {
+      result->val = 0.0;
+      result->err = 0.0;
+      return stat_lng;
+    }
+    else {
+      return gsl_sf_exp_mult_err_e(-lng.val, lng.err, sgn, 0.0, result);
+    }
+  }
+  else {
+    gsl_sf_result g;
+    int stat_g = gamma_xgthalf(x, &g);
+    if(stat_g == GSL_EOVRFLW) {
+      UNDERFLOW_ERROR(result);
+    }
+    else {
+      result->val  = 1.0/g.val;
+      result->err  = fabs(g.err/g.val) * fabs(result->val);
+      result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+      CHECK_UNDERFLOW(result);
+      return GSL_SUCCESS;
+    }
+  }
+}
+
+# if (0)
+
+int
+gsl_sf_lngamma_complex_e(double zr, double zi, gsl_sf_result * lnr, gsl_sf_result * arg)
+{
+  if(zr <= 0.5) {
+    /* Transform to right half plane using reflection;
+     * in fact we do a little better by stopping at 1/2.
+     */
+    double x = 1.0-zr;
+    double y = -zi;
+    gsl_sf_result a, b;
+    gsl_sf_result lnsin_r, lnsin_i;
+
+    int stat_l = lngamma_lanczos_complex(x, y, &a, &b);
+    int stat_s = gsl_sf_complex_logsin_e(M_PI*zr, M_PI*zi, &lnsin_r, &lnsin_i);
+
+    if(stat_s == GSL_SUCCESS) {
+      int stat_r;
+      lnr->val = M_LNPI - lnsin_r.val - a.val;
+      lnr->err = lnsin_r.err + a.err + 2.0 * GSL_DBL_EPSILON * fabs(lnr->val);
+      arg->val = -lnsin_i.val - b.val;
+      arg->err = lnsin_i.err + b.err + 2.0 * GSL_DBL_EPSILON * fabs(arg->val);
+      stat_r = gsl_sf_angle_restrict_symm_e(&(arg->val));
+      return GSL_ERROR_SELECT_2(stat_r, stat_l);
+    }
+    else {
+      DOMAIN_ERROR_2(lnr,arg);
+    }
+  }
+  else {
+    /* otherwise plain vanilla Lanczos */
+    return lngamma_lanczos_complex(zr, zi, lnr, arg);
+  }
+}
+
+
+int gsl_sf_taylorcoeff_e(const int n, const double x, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(x < 0.0 || n < 0) {
+    DOMAIN_ERROR(result);
+  }
+  else if(n == 0) {
+    result->val = 1.0;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else if(n == 1) {
+    result->val = x;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else if(x == 0.0) {
+    result->val = 0.0;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else {
+    const double log2pi = M_LNPI + M_LN2;
+    const double ln_test = n*(log(x)+1.0) + 1.0 - (n+0.5)*log(n+1.0) + 0.5*log2pi;
+
+    if(ln_test < GSL_LOG_DBL_MIN+1.0) {
+      UNDERFLOW_ERROR(result);
+    }
+    else if(ln_test > GSL_LOG_DBL_MAX-1.0) {
+      OVERFLOW_ERROR(result);
+    }
+    else {
+      double product = 1.0;
+      int k;
+      for(k=1; k<=n; k++) {
+        product *= (x/k);
+      }
+      result->val = product;
+      result->err = n * GSL_DBL_EPSILON * product;
+      CHECK_UNDERFLOW(result);
+      return GSL_SUCCESS;
+    }    
+  }
+}
+
+
+int gsl_sf_fact_e(const unsigned int n, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(n < 18) {
+    result->val = fact_table[n].f;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else if(n <= GSL_SF_FACT_NMAX){
+    result->val = fact_table[n].f;
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+  else {
+    OVERFLOW_ERROR(result);
+  }
+}
+
+
+int gsl_sf_doublefact_e(const unsigned int n, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(n < 26) {
+    result->val = doub_fact_table[n].f;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else if(n <= GSL_SF_DOUBLEFACT_NMAX){
+    result->val = doub_fact_table[n].f;
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+  else {
+    OVERFLOW_ERROR(result);
+  }
+}
+
+
+int gsl_sf_lnfact_e(const unsigned int n, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(n <= GSL_SF_FACT_NMAX){
+    result->val = log(fact_table[n].f);
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+  else {
+    gsl_sf_lngamma_e(n+1.0, result);
+    return GSL_SUCCESS;
+  }
+}
+
+
+int gsl_sf_lndoublefact_e(const unsigned int n, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(n <= GSL_SF_DOUBLEFACT_NMAX){
+    result->val = log(doub_fact_table[n].f);
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+  else if(GSL_IS_ODD(n)) {
+    gsl_sf_result lg;
+    gsl_sf_lngamma_e(0.5*(n+2.0), &lg);
+    result->val = 0.5*(n+1.0) * M_LN2 - 0.5*M_LNPI + lg.val;
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val) + lg.err;
+    return GSL_SUCCESS;
+  }
+  else {
+    gsl_sf_result lg;
+    gsl_sf_lngamma_e(0.5*n+1.0, &lg);
+    result->val = 0.5*n*M_LN2 + lg.val;
+    result->err = 2.0 * GSL_DBL_EPSILON * fabs(result->val) + lg.err;
+    return GSL_SUCCESS;
+  }
+}
+
+
+int gsl_sf_lnchoose_e(unsigned int n, unsigned int m, gsl_sf_result * result)
+{
+  /* CHECK_POINTER(result) */
+
+  if(m > n) {
+    DOMAIN_ERROR(result);
+  }
+  else if(m == n || m == 0) {
+    result->val = 0.0;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else {
+    gsl_sf_result nf;
+    gsl_sf_result mf;
+    gsl_sf_result nmmf;
+    if(m*2 > n) m = n-m;
+    gsl_sf_lnfact_e(n, &nf);
+    gsl_sf_lnfact_e(m, &mf);
+    gsl_sf_lnfact_e(n-m, &nmmf);
+    result->val  = nf.val - mf.val - nmmf.val;
+    result->err  = nf.err + mf.err + nmmf.err;
+    result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  }
+}
+
+
+int gsl_sf_choose_e(unsigned int n, unsigned int m, gsl_sf_result * result)
+{
+  if(m > n) {
+    DOMAIN_ERROR(result);
+  }
+  else if(m == n || m == 0) {
+    result->val = 1.0;
+    result->err = 0.0;
+    return GSL_SUCCESS;
+  }
+  else if (n <= GSL_SF_FACT_NMAX) {
+    result->val = (fact_table[n].f / fact_table[m].f) / fact_table[n-m].f;
+    result->err = 6.0 * GSL_DBL_EPSILON * fabs(result->val);
+    return GSL_SUCCESS;
+  } else {
+    if(m*2 < n) m = n-m;
+
+    if (n - m < 64)  /* compute product for a manageable number of terms */
+      {
+        double prod = 1.0;
+        unsigned int k;
+        
+        for(k=n; k>=m+1; k--) {
+          double tk = (double)k / (double)(k-m);
+          if(tk > GSL_DBL_MAX/prod) {
+            OVERFLOW_ERROR(result);
+          }
+          prod *= tk;
+        }
+        result->val = prod;
+        result->err = 2.0 * GSL_DBL_EPSILON * prod * fabs(n-m);
+        return GSL_SUCCESS;
+      }
+    else
+      {
+        gsl_sf_result lc;
+        const int stat_lc = gsl_sf_lnchoose_e (n, m, &lc);
+        const int stat_e  = gsl_sf_exp_err_e(lc.val, lc.err, result);
+        return GSL_ERROR_SELECT_2(stat_lc, stat_e);
+      }
+  }
+}
+
+
+/*-*-*-*-*-*-*-*-*-* Functions w/ Natural Prototypes *-*-*-*-*-*-*-*-*-*-*/
+
+#include "eval.h"
+
+double gsl_sf_fact(const unsigned int n)
+{
+  EVAL_RESULT(gsl_sf_fact_e(n, &result));
+}
+
+double gsl_sf_lnfact(const unsigned int n)
+{
+  EVAL_RESULT(gsl_sf_lnfact_e(n, &result));
+}
+
+double gsl_sf_doublefact(const unsigned int n)
+{
+  EVAL_RESULT(gsl_sf_doublefact_e(n, &result));
+}
+
+double gsl_sf_lndoublefact(const unsigned int n)
+{
+  EVAL_RESULT(gsl_sf_lndoublefact_e(n, &result));
+}
+
+double gsl_sf_lngamma(const double x)
+{
+  EVAL_RESULT(gsl_sf_lngamma_e(x, &result));
+}
+
+double gsl_sf_gamma(const double x)
+{
+  EVAL_RESULT(gsl_sf_gamma_e(x, &result));
+}
+
+double gsl_sf_gammastar(const double x)
+{
+  EVAL_RESULT(gsl_sf_gammastar_e(x, &result));
+}
+
+double gsl_sf_gammainv(const double x)
+{
+  EVAL_RESULT(gsl_sf_gammainv_e(x, &result));
+}
+
+double gsl_sf_taylorcoeff(const int n, const double x)
+{
+  EVAL_RESULT(gsl_sf_taylorcoeff_e(n, x, &result));
+}
+
+double gsl_sf_choose(unsigned int n, unsigned int m)
+{
+  EVAL_RESULT(gsl_sf_choose_e(n, m, &result));
+}
+
+double gsl_sf_lnchoose(unsigned int n, unsigned int m)
+{
+  EVAL_RESULT(gsl_sf_lnchoose_e(n, m, &result));
+}
+# endif
Index: /trunk/Ohana/src/libohana/src/gsl_utils.c
===================================================================
--- /trunk/Ohana/src/libohana/src/gsl_utils.c	(revision 37807)
+++ /trunk/Ohana/src/libohana/src/gsl_utils.c	(revision 37807)
@@ -0,0 +1,387 @@
+# include <ohana.h>
+# include <gsl_utils.h>
+
+/*-*-*-*-*-*-*-*-*-*-*-* Private Section *-*-*-*-*-*-*-*-*-*-*-*/
+
+/* Chebyshev expansion for log(1 + x(t))/x(t)
+ *
+ * x(t) = (4t-1)/(2(4-t))
+ * t(x) = (8x+1)/(2(x+2))
+ * -1/2 < x < 1/2
+ * -1 < t < 1
+ */
+static double log_1plusx_data[21] = {
+  2.16647910664395270521272590407,
+ -0.28565398551049742084877469679,
+  0.01517767255690553732382488171,
+ -0.00200215904941415466274422081,
+  0.00019211375164056698287947962,
+ -0.00002553258886105542567601400,
+  2.9004512660400621301999384544e-06,
+ -3.8873813517057343800270917900e-07,
+  4.7743678729400456026672697926e-08,
+ -6.4501969776090319441714445454e-09,
+  8.2751976628812389601561347296e-10,
+ -1.1260499376492049411710290413e-10,
+  1.4844576692270934446023686322e-11,
+ -2.0328515972462118942821556033e-12,
+  2.7291231220549214896095654769e-13,
+ -3.7581977830387938294437434651e-14,
+  5.1107345870861673561462339876e-15,
+ -7.0722150011433276578323272272e-16,
+  9.7089758328248469219003866867e-17,
+ -1.3492637457521938883731579510e-17,
+  1.8657327910677296608121390705e-18
+};
+
+static cheb_series log_1plusx_cs = {
+  log_1plusx_data,
+  20,
+  -1, 1,
+  10
+};
+
+static inline double ohana_gsl_cheb_eval(const cheb_series *cs, const double x, double *err) {
+
+  double d  = 0.0;
+  double dd = 0.0;
+
+  double y  = (2.0*x - cs->a - cs->b) / (cs->b - cs->a);
+  double y2 = 2.0 * y;
+
+  double e = 0.0;
+
+  int j;
+  for (j = cs->order; j >= 1; j--) {
+    double temp = d;
+    d = y2*d - dd + cs->c[j];
+    e += fabs(y2*temp) + fabs(dd) + fabs(cs->c[j]);
+    dd = temp;
+  }
+
+  { 
+    double temp = d;
+    d = y*d - dd + 0.5 * cs->c[0];
+    e += fabs(y*temp) + fabs(dd) + 0.5 * fabs(cs->c[0]);
+  }
+
+  double value = d;
+  if (err) *err = GSL_DBL_EPSILON * e + fabs(cs->c[cs->order]);
+
+  return value;
+}
+
+/*** end chebyshev approximation code *******************************************/
+
+double ohana_gsl_sf_log_1plusx (double x, double *err) {
+
+  if (x <= -1.0) return NAN;
+
+  if (fabs(x) < GSL_ROOT6_DBL_EPSILON) {
+    const double c1 = -0.5;
+    const double c2 =  1.0/3.0;
+    const double c3 = -1.0/4.0;
+    const double c4 =  1.0/5.0;
+    const double c5 = -1.0/6.0;
+    const double c6 =  1.0/7.0;
+    const double c7 = -1.0/8.0;
+    const double c8 =  1.0/9.0;
+    const double c9 = -1.0/10.0;
+    const double t  =  c5 + x*(c6 + x*(c7 + x*(c8 + x*c9)));
+    double value = x * (1.0 + x*(c1 + x*(c2 + x*(c3 + x*(c4 + x*t)))));
+    if (err) *err = GSL_DBL_EPSILON * fabs(value);
+    return value;
+  }
+
+  if (fabs(x) < 0.5) {
+    double t = 0.5*(8.0*x + 1.0)/(x+2.0);
+
+    // log_1plusx_cs is a static cheb_series structure
+    double c = ohana_gsl_cheb_eval (&log_1plusx_cs, t, err);
+
+    double value = x * c;
+
+    if (err) *err = fabs(x * *err); 
+
+    return value;
+  }
+
+  double value = log(1.0 + x);
+
+  if (err) *err = GSL_DBL_EPSILON * fabs(value);
+
+  return value;
+}
+
+double ohana_gsl_sf_lnpoch(const double a, const double x, double *err) {
+
+  if (a   <= 0.0) return NAN;
+  if (a+x <= 0.0) return NAN;
+
+  if (x == 0.0) {
+    double value = 0.0;
+    if (err) *err = 0.0;
+    return value;
+  }
+
+  double value = ohana_gsl_lnpoch_pos (a, x, err);
+  return value;
+}
+
+/* Assumes a>0 and a+x>0.
+ */
+double ohana_gsl_lnpoch_pos (const double a, const double x, double *err) {
+
+  double absx = fabs(x);
+
+  if (absx > 0.1*a || absx*log(MAX(a,2.0)) > 0.1) {
+    if(a < GSL_SF_GAMMA_XMAX && a+x < GSL_SF_GAMMA_XMAX) {
+      /* If we can do it by calculating the gamma functions
+       * directly, then that will be more accurate than
+       * doing the subtraction of the logs.
+       */
+
+      double g1_error = NAN;
+      double g2_error = NAN;
+      double *ptrerr = NULL;
+
+      if (err) ptrerr = &g1_error;
+      double g1_value = ohana_gsl_sf_gammainv(a, ptrerr);
+
+      if (err) ptrerr = &g2_error;
+      double g2_value = ohana_gsl_sf_gammainv(a+x, ptrerr);
+
+      value = -log(g2_value / g1_value);
+
+      if (err) {
+	double tmp_err = (g1_error / fabs(g1_value)) + (g2_error / fabs(g2_value));
+	tmp_err += 2.0 * GSL_DBL_EPSILON * fabs(value);
+	*err = tmp_err;
+      }
+      return value;
+    }
+
+    /* Otherwise we must do the subtraction.
+     */
+    double lg1_error = NAN;
+    double lg2_error = NAN;
+    double *ptrerr = NULL;
+
+    if (err) ptrerr = &lg1_error;
+    double lg1_value = ohana_gsl_sf_lngamma(a, ptrerr);
+
+    if (err) ptrerr = &lg2_error;
+    double lg2_value = ohana_gsl_sf_lngamma(a+x, ptrerr);
+
+    double value = lg2_value - lg1_value;
+
+    if (err) {
+      double tmp_err = lg2_error + lg1_error;
+      tmp_err += 2.0 * GSL_DBL_EPSILON * fabs(value);
+      *err = tmp_err;
+    }
+    return value;
+  }
+
+  if ((absx < 0.1*a) && (a > 15.0)) {
+    /* Be careful about the implied subtraction.
+     * Note that both a+x and and a must be
+     * large here since a is not small
+     * and x is not relatively large.
+     * So we calculate using Stirling for Log[Gamma(z)].
+     *
+     *   Log[Gamma(a+x)/Gamma(a)] = x(Log[a]-1) + (x+a-1/2)Log[1+x/a]
+     *                              + (1/(1+eps)   - 1) / (12 a)
+     *                              - (1/(1+eps)^3 - 1) / (360 a^3)
+     *                              + (1/(1+eps)^5 - 1) / (1260 a^5)
+     *                              - (1/(1+eps)^7 - 1) / (1680 a^7)
+     *                              + ...
+     */
+    const double eps = x/a;
+    const double den = 1.0 + eps;
+    const double d3 = den*den*den;
+    const double d5 = d3*den*den;
+    const double d7 = d5*den*den;
+    const double c1 = -eps/den;
+    const double c3 = -eps*(3.0+eps*(3.0+eps))/d3;
+    const double c5 = -eps*(5.0+eps*(10.0+eps*(10.0+eps*(5.0+eps))))/d5;
+    const double c7 = -eps*(7.0+eps*(21.0+eps*(35.0+eps*(35.0+eps*(21.0+eps*(7.0+eps))))))/d7;
+
+    const double p8 = ohana_gsl_sf_pow_int(1.0+eps,8);
+    const double c8 = 1.0/p8             - 1.0;  /* these need not   */
+    const double c9 = 1.0/(p8*(1.0+eps)) - 1.0;  /* be very accurate */
+    const double a4 = a*a*a*a;
+    const double a6 = a4*a*a;
+    const double ser_1 = c1 + c3/(30.0*a*a) + c5/(105.0*a4) + c7/(140.0*a6);
+    const double ser_2 = c8/(99.0*a6*a*a) - 691.0/360360.0 * c9/(a6*a4);
+    const double ser = (ser_1 + ser_2)/ (12.0*a);
+
+    double term1 = x * log(a/M_E);
+    double term2;
+
+    double ln_1peps_error = NAN;
+    double *ptr = err ? &ln_1peps_error : NULL;
+
+    double ln_1peps_value = ohana_gsl_sf_log_1plusx(eps, ptr);  /* log(1 + x/a) */
+    term2 = (x + a - 0.5) * ln_1peps_value;
+
+    double value = term1 + term2 + ser;
+    
+    if (err) {
+      double tmp_err = GSL_DBL_EPSILON*fabs(term1);
+      tmp_err += fabs((x + a - 0.5)*ln_1peps_error);
+      tmp_err += fabs(ln_1peps_value) * GSL_DBL_EPSILON * (fabs(x) + fabs(a) + 0.5);
+      tmp_err += 2.0 * GSL_DBL_EPSILON * fabs(value);
+      *err = tmp_err;
+    }
+    return value;
+  }
+
+  double poch_rel_error = NAN;
+  double *ptr = err ? &poch_rel_error : NULL;
+  double poch_rel_value = ohana_gsl_pochrel_smallx (a, x, ptr);
+
+  double eps = x*poch_rel_value;
+
+  double value = ohana_gsl_sf_log_1plusx(eps, NULL);
+
+  if (err) {
+    double tmp_err  = 2.0 * fabs(x * poch_rel_error / (1.0 + eps));
+    tmp_err += 2.0 * GSL_DBL_EPSILON * fabs(value);
+    *err = tmp_err;
+  }
+
+  return value;
+}
+
+/* ((a)_x - 1)/x in the "small x" region where
+ * cancellation must be controlled.
+ *
+ * Based on SLATEC DPOCH1().
+ */
+/*
+C When ABS(X) is so small that substantial cancellation will occur if
+C the straightforward formula is used, we use an expansion due
+C to Fields and discussed by Y. L. Luke, The Special Functions and Their
+C Approximations, Vol. 1, Academic Press, 1969, page 34.
+C
+C The ratio POCH(A,X) = GAMMA(A+X)/GAMMA(A) is written by Luke as
+C        (A+(X-1)/2)**X * polynomial in (A+(X-1)/2)**(-2) .
+C In order to maintain significance in POCH1, we write for positive a
+C        (A+(X-1)/2)**X = EXP(X*LOG(A+(X-1)/2)) = EXP(Q)
+C                       = 1.0 + Q*EXPREL(Q) .
+C Likewise the polynomial is written
+C        POLY = 1.0 + X*POLY1(A,X) .
+C Thus,
+C        POCH1(A,X) = (POCH(A,X) - 1) / X
+C                   = EXPREL(Q)*(Q/X + Q*POLY1(A,X)) + POLY1(A,X)
+C
+*/
+double ohana_gsl_pochrel_smallx(const double a, const double x, double *err) {
+  /*
+   SQTBIG = 1.0D0/SQRT(24.0D0*D1MACH(1))
+   ALNEPS = LOG(D1MACH(3))
+   */
+  const double SQTBIG = 1.0/(2.0*M_SQRT2*M_SQRT3*GSL_SQRT_DBL_MIN);
+  const double ALNEPS = GSL_LOG_DBL_EPSILON - M_LN2;
+
+  if (x == 0.0) {
+    double value = ohana_gsl_sf_psi(a, err);
+    return value;
+  }
+
+  const double bp   = (  (a < -0.5) ? 1.0-a-x : a );
+  const int    incr = ( (bp < 10.0) ? 11.0-bp : 0 );
+  const double b    = bp + incr;
+
+  double var    = b + 0.5*(x-1.0);
+  double alnvar = log(var);
+  double q = x*alnvar;
+  
+  double poly1 = 0.0;
+  
+  if (var < SQTBIG) {
+    const int nterms = (int)(-0.5*ALNEPS/alnvar + 1.0);
+    const double var2 = (1.0/var)/var;
+    const double rho  = 0.5 * (x + 1.0);
+    double term = var2;
+    double gbern[24];
+    int k, j;
+
+    gbern[1] = 1.0;
+    gbern[2] = -rho/12.0;
+    poly1 = gbern[2] * term;
+
+    if (nterms > 20) {
+      /* NTERMS IS TOO BIG, MAYBE D1MACH(3) IS BAD */
+      /* nterms = 20; */
+      value = NAN;
+      if (err) *err = NAN;
+      return value;
+    }
+
+    for (k = 2; k <= nterms; k++) {
+      double gbk = 0.0;
+      for (j = 1; j <= k; j++) {
+	gbk += bern[k-j+1]*gbern[j];
+      }
+      gbern[k+1] = -rho*gbk/k;
+
+      term  *= (2*k-2-x)*(2*k-1-x)*var2;
+      poly1 += gbern[k+1]*term;
+    }
+  }
+
+  double dexprl = ohana_gsl_sf_expm1(q);
+  if (!isfinite(dexprl)) {
+    double value = NAN;
+    if (err) *err = NAN;
+    return value;
+  }
+
+  dexprl = dexprl / q;
+  poly1 *= (x - 1.0);
+  double dpoch1 = dexprl * (alnvar + q * poly1) + poly1;
+
+  int i;
+  for (i = incr-1; i >= 0; i--) {
+    /*
+      C WE HAVE DPOCH1(B,X), BUT BP IS SMALL, SO WE USE BACKWARDS RECURSION
+      C TO OBTAIN DPOCH1(BP,X).
+    */
+    double binv = 1.0/(bp+i);
+    dpoch1 = (dpoch1 - binv) / (1.0 + x*binv);
+  }
+
+  if (bp == a) {
+    double value = dpoch1;
+    if (err) *err = 2.0 * GSL_DBL_EPSILON * (fabs(incr) + 1.0) * fabs(value);
+    return value;
+  }
+
+  /*
+    C WE HAVE DPOCH1(BP,X), BUT A IS LT -0.5.  WE THEREFORE USE A
+    C REFLECTION FORMULA TO OBTAIN DPOCH1(A,X).
+  */
+  double sinpxx = sin(M_PI*x)/x;
+  double sinpx2 = sin(0.5*M_PI*x);
+  double t1 = sinpxx/tan(M_PI*b);
+  double t2 = 2.0*sinpx2*(sinpx2/x);
+  double trig  = t1 - t2;
+  double value = dpoch1 * (1.0 + x*trig) + trig;
+
+  if (err) {
+    double tmp_err  = (fabs(dpoch1*x) + 1.0) * GSL_DBL_EPSILON * (fabs(t1) + fabs(t2));
+    tmp_err += 2.0 * GSL_DBL_EPSILON * (fabs(incr) + 1.0) * fabs(value);
+    *err = tmp_err;
+  }
+  return value;
+}
+
+/* functions I still need to define: */
+
+ohana_gsl_sf_gammainv
+ohana_gsl_sf_lngamma
+ohana_gsl_sf_pow_int
+ohana_gsl_sf_psi
+ohana_gsl_sf_expm1
Index: /trunk/Ohana/src/libohana/src/legendre.c
===================================================================
--- /trunk/Ohana/src/libohana/src/legendre.c	(revision 37807)
+++ /trunk/Ohana/src/libohana/src/legendre.c	(revision 37807)
@@ -0,0 +1,366 @@
+# include <ohana.h>
+# include <gsl_utils.h>
+
+/* some useful functions for legendre polynomials, spherical harmonics, and vector spherical harmonics */
+
+/* Some of the functions below are derived based on code from the gnu scientific library (gsl) v1.11
+
+   The corresponding copyright from that code is given in doc/gsl.txt
+
+   * From:
+   * specfunc/legendre_poly.c
+   * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 Gerard Jungman
+   * Author:  G. Jungman
+
+   * adaptations by Eugene Magnier
+ */
+
+/* Calculate P_m^m(x) from the analytic result:
+ *   P_m^m(x) = (-1)^m (2m-1)!! (1-x^2)^(m/2) , m > 0
+ *            = 1 , m = 0
+ * from gsl v1.11 specfunc/legendre_poly.c:
+ */
+static double legendre_Pmm(int m, double x) {
+
+  if (m == 0) return 1.0;
+
+  double value = 1.0;
+  double factor = sqrt(1.0 - x) * sqrt(1.0 + x);
+  double coeff = 1.0;
+
+  int i;
+  for (i = 1; i <= m; i++) {
+    value *= -coeff * factor;
+    coeff += 2.0;
+  }
+  return value;
+}
+
+/** these functions also return the accumulated floating-point error if requested  */
+
+static double legendre_P0(double x, double *err) {
+
+  double value = 1.0;
+  if (err) *err = 0.0;
+
+  return value;
+}
+
+static double legendre_P1(double x, double *err) {
+
+  double value = x;
+  if (err) *err = 0.0;
+
+  return value;
+}
+
+static double legendre_P2(double x, double *err) {
+
+  double value = 0.5*(3.0*x*x - 1.0);
+  if (err) *err = GSL_DBL_EPSILON * (fabs(3.0*x*x) + 1.0);
+
+  return value;
+}
+
+static double legendre_P3(double x, double *err) {
+
+  double value = 0.5*x*(5.0*x*x - 3.0);
+  if (err) *err = GSL_DBL_EPSILON * (fabs(value) + 0.5 * fabs(x) * (fabs(5.0*x*x) + 3.0));
+
+  return value;
+}
+
+double legendre_Pl (int l, double x, double *err) { 
+
+  double value;
+
+  if (l < 0) return NAN;
+  if (x < -1.0) return NAN;
+  if (x > +1.0) return NAN;
+
+  // pre-defined l-values:
+  if (l == 0) {
+    value = legendre_P0(x, err);
+    return value;
+  }
+  if (l == 1) {
+    value = legendre_P1(x, err);
+    return value;
+  }
+  if (l == 2) {
+    value = legendre_P2(x, err);
+    return value;
+  }
+  if (l == 3) {
+    value = legendre_P3(x, err);
+    return value;
+  }
+
+  if (x == 1.0) {
+    value = 1.0;
+    if (err) *err = 0.0;
+    return value;
+  }
+  if (x == -1.0) {
+    result->val = (GSL_IS_ODD(l) ? -1.0 : 1.0);
+    if (err) *err = 0.0;
+    return value;
+  }
+
+  if (l < 100000) {
+    /* upward recurrence: l P_l = (2l-1) z P_{l-1} - (l-1) P_{l-2} */
+
+    double p_LM2 = 1.0;    /* P_0(x) */
+    double p_LM1 = x;      /* P_1(x) */
+    double p_L = p_LM1;
+
+    double e_LM2 = GSL_DBL_EPSILON;
+    double e_LM1 = fabs(x)*GSL_DBL_EPSILON;
+    double e_L = e_LM1;
+
+    int L;
+    for (L = 2; L <= l; L++){
+      p_L = (x*(2*L-1)*p_LM1 - (L-1)*p_LM2) / L;
+      p_LM2 = p_LM1;
+      p_LM1 = p_L;
+
+      if (err) {
+	e_L = 0.5*(fabs(x)*(2*L-1.0) * e_LM1 + (L-1.0)*e_LM2)/L;
+	e_LM2 = e_LM1;
+	e_LM1 = e_L;
+      }
+    }
+
+    value = p_L;
+    if (err) *err = e_L + l*fabs(p_L)*GSL_DBL_EPSILON;
+    return value;
+  }
+
+  /* last resort : Asymptotic expansion.
+   * [Olver, p. 473]
+   */
+# if (0)
+  double u  = l + 0.5;
+  double th = acos(x);
+  gsl_sf_result J0;
+  gsl_sf_result Jm1;
+  int stat_J0  = gsl_sf_bessel_J0_e(u*th, &J0);
+  int stat_Jm1 = gsl_sf_bessel_Jn_e(-1, u*th, &Jm1);
+  double pre;
+  double B00;
+  double c1;
+
+  /* B00 = 1/8 (1 - th cot(th) / th^2
+   * pre = sqrt(th/sin(th))
+   */
+  if(th < GSL_ROOT4_DBL_EPSILON) {
+    B00 = (1.0 + th*th/15.0)/24.0;
+    pre = 1.0 + th*th/12.0;
+  }
+  else {
+    double sin_th = sqrt(1.0 - x*x);
+    double cot_th = x / sin_th;
+    B00 = 1.0/8.0 * (1.0 - th * cot_th) / (th*th);
+    pre = sqrt(th/sin_th);
+  }
+
+  c1 = th/u * B00;
+
+  result->val  = pre * (J0.val + c1 * Jm1.val);
+  result->err  = pre * (J0.err + fabs(c1) * Jm1.err);
+  result->err += GSL_SQRT_DBL_EPSILON * fabs(result->val);
+
+  return GSL_ERROR_SELECT_2(stat_J0, stat_Jm1);
+# else
+  fprintf (stderr, "legendre polynomials are not defined for l >= 10000\n");
+  return NAN;
+# endif
+}
+
+/* If l is large and m is large, then we have to worry
+ * about overflow. Calculate an approximate exponent which
+ * measures the normalization of this thing.
+ */
+
+double legendre_Plm (int l, int m, double x, double *err) {
+
+  // check on the domain
+  if (m < 0) return NAN;
+  if (l < m) return NAN;
+  if (x < -1.0) return NAN;
+  if (x > +1.0) return NAN;
+
+  const double dif = l-m;
+  const double sum = l+m;
+  const double t_d = ( dif == 0.0 ? 0.0 : 0.5 * dif * (log(dif)-1.0) );
+  const double t_s = ( dif == 0.0 ? 0.0 : 0.5 * sum * (log(sum)-1.0) );
+  const double exp_check = 0.5 * log(2.0*l+1.0) + t_d - t_s;
+
+  if (exp_check < GSL_LOG_DBL_MIN + 10.0) return NAN;
+
+  /* Account for the error due to the
+   * representation of 1-x.
+   */
+  const double err_amp = 1.0 / (GSL_DBL_EPSILON + fabs(1.0-fabs(x)));
+
+  /* P_m^m(x) and P_{m+1}^m(x) */
+  double p_mm   = legendre_Pmm(m, x);
+  double p_mmp1 = x * (2*m + 1) * p_mm;
+
+  double value;
+
+  if (l == m){
+    value = p_mm;
+    if (err) *err = err_amp * 2.0 * GSL_DBL_EPSILON * fabs(p_mm);
+    return value;
+  }
+
+  if (l == m + 1) {
+    value = p_mmp1;
+    if (err) *err = err_amp * 2.0 * GSL_DBL_EPSILON * fabs(p_mmp1);
+    return value;
+  }
+
+  /* upward recurrence: (l-m) P(l,m) = (2l-1) z P(l-1,m) - (l+m-1) P(l-2,m)
+   * start at P(m,m), P(m+1,m)
+   */
+
+  double p_LM2 = p_mm;
+  double p_LM1 = p_mmp1;
+  double p_L = 0.0;
+
+  int L;
+  for (L = m + 2; L <= l; L++){
+    p_L = (x*(2*L-1)*p_LM1 - (L+m-1)*p_LM2) / (L-m);
+    p_LM2 = p_LM1;
+    p_LM1 = p_L;
+  }
+  value = p_L;
+
+  if (err) *err = err_amp * (0.5*(l-m) + 1.0) * GSL_DBL_EPSILON * fabs(p_L);
+  
+  return value;
+}
+
+double legendre_Plm_sphere(int l, int m, double x, double *err) {
+
+  // check on the domain
+  if (m < 0) return NAN;
+  if (l < m) return NAN;
+  if (x < -1.0) return NAN;
+  if (x > +1.0) return NAN;
+
+  if (m == 0) {
+
+    double Pvalue = legendre_Pl (l, x, err);
+
+    double pre = sqrt((2.0*l + 1.0)/(4.0*M_PI));
+
+    double value  = pre * Pvalue;
+    if (err) {
+      double tmp_err = pre * *err;
+      *err = tmp_err + 2.0 * GSL_DBL_EPSILON * fabs(value);
+    }
+    return value;
+  }
+
+  if (x == 1.0 || x == -1.0) {
+    /* m > 0 here */
+    double value = 0.0;
+    if (err) *err = 0.0;
+    return value;
+  }
+
+
+  /* m > 0 and |x| < 1 here */
+
+  /* Starting value for recursion.
+   * Y_m^m(x) = sqrt( (2m+1)/(4pi m) gamma(m+1/2)/gamma(m) ) (-1)^m (1-x^2)^(m/2) / pi^(1/4)
+   */
+
+  const double sgn = ( GSL_IS_ODD(m) ? -1.0 : 1.0);
+  const double y_mmp1_factor = x * sqrt(2.0*m + 3.0);
+
+  double y_mmp1, y_mmp1_err;
+
+  double *errptr = NULL;
+  double lncirc_err, lnpoch_err;
+
+  // NOTE: only evaluate the error if requested
+  if (err) errptr = &lncirc_err
+  double lncirc = ohana_gsl_sf_log_1plusx(-x*x, errptr);
+
+  if (err) errptr = &lnpoch_err
+  double lnpoch = ohana_gsl_sf_lnpoch(m, 0.5, errptr);  /* Gamma(m+1/2)/Gamma(m) */
+
+  double lnpre_val = -0.25*M_LNPI + 0.5 * (lnpoch + m*lncirc);
+  double lnpre_err = NAN;
+  
+  if (err) {
+    lnpre_err = 0.25*M_LNPI*GSL_DBL_EPSILON + 0.5 * (lnpoch_err + fabs(m)*lncirc_err);
+  }
+
+  /* Compute exp(ln_pre) with error term, avoiding call to gsl_sf_exp_err BJG */
+  double ex_pre_val = exp(lnpre_val);
+
+  double ex_pre_err = NAN;
+  if (err) {
+    ex_pre_err = 2.0*(sinh(lnpre_err) + GSL_DBL_EPSILON)*ex_pre_val;
+  }
+
+  double sr = sqrt((2.0+1.0/m)/(4.0*M_PI));
+  double y_mm = sgn * sr * ex_pre_val;
+  double y_mmp1 = y_mmp1_factor * y_mm;
+
+  double y_mm_err = NAN;
+  double y_mmp1_err = NAN;
+  if (err) {
+    y_mm_err  = 2.0 * GSL_DBL_EPSILON * fabs(y_mm) + sr * ex_pre_err;
+    y_mm_err *= 1.0 + 1.0/(GSL_DBL_EPSILON + fabs(1.0-x));
+    y_mmp1_err = fabs(y_mmp1_factor) * y_mm_err;
+  }
+
+  if (l == m){
+    double value = y_mm;
+    if (err) {
+      *err = y_mm_err;
+      *err += 2.0 * GSL_DBL_EPSILON * fabs(y_mm);
+    }
+    return value;
+  }
+
+  if (l == m + 1) {
+    double value = y_mmp1;
+    if (err) {
+      *err  = y_mmp1_err;
+      *err += 2.0 * GSL_DBL_EPSILON * fabs(y_mmp1);
+    }
+    return value;
+  }
+
+  double y_L = 0.0;
+  double y_L_err = NAN;
+
+  /* Compute Y_l^m, l > m+1, upward recursion on l. */
+  int L;
+  for(L=m+2; L <= l; L++){
+    const double rat1 = (double)(L-m)/(double)(L+m);
+    const double rat2 = (L-m-1.0)/(L+m-1.0);
+    const double factor1 = sqrt(rat1*(2.0*L+1.0)*(2.0*L-1.0));
+    const double factor2 = sqrt(rat1*rat2*(2.0*L+1.0)/(2.0*L-3.0));
+    y_L = (x*y_mmp1*factor1 - (L+m-1.0)*y_mm*factor2) / (L-m);
+    y_mm   = y_mmp1;
+    y_mmp1 = y_L;
+
+    if (err) {
+      y_L_err = 0.5*(fabs(x*factor1)*y_mmp1_err + fabs((L+m-1.0)*factor2)*y_mm_err) / fabs(L-m);
+      y_mm_err = y_mmp1_err;
+      y_mmp1_err = y_L_err;
+    }
+  }
+
+  value = y_L;
+  if (err) *err = y_L_err + (0.5*(l-m) + 1.0) * GSL_DBL_EPSILON * fabs(y_L);
+
+  return value;
+}
Index: /trunk/Ohana/src/libohana/src/ohana_allocate.c
===================================================================
--- /trunk/Ohana/src/libohana/src/ohana_allocate.c	(revision 37806)
+++ /trunk/Ohana/src/libohana/src/ohana_allocate.c	(revision 37807)
@@ -178,5 +178,10 @@
   if (!in) return;
 
+  // fprintf (stderr, "free %zx\n", (size_t) in);
+
   ref = (Memblock *) in - 1;
+
+  // fprintf (stderr, "  file: %s, line: %d, func: %s, size: %zd, addr: %zx\n", 
+  // ref->file, ref->line, ref->func, ref->size, (size_t) ref);
 
   pthread_mutex_lock(&memBlockListMutex);
Index: /trunk/Ohana/src/libohana/src/spherical.c
===================================================================
--- /trunk/Ohana/src/libohana/src/spherical.c	(revision 37807)
+++ /trunk/Ohana/src/libohana/src/spherical.c	(revision 37807)
@@ -0,0 +1,276 @@
+# include "ohana.h"
+# include <gsl/gsl_sf_legendre.h>
+
+SHterms *SHtermsInit (int lmax) {
+
+  // 0,0  -- N = 1, S = 1
+  // 1,-1; 1,0; 1,1 -- N = 3, S = 4 (l+1)^2
+  // 2,-2; 2,-1; 2,0; 2,1; 2,2 -- N = 5, S = 9 (l+1)^2
+  // 3,-3; 3,-2; 3,-1; 3,0; 3,1; 3,2, 3,3 -- N = 7, S = 16 (l+1)^2
+
+  SHterms *terms = NULL;
+  ALLOCATE (terms, SHterms, 1);
+  terms->lmax = lmax;
+  terms->Nterms = SQ(lmax+1);
+
+  ALLOCATE_ZERO (terms->Fr, double, terms->Nterms);
+  ALLOCATE_ZERO (terms->Fi, double, terms->Nterms);
+  ALLOCATE_ZERO (terms->l,     int, terms->Nterms);
+  ALLOCATE_ZERO (terms->m,     int, terms->Nterms);
+  
+  int l, m;
+  int n = 0;
+  terms->l[n] = 0;
+  terms->m[n] = 0;
+  n++;
+  for (l = 1; l <= terms->lmax; l++) {
+    for (m = -l; m <= l; m++) {
+      terms->l[n] = l;
+      terms->m[n] = m;
+      n++;
+    }
+  }
+  return terms;
+}
+
+void SHtermsFree (SHterms *terms) {
+
+  free (terms->Fr);
+  free (terms->Fi);
+  free (terms->l);
+  free (terms->m);
+
+  free (terms);
+  return;
+}
+
+void SHtermsForRD (SHterms *terms, double R, double D) {
+
+  // given a point on the sky R,D
+
+  // for the real terms, Y_lm =
+  // (m >  0) : Y_{l,m} = K(l,m) P_{l,m}*cos(m*R)    = gsl_sphPlm(l,|m|,cos(90-D))*cos(|m|*R)  [cos(x) == cos(-x)]
+  // (m <  0) : Y_{l,m} = K(l,m) P_{l,m}*sin(|m|*R)  = gsl_sphPlm(l,|m|,cos(90-D))*sin(|m|*R)
+  // (m == 0) : Y_{l,m} = K(l,m) P_{l,m}             = gsl_sphPlm(l,|m|,cos(90-D))
+
+  double Rrad = R * RAD_DEG;
+  double Drad = D * RAD_DEG;
+
+  int l, m;
+
+  int n = 0;
+  for (l = 0; l <= terms->lmax; l++) {
+
+    for (m = -l; m <= l; m++) {
+    
+      int m_abs = abs(m);
+      double aP_lm = gsl_sf_legendre_sphPlm(l, m_abs, cos(M_PI/2 - Drad)); // alt-P_lm = P_lm (l, |m|, cos(90 - D))
+
+      terms->Fr[n] = aP_lm * cos (m_abs * Rrad) ; 
+      terms->Fi[n] = aP_lm * sin (m_abs * Rrad);
+      n++;
+    }
+  }
+}
+/** I can confirm that these terms match those generated by V. Makarov's program **/
+
+void SHtermsForLM (SHterms *terms, double R, double D, int l, int m) {
+
+  double Rrad = R * RAD_DEG;
+  double Drad = D * RAD_DEG;
+
+  int m_abs = abs(m);
+  double aP_lm = gsl_sf_legendre_sphPlm(l, m_abs, cos(M_PI/2 - Drad)); // alt-P_lm = P_lm (l, |m|, cos(90 - D))
+
+  terms->Fr[0] = aP_lm * cos (m_abs * Rrad) ; 
+  terms->Fi[0] = aP_lm * sin (m_abs * Rrad);
+  terms->l[0] = l;
+  terms->m[0] = m;
+}
+
+VSHterms *VSHtermsInit (int lmax) {
+
+  // 0,0  -- N = 1, S = 1
+  // 1,-1; 1,0; 1,1 -- N = 3, S = 4 (l+1)^2
+  // 2,-2; 2,-1; 2,0; 2,1; 2,2 -- N = 5, S = 9 (l+1)^2
+  // 3,-3; 3,-2; 3,-1; 3,0; 3,1; 3,2, 3,3 -- N = 7, S = 16 (l+1)^2
+
+  VSHterms *terms = NULL;
+  ALLOCATE (terms, VSHterms, 1);
+  terms->lmax = lmax;
+  terms->Nterms = SQ(lmax+1);
+
+  ALLOCATE (terms->dR_B, double, terms->Nterms);
+  ALLOCATE (terms->dR_E, double, terms->Nterms);
+  ALLOCATE (terms->dD_B, double, terms->Nterms);
+  ALLOCATE (terms->dD_E, double, terms->Nterms);
+  ALLOCATE (terms->l,    int,    terms->Nterms);
+  ALLOCATE (terms->m,    int,    terms->Nterms);
+  
+  int l, m;
+  int n = 0;
+  terms->l[n] = 0;
+  terms->m[n] = 0;
+  n++;
+  for (l = 1; l <= terms->lmax; l++) {
+    for (m = -l; m <= l; m++) {
+      terms->l[n] = l;
+      terms->m[n] = m;
+      n++;
+    }
+  }
+
+  return terms;
+}
+
+void VSHtermsFree (VSHterms *terms) {
+
+  if (!terms) return;
+
+  free (terms->dR_B);
+  free (terms->dR_E);
+  free (terms->dD_B);
+  free (terms->dD_E);
+  free (terms->l);
+  free (terms->m);
+  
+  free (terms);
+
+  return;
+}
+
+void VSHtermsForRD (VSHterms *terms, double R, double D) {
+
+  // given a point on the sky R,D
+
+  // we are calculating 2 terms: (dR,dD) = (dR^B + dR^E, dD^B + dD^E)
+
+  // ^E terms are the 'electric' component, ^B are the 'magnetic' component
+
+  // dR^B = \sum_{l,m} b_{l,m} dR^B_{l,m} (l,m,R,D) 
+  // dD^B = \sum_{l,m} b_{l,m} dD^B_{l,m} (l,m,R,D)
+
+  // dR^E = \sum_{l,m} e_{l,m} dR^E_{l,m} (l,m,R,D) 
+  // dD^E = \sum_{l,m} e_{l,m} dD^E_{l,m} (l,m,R,D)
+  
+  // dR^E_{l,m} is Valeri's Elm, item[0], dD^E_{l,m} is Valeri's Elm, item[1]
+  // dR^B_{l,m} is Valeri's Blm, item[0], dD^B_{l,m} is Valeri's Blm, item[1]
+
+  // NOTE : as implemented by Valeri, the coeffs b_{l,m} are the same for both R and D
+  // components; ditto for e_{l,m}
+
+  // there are thus 4 X_l,m terms that need to be generated, for dR^B, dR^E, dD^B, dD^E.
+  // Valeri calls these S_lm_lat and S_lm_lon
+
+  // dR^B_{l,m} = +S^D_{l,m}(l,m,R,D)
+  // dD^B_{l,m} = -S^R_{l,m}(l,m,R,D) / cos(D)
+
+  // dR^E_{l,m} = +S^R_{l,m}(l,m,R,D) / cos(D)
+  // dD^E_{l,m} = +S^D_{l,m}(l,m,R,D)
+
+  // S^R_{l,m} == Slm_lng
+  // (m >  0) : -m S_{l,m}(l,-m,R,D) 
+  // (m <= 0) : -m S_{l,m}(l,-m,R,D)
+  // NOTE: these are in fact the same...
+  // NOTE: sending in -m means we get sin() for (m > 0) and cos() for (m <= 0)
+
+  // S_{l,m}(l,m,R,D) :
+  // aP_{l,m} = gsl_Plm(l,|m|,cos(90-D))
+
+  // (m >  0) : S_{l,m} = aP_{l,m}*cos(m*R)    = gsl_Plm(l,|m|,cos(90-D))*cos(|m|*R)  [cos(x) == cos(-x)]
+  // (m <  0) : S_{l,m} = aP_{l,m}*sin(|m|*R)  = gsl_Plm(l,|m|,cos(90-D))*sin(|m|*R)
+  // (m == 0) : S_{l,m} = aP_{l,m}             = gsl_Plm(l,|m|,cos(90-D))
+
+  // S^R_{l,m} = -m S_{l,m}(l,-m,R,D)
+  // (m >  0) : S^R_{l,m} = -m aP_lm*sin(|m|*R)
+  // (m <  0) : S^R_{l,m} = -m aP_lm*cos(|m|*R)
+  // (m == 0) : S^R_{l,m} = 0
+
+  // S_^D_{l,m} == Slm_lat:
+  //
+  // aP_{l,  m}(D) = gsl_Plm(l,  |m|,cos(90-D))
+  // aP_{l+1,m}(D) = gsl_Plm(l+1,|m|,cos(90-D))
+
+  // dPlm_dD = ((l+1)*sin(D)*aP_{l,m} - (l+1-|m|)*aP_{l+1,m}) / cos(D)
+  // dSlm_dD
+  // (m == 0) : dPlm_dD
+  // (m > 0) : dPlm_dD*cos( m *R)
+  // (m < 0) : dPlm_dD*sin(|m|*R)
+
+  double Rrad = R * RAD_DEG;
+  double Drad = D * RAD_DEG;
+
+  int l, m;
+
+  // Vlm(l=0) = 0, so assign directly (we ignore for fitting)
+  int n = 0;
+  terms->dR_B[n] = 0.0;
+  terms->dD_B[n] = 0.0;
+  terms->dR_E[n] = 0.0;
+  terms->dD_E[n] = 0.0;
+  n++;
+
+  for (l = 1; l <= terms->lmax; l++) {
+
+    for (m = -l; m <= l; m++) {
+    
+      int m_abs = abs(m);
+      double aP_lm = gsl_sf_legendre_sphPlm(l, m_abs, cos(M_PI/2 - Drad)); // alt-P_lm = P_lm (l, |m|, cos(90 - D))
+
+      // note that for m == 0, S_R_lm -> 0 (due to m*)
+      double S_R_lm = (m > 0) ? -m * aP_lm * sin (m_abs * Rrad) : -m * aP_lm * cos (m_abs * Rrad) ; 
+      
+      // double aP_lp1m = gsl_sf_legendre_Plm(l + 1, m_abs, cos(M_PI/2 - Drad));
+      double aP_lp1m = gsl_sf_legendre_sphPlm(l + 1, m_abs, cos(M_PI/2 - Drad));
+      // reuse for aP_lm above?
+      
+      double dPlm_dD = ((l+1) * sin(Drad) * aP_lm - (l+1-m_abs)*aP_lp1m) / cos(Drad);
+      // XXX : this diverges @ D = 90.0??
+      // this is derived from the recursion relation:
+      // (1 - x^2) (d/dx) P_l^m(x) = (l + 1) x P_l^m(x) - (l - m + 1) P^m_{l+1}
+      // x = cos \theta = sin D
+      // (1 - x^2) = sin^2 \theta = cos^2 D
+      // (d/d\theta) P_l^m(x) = [(d/dx) P_l^m(x)] [dx/d\theta]
+      // dx/d\theta = d cos\theta / d\theta = -sin \theta = -cos D [d/dD changes the sign]
+
+      // (d/d\theta) P^m_l (x) = ((l + 1) sin D P_l^m(x) - (l - m + 1) P^m_{l+1}) / cos D
+
+      double S_D_lm = (m < 0) ? dPlm_dD * sin(m_abs * Rrad) : dPlm_dD * cos(m_abs * Rrad);
+  
+      terms->dR_B[n] = +S_D_lm;
+      terms->dD_B[n] = -S_R_lm / cos(Drad);
+      terms->dR_E[n] = +S_R_lm / cos(Drad);
+      terms->dD_E[n] = +S_D_lm;
+
+      // fprintf (stderr, "%d : %d,%d : %f %f : %f %f %f %f\n", n, l, m, S_R_lm, S_D_lm, terms->dR_B[n], terms->dR_E[n], terms->dD_B[n], terms->dD_E[n]);
+      // fprintf (stderr, "%d : %d,%d : %f %f %f %f\n", n-1, l, m, terms->dR_B[n], terms->dR_E[n], terms->dD_B[n], terms->dD_E[n]);
+      n++;
+    }
+  }
+}
+/** I can confirm that these terms match those generated by V. Makarov's program **/
+
+void VSHtermsForLM (VSHterms *terms, double R, double D, int l, int m) {
+
+  double Rrad = R * RAD_DEG;
+  double Drad = D * RAD_DEG;
+
+  int m_abs = abs(m);
+  double aP_lm = gsl_sf_legendre_Plm(l, m_abs, cos(M_PI/2 - Drad)); // alt-P_lm = P_lm (l, |m|, cos(90 - D))
+
+  // note that for m == 0, S_R_lm -> 0 (due to m*)
+  double S_R_lm = (m > 0) ? -m * aP_lm * sin (m_abs * Rrad) : -m * aP_lm * cos (m_abs * Rrad) ; 
+      
+  double aP_lp1m = gsl_sf_legendre_Plm(l + 1, m_abs, cos(M_PI/2 - Drad));
+  // reuse for aP_lm above?
+      
+  double dPlm_dD = ((l+1) * sin(Drad) * aP_lm - (l+1-m_abs)*aP_lp1m) / cos(Drad);
+  // XXX : this diverges @ D = 90.0??
+
+  double S_D_lm = (m < 0) ? dPlm_dD * sin(m_abs * Rrad) : dPlm_dD * cos(m_abs * Rrad);
+  
+  terms->dR_B[0] = +S_D_lm;
+  terms->dD_B[0] = -S_R_lm / cos(Drad);
+  terms->dR_E[0] = +S_R_lm / cos(Drad);
+  terms->dD_E[0] = +S_D_lm;
+}
Index: /trunk/Ohana/src/libohana/src/vstats.c
===================================================================
--- /trunk/Ohana/src/libohana/src/vstats.c	(revision 37807)
+++ /trunk/Ohana/src/libohana/src/vstats.c	(revision 37807)
@@ -0,0 +1,406 @@
+# include <ohana.h>
+
+int vstats_setmode (VStatsType *stats, char *mode) {
+
+  stats->statmode = VSTATS_NONE;
+  if (!strcmp (mode, "MEAN"))             { stats->statmode = VSTATS_MEAN; return TRUE; }
+  if (!strcmp (mode, "MEDIAN"))           { stats->statmode = VSTATS_MEDIAN; return TRUE; }
+  if (!strcmp (mode, "WT_MEAN"))          { stats->statmode = VSTATS_WT_MEAN; return TRUE; }
+  if (!strcmp (mode, "INNER_MEAN"))       { stats->statmode = VSTATS_INNER_MEAN; return TRUE; }
+  if (!strcmp (mode, "INNER_WTMEAN"))     { stats->statmode = VSTATS_INNER_WTMEAN; return TRUE; }
+  if (!strcmp (mode, "CHI_INNER_MEAN"))   { stats->statmode = VSTATS_CHI_INNER_MEAN; return TRUE; }
+  if (!strcmp (mode, "CHI_INNER_WTMEAN")) { stats->statmode = VSTATS_CHI_INNER_WTMEAN; return TRUE; }
+
+  push_error ("ERROR: invalid stats mode");
+  return FALSE;
+}
+
+// NOTE: this function resorts the input vectors.  if this is not what you want, you need
+// to either pass a vector copy or save the sequence
+int vstats_getstats (double *value, double *dvalue, double *weight, int N, VStatsType *stats) {
+  
+  int i, ks, ke;
+  double Mo, dMo, M, dM, Nm, X2, dS, R, W, *chi;
+
+  myAssert (stats->statmode != VSTATS_NONE, "programming error, liststats mode not set");
+
+  ke = ks = dMo = 0;
+
+  stats[0].Nmeas   = N;
+  stats[0].mean    = NAN;
+  stats[0].median  = NAN;
+  stats[0].sigma   = NAN;
+  stats[0].error   = NAN;
+  stats[0].chisq   = NAN;
+  stats[0].min     = NAN;
+  stats[0].max     = NAN;
+  stats[0].Upper80 = NAN;
+  stats[0].Lower20 = NAN;
+  stats[0].total   = NAN;
+
+  // take care of some ridiculous special cases
+  if (N < 1) return FALSE;
+
+  if (N == 1) {
+    stats[0].mean    = value[0];
+    stats[0].median  = value[0];
+    stats[0].sigma   = NAN;
+    stats[0].error   = dvalue ? dvalue[0] : NAN;
+    stats[0].chisq   = NAN;
+    stats[0].min     = value[0];
+    stats[0].max     = value[0];
+    stats[0].Upper80 = value[0];
+    stats[0].Lower20 = value[0];
+    stats[0].total   = value[0];
+    return TRUE;
+  }
+
+  // for less than 5 entries, this is equivalent to max,min
+  int N80 = MIN (N-1, 0.8*N);
+  int N20 = MAX (  0, 0.2*N);
+
+  if (weight && dvalue) {
+    dsortthree (value, dvalue, weight, N);
+  } else {
+    if (dvalue) {
+      dsortpair (value, dvalue, N);
+    } else if (weight) {
+      dsortpair (value, weight, N);
+    } else {
+      dsort (value, N);
+    }
+  }
+
+  // these values do not depend on the errors or weighting scheme
+  if (N % 2) {
+    stats[0].median  = value[(int)(N/2)];
+  } else {
+    stats[0].median  = 0.5*(value[(int)(N/2)] + value[(int)(N/2) - 1]);
+  }
+  stats[0].min     = value[0];
+  stats[0].max     = value[N-1];
+  stats[0].Upper80 = value[N80];
+  stats[0].Lower20 = value[N20];
+
+  switch (stats->statmode) {
+    case VSTATS_MEDIAN:
+      ks = 0;
+      ke = N;
+      Mo = stats[0].median;
+      Nm = N;
+      goto chisq;
+      break;
+    case VSTATS_MEAN:
+    case VSTATS_WT_MEAN:
+      ks = 0;
+      ke = N;
+      break;
+    case VSTATS_INNER_MEAN:
+    case VSTATS_INNER_WTMEAN:
+    case VSTATS_CHI_INNER_MEAN:
+    case VSTATS_CHI_INNER_WTMEAN:
+      ks = 0.25*N + 0.50;
+      ke = 0.75*N + 0.25;
+      if (N <= 3) {
+	ks = 0;
+	ke = N;
+      }
+      break;
+    case VSTATS_NONE:
+      myAbort ("undefined stats");
+  }    
+
+  // for these two modes, I need a vector of the chi-square contribution
+  // I'm actually just using chisq to get the correct sorting order
+  if ((stats->statmode == VSTATS_CHI_INNER_MEAN) || (stats->statmode == VSTATS_CHI_INNER_WTMEAN)) {
+    if (!dvalue) myAbort ("invalid combination: Chisq-based mean, no dvalue provided");
+    ALLOCATE (chi, double, N);
+    for (i = 0; i < N; i++) {
+      chi[i] = (value[i] - stats[0].median) / dvalue[i];
+    }
+    if (weight) {
+      dsortfour (chi, value, dvalue, weight, N);
+    } else {
+      dsortthree (chi, value, dvalue, N);
+    }
+    free (chi);
+  }
+
+  int WeightedMean = FALSE;
+  WeightedMean |= (stats->statmode == VSTATS_WT_MEAN);
+  WeightedMean |= (stats->statmode == VSTATS_INNER_WTMEAN);
+  WeightedMean |= (stats->statmode == VSTATS_CHI_INNER_WTMEAN);
+
+  if (WeightedMean && !dvalue) myAbort ("invalid combination: weighted mean, no dvalue provided");
+
+  /* calculate stats based on the desired weighting scheme */
+  M = dM = Nm = W = R = 0;
+  if (weight) {
+    // the weight value is multiplied by whichever nominal weighting scheme is provided 
+    // thus the user should set weight to 1.0 for nominal weight, and 100 for heavy weight (or so)
+    // and 0.01 for under-weight
+    if (WeightedMean) {
+      for (i = ks; i < ke; i++) {
+	M  += value[i] * weight[i] / SQ(dvalue[i]);
+	W  +=            weight[i] / SQ(dvalue[i]);
+	dM += SQ (weight[i] / dvalue[i]);
+	R  += weight[i] / SQ(dvalue[i]);
+	Nm += 1.0;  
+      }	
+      Mo  = M / W;
+      dMo = sqrt (dM) / R;
+    } else {
+      for (i = ks; i < ke; i++) {
+	M  += value[i] * weight[i];
+	W  +=            weight[i];
+	dM += dvalue ? SQ (weight[i] * dvalue[i]) : 0.0;
+	R  += weight[i];
+	Nm += 1.0;  
+      }	
+      Mo  = M / W;
+      dMo = dvalue ? sqrt (dM) / R : NAN;
+    }
+  } else {
+    // NULL weight vector is supplied, revert to standard form (above reverts if weight[i] == 1)
+    if (WeightedMean) {
+      // weighted by inverse-variance
+      for (i = ks; i < ke; i++) {
+	M   += value[i] / SQ (dvalue[i]);
+	dM  += 1.0 / SQ (dvalue[i]);
+	Nm  += 1.0;  
+      }	
+      Mo = M / dM;
+      dMo = sqrt (1.0 / dM);
+    } else {
+      // pure un-weighted
+      for (i = ks; i < ke; i++) {
+	M   += value[i];
+	dM  += dvalue ? SQ (dvalue[i]) : 0.0;
+	Nm  += 1.0;  
+      }	
+      Mo = M / Nm;
+      dMo = dvalue ? sqrt (dM) / Nm : NAN;
+    }
+  }
+
+ chisq:
+  /* find sigma and chisq */
+  X2 = dS = 0;
+  for (i = ks; i < ke; i++) {
+    M  = SQ (value[i] - Mo);
+    dM = dvalue ? SQ (dvalue[i]) : 1.0;
+    X2 += M / dM;
+    dS += M;
+  }
+  X2 = dvalue ? X2 / (Nm - 1) : NAN;
+  dS = sqrt (dS / (Nm - 1));
+
+  stats[0].mean  = Mo;
+  stats[0].Nmeas = Nm;
+  stats[0].chisq = X2;
+  stats[0].sigma = dS;
+  stats[0].error = dMo;
+
+  return (TRUE);
+}
+
+// NOTE: this function resorts the input vectors.  if this is not what you want, you need
+// to either pass a vector copy or save the sequence
+int vstats_getstats_f (float *value, float *dvalue, float *weight, int N, VStatsType *stats) {
+  
+  int i, ks, ke;
+  float Mo, dMo, M, dM, Nm, X2, dS, R, W, *chi;
+
+  myAssert (stats->statmode != VSTATS_NONE, "programming error, liststats mode not set");
+
+  ke = ks = dMo = 0;
+
+  stats[0].Nmeas   = N;
+  stats[0].mean    = NAN;
+  stats[0].median  = NAN;
+  stats[0].sigma   = NAN;
+  stats[0].error   = NAN;
+  stats[0].chisq   = NAN;
+  stats[0].min     = NAN;
+  stats[0].max     = NAN;
+  stats[0].Upper80 = NAN;
+  stats[0].Lower20 = NAN;
+  stats[0].total   = NAN;
+
+  // take care of some ridiculous special cases
+  if (N < 1) return FALSE;
+
+  if (N == 1) {
+    stats[0].mean    = value[0];
+    stats[0].median  = value[0];
+    stats[0].sigma   = NAN;
+    stats[0].error   = dvalue ? dvalue[0] : NAN;
+    stats[0].chisq   = NAN;
+    stats[0].min     = value[0];
+    stats[0].max     = value[0];
+    stats[0].Upper80 = value[0];
+    stats[0].Lower20 = value[0];
+    stats[0].total   = value[0];
+    return TRUE;
+  }
+
+  // for less than 5 entries, this is equivalent to max,min
+  int N80 = MIN (N-1, 0.8*N);
+  int N20 = MAX (  0, 0.2*N);
+
+  if (weight && dvalue) {
+    fsortthree (value, dvalue, weight, N);
+  } else {
+    if (dvalue) {
+      fsortpair (value, dvalue, N);
+    } else if (weight) {
+      fsortpair (value, weight, N);
+    } else {
+      fsort (value, N);
+    }
+  }
+
+  // these values do not depend on the errors or weighting scheme
+  if (N % 2) {
+    stats[0].median  = value[(int)(N/2)];
+  } else {
+    stats[0].median  = 0.5*(value[(int)(N/2)] + value[(int)(N/2) - 1]);
+  }
+  stats[0].min     = value[0];
+  stats[0].max     = value[N-1];
+  stats[0].Upper80 = value[N80];
+  stats[0].Lower20 = value[N20];
+
+  switch (stats->statmode) {
+    case VSTATS_MEDIAN:
+      ks = 0;
+      ke = N;
+      Mo = stats[0].median;
+      Nm = N;
+      goto chisq;
+      break;
+    case VSTATS_MEAN:
+    case VSTATS_WT_MEAN:
+      ks = 0;
+      ke = N;
+      break;
+    case VSTATS_INNER_MEAN:
+    case VSTATS_INNER_WTMEAN:
+    case VSTATS_CHI_INNER_MEAN:
+    case VSTATS_CHI_INNER_WTMEAN:
+      ks = 0.25*N + 0.50;
+      ke = 0.75*N + 0.25;
+      if (N <= 3) {
+	ks = 0;
+	ke = N;
+      }
+      break;
+    case VSTATS_NONE:
+      myAbort ("undefined stats");
+  }    
+
+  // for these two modes, I need a vector of the chi-square contribution
+  // I'm actually just using chisq to get the correct sorting order
+  if ((stats->statmode == VSTATS_CHI_INNER_MEAN) || (stats->statmode == VSTATS_CHI_INNER_WTMEAN)) {
+    if (!dvalue) myAbort ("invalid combination: Chisq-based mean, no dvalue provided");
+    ALLOCATE (chi, float, N);
+    for (i = 0; i < N; i++) {
+      chi[i] = (value[i] - stats[0].median) / dvalue[i];
+    }
+    if (weight) {
+      fsortfour (chi, value, dvalue, weight, N);
+    } else {
+      fsortthree (chi, value, dvalue, N);
+    }
+    free (chi);
+  }
+
+  int WeightedMean = FALSE;
+  WeightedMean |= (stats->statmode == VSTATS_WT_MEAN);
+  WeightedMean |= (stats->statmode == VSTATS_INNER_WTMEAN);
+  WeightedMean |= (stats->statmode == VSTATS_CHI_INNER_WTMEAN);
+
+  if (WeightedMean && !dvalue) myAbort ("invalid combination: weighted mean, no dvalue provided");
+
+  /* calculate stats based on the desired weighting scheme */
+  M = dM = Nm = W = R = 0;
+  if (weight) {
+    // the weight value is multiplied by whichever nominal weighting scheme is provided 
+    // thus the user should set weight to 1.0 for nominal weight, and 100 for heavy weight (or so)
+    // and 0.01 for under-weight
+    if (WeightedMean) {
+      for (i = ks; i < ke; i++) {
+	M  += value[i] * weight[i] / SQ(dvalue[i]);
+	W  +=            weight[i] / SQ(dvalue[i]);
+	dM += SQ (weight[i] / dvalue[i]);
+	R  += weight[i] / SQ(dvalue[i]);
+	Nm += 1.0;  
+      }	
+      Mo  = M / W;
+      dMo = sqrt (dM) / R;
+    } else {
+      for (i = ks; i < ke; i++) {
+	M  += value[i] * weight[i];
+	W  +=            weight[i];
+	dM += dvalue ? SQ (weight[i] * dvalue[i]) : 0.0;
+	R  += weight[i];
+	Nm += 1.0;  
+      }	
+      Mo  = M / W;
+      dMo = dvalue ? sqrt (dM) / R : NAN;
+    }
+  } else {
+    // NULL weight vector is supplied, revert to standard form (above reverts if weight[i] == 1)
+    if (WeightedMean) {
+      // weighted by inverse-variance
+      for (i = ks; i < ke; i++) {
+	M   += value[i] / SQ (dvalue[i]);
+	dM  += 1.0 / SQ (dvalue[i]);
+	Nm  += 1.0;  
+      }	
+      Mo = M / dM;
+      dMo = sqrt (1.0 / dM);
+    } else {
+      // pure un-weighted
+      for (i = ks; i < ke; i++) {
+	M   += value[i];
+	dM  += dvalue ? SQ (dvalue[i]) : 0.0;
+	Nm  += 1.0;  
+      }	
+      Mo = M / Nm;
+      dMo = dvalue ? sqrt (dM) / Nm : NAN;
+    }
+  }
+
+ chisq:
+  /* find sigma and chisq */
+  X2 = dS = 0;
+  for (i = ks; i < ke; i++) {
+    M  = SQ (value[i] - Mo);
+    dM = dvalue ? SQ (dvalue[i]) : 1.0;
+    X2 += M / dM;
+    dS += M;
+  }
+  X2 = dvalue ? X2 / (Nm - 1) : NAN;
+  dS = sqrt (dS / (Nm - 1));
+
+  stats[0].mean  = Mo;
+  stats[0].Nmeas = Nm;
+  stats[0].chisq = X2;
+  stats[0].sigma = dS;
+  stats[0].error = dMo;
+
+  return (TRUE);
+}
+
+// we could define the weight to be the only scale factor:
+// \mu      = \sum (value_i * weight_i) / \sum (weight_i)
+// \sigma^2 = (1/R) \sum (weight_i^2 \sigma_i^2) 
+// R = \sum (weight_i^2)
+
+// or, we could define the weight to be a scale factor times the inverse error:
+// \mu      = \sum (value_i * weight_i / sigma_i) / \sum (weight_i)
+// \sigma^2 = (1/R) \sum (weight_i^2 \sigma_i^2) 
+// R = \sum (weight_i^2)
+
+// 
Index: /trunk/Ohana/src/markrock/src/gcatstats.c
===================================================================
--- /trunk/Ohana/src/markrock/src/gcatstats.c	(revision 37806)
+++ /trunk/Ohana/src/markrock/src/gcatstats.c	(revision 37807)
@@ -34,12 +34,9 @@
   /* project onto rectilinear grid with 1 arcsec pixels, sort by X */
   /* reference for coords is center of field  */
+  InitCoords (&catstats[0].coords, "DEC--TAN");
   catstats[0].coords.crval1 = RaCenter;
   catstats[0].coords.crval2 = DecCenter;
-  catstats[0].coords.crpix1 = catstats[0].coords.crpix2 = 0.0;
   catstats[0].coords.cdelt1 = catstats[0].coords.cdelt2 = 1.0 / 3600.0;
-  catstats[0].coords.pc1_1 = catstats[0].coords.pc2_2 = 1.0;
-  catstats[0].coords.pc1_2 = catstats[0].coords.pc2_1 = 0.0;
-  strcpy (catstats[0].coords.ctype, "DEC--TAN");
-
+  
   X1 = catstats[0].X;
   Y1 = catstats[0].Y;
Index: /trunk/Ohana/src/markstar/src/gcatstats.c
===================================================================
--- /trunk/Ohana/src/markstar/src/gcatstats.c	(revision 37806)
+++ /trunk/Ohana/src/markstar/src/gcatstats.c	(revision 37807)
@@ -44,11 +44,8 @@
   /* project onto rectilinear grid with 1 arcsec pixels, sort by X */
   /* reference for coords is center of field  */
+  InitCoords (&catstats[0].coords, "DEC--TAN");
   catstats[0].coords.crval1 = RaCenter;
   catstats[0].coords.crval2 = DecCenter;
-  catstats[0].coords.crpix1 = catstats[0].coords.crpix2 = 0.0;
   catstats[0].coords.cdelt1 = catstats[0].coords.cdelt2 = 1.0 / 3600.0;
-  catstats[0].coords.pc1_1 = catstats[0].coords.pc2_2 = 1.0;
-  catstats[0].coords.pc1_2 = catstats[0].coords.pc2_1 = 0.0;
-  strcpy (catstats[0].coords.ctype, "DEC--TAN");
 
   X1 = catstats[0].X;
Index: /trunk/Ohana/src/mosastro/src/chips.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/chips.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/chips.c	(revision 37807)
@@ -21,9 +21,5 @@
 
     /* bore site center guess */
-    strcpy (chip[i].map.ctype, "DEC--PLY");
-    chip[i].map.crpix1 = 0.0;
-    chip[i].map.crpix2 = 0.0;
-    chip[i].map.cdelt1 = 1.0;
-    chip[i].map.cdelt2 = 1.0;
+    InitCoords (&chip[i].map, "DEC--PLY");
 
     /* find (L,M) coords of reference pixel (0,0) */
Index: /trunk/Ohana/src/mosastro/src/fakefield.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/fakefield.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/fakefield.c	(revision 37807)
@@ -22,7 +22,5 @@
 
   /* bore site center guess */
-  strcpy (field.project.ctype, "DEC--TAN");
-  field.project.crpix1 = 0;
-  field.project.crpix2 = 0;
+  InitCoords (&field.project, "DEC--TAN");
   
   /* set TP plate scale */
@@ -30,31 +28,6 @@
   field.project.cdelt2 = 1.0/3600.0;
 
-  /** allow guess at field rotation?? **/
-  field.project.pc1_1  = 1;
-  field.project.pc2_2  = 1;
-  field.project.pc1_2  = 0;
-  field.project.pc2_1  = 0;
-  field.project.Npolyterms = 1;
-
   /** distort only has power in polyterms **/
-  strcpy (field.distort.ctype, "DEC--PLY");
-  field.distort.crval1 = 0.0;
-  field.distort.crval2 = 0.0;
-  field.distort.crpix1 = 0.0;
-  field.distort.crpix2 = 0.0;
-  field.distort.cdelt1 = 1.0;
-  field.distort.cdelt2 = 1.0;
-  
-  field.distort.pc1_1  = 1;
-  field.distort.pc2_2  = 1;
-  field.distort.pc1_2  = 0;
-  field.distort.pc2_1  = 0;
-
-  /* how do we handle renormalization? (fixed at 1000 pixels??) */
-  field.distort.Npolyterms = 3;
-  for (i = 0; i < 7; i++) {
-    field.distort.polyterms[i][0] = 0;
-    field.distort.polyterms[i][1] = 0;
-  }
+  InitCoords (&field.distort, "DEC--PLY");
 
   return (1);
Index: /trunk/Ohana/src/mosastro/src/field.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/field.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/field.c	(revision 37807)
@@ -12,5 +12,5 @@
 
   /* bore site center guess */
-  strcpy (field.project.ctype, "DEC--TAN");
+  InitCoords (&field.project, "DEC--TAN");
 
   # if (PSASTRO_MODE)
@@ -23,7 +23,4 @@
   # endif
 
-  field.project.crpix1 = 0;
-  field.project.crpix2 = 0;
-  
   /* measure average plate scale - would be better using parabolic min... */
   field.project.cdelt1 = 0;
@@ -47,33 +44,6 @@
 	   3600*field.project.cdelt1, 3600*field.project.cdelt2);
 
-  /** allow guess at field rotation?? **/
-  field.project.pc1_1  = 1;
-  field.project.pc2_2  = 1;
-  field.project.pc1_2  = 0;
-  field.project.pc2_1  = 0;
-  field.project.Npolyterms = 1;
-
   /* bore site center guess */
-  strcpy (field.distort.ctype, "DEC--PLY");
-  field.distort.crval1 = 0.0;
-  field.distort.crval2 = 0.0;
-  field.distort.crpix1 = 0.0;
-  field.distort.crpix2 = 0.0;
-  field.distort.cdelt1 = 1.0;
-  field.distort.cdelt2 = 1.0;
-  
-  /** allow guess at field rotation?? **/
-  field.distort.pc1_1  = 1;
-  field.distort.pc2_2  = 1;
-  field.distort.pc1_2  = 0;
-  field.distort.pc2_1  = 0;
-
-  /* allow 2nd and 3rd order? */
-  /* how do we handle renormalization? (fixed at 1000 pixels??) */
-  field.distort.Npolyterms = 1;
-  for (i = 0; i < 7; i++) {
-    field.distort.polyterms[i][0] = 0;
-    field.distort.polyterms[i][1] = 0;
-  }
+  InitCoords (&field.distort, "DEC--PLY");
   return (TRUE);
 }
@@ -138,5 +108,4 @@
 int load_field (char *filename) {
 
-  int i;
   Coords coords;
   Header header;
@@ -156,9 +125,7 @@
 
   /* bore site center guess */
-  strcpy (field.project.ctype, "DEC--TAN");
+  InitCoords (&field.project, "DEC--TAN");
   field.project.crval1 = 0.5*(field.Rmin + field.Rmax);
   field.project.crval2 = 0.5*(field.Dmin + field.Dmax);
-  field.project.crpix1 = 0;
-  field.project.crpix2 = 0;
   
   /* measure average plate scale - would be better using parabolic min... */
@@ -174,25 +141,6 @@
 
   /* bore site center guess */
-  strcpy (field.distort.ctype, "DEC--PLY");
-  field.distort.crval1 = 0.0;
-  field.distort.crval2 = 0.0;
-  field.distort.crpix1 = 0.0;
-  field.distort.crpix2 = 0.0;
-  field.distort.cdelt1 = 1.0;
-  field.distort.cdelt2 = 1.0;
-  
-  /** allow guess at field rotation?? **/
-  field.distort.pc1_1  = 1;
-  field.distort.pc2_2  = 1;
-  field.distort.pc1_2  = 0;
-  field.distort.pc2_1  = 0;
+  InitCoords (&field.distort, "DEC--PLY");
 
-  /* allow 2nd and 3rd order? */
-  /* how do we handle renormalization? (fixed at 1000 pixels??) */
-  field.distort.Npolyterms = coords.Npolyterms;
-  for (i = 0; i < 7; i++) {
-    field.distort.polyterms[i][0] = coords.polyterms[i][0];
-    field.distort.polyterms[i][1] = coords.polyterms[i][1];
-  }
   return (TRUE);
 }
Index: /trunk/Ohana/src/mosastro/src/fitpoly.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/fitpoly.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/fitpoly.c	(revision 37807)
@@ -130,4 +130,6 @@
   double R;
 
+  InitCoords (coords, "DEC--PLY");
+
   /* update the higher order terms */
   if (NORDER > 1) {
@@ -166,5 +168,4 @@
 
   coords[0].Npolyterms = NORDER;
-  strcpy (coords[0].ctype, "DEC--PLY");
 }
 
Index: /trunk/Ohana/src/mosastro/src/mkobs.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/mkobs.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/mkobs.c	(revision 37807)
@@ -104,5 +104,5 @@
 
       /* Chip-Sky terms */
-      strcpy (coords.ctype, "DEC--WRP");
+      InitCoords (&coords, "DEC--WRP");
       coords.crval1 = Ro;
       coords.crval2 = Do;
@@ -124,10 +124,4 @@
       }
 
-      coords.pc1_1  = 1;
-      coords.pc2_2  = 1;
-      coords.pc1_2  = 0;
-      coords.pc2_1  = 0;
-      coords.Npolyterms = 1;
-
       header = mkheader (2*dX, 2*dY, Nstars, &coords);
 
Index: /trunk/Ohana/src/mosastro/src/mkstandards.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/mkstandards.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/mkstandards.c	(revision 37807)
@@ -32,18 +32,11 @@
 
   /* bore site center guess */
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
   coords.crval1 = atof(argv[1]);
   coords.crval2 = atof(argv[2]);
   coords.crpix1 = dX;
   coords.crpix2 = dX;
-  
   coords.cdelt1 = 1/3600.0;
   coords.cdelt2 = 1/3600.0;
-
-  coords.pc1_1  = 1;
-  coords.pc2_2  = 1;
-  coords.pc1_2  = 0;
-  coords.pc2_1  = 0;
-  coords.Npolyterms = 1;
 
   if (Random) {
Index: /trunk/Ohana/src/mosastro/src/testcoords.c
===================================================================
--- /trunk/Ohana/src/mosastro/src/testcoords.c	(revision 37806)
+++ /trunk/Ohana/src/mosastro/src/testcoords.c	(revision 37807)
@@ -53,5 +53,6 @@
     exit (1);
   }
-  RegisterMosaic (&field.project);
+
+  chip[0].map.mosaic = &field.project;
   strcpy (chip[0].map.ctype, "DEC--WRP");
   for (i = 0; i < chip[0].Nmatch; i++) {
Index: /trunk/Ohana/src/opihi/cmd.astro/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/Makefile	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/Makefile	(revision 37807)
@@ -71,4 +71,6 @@
 $(SRC)/star.$(ARCH).o		   \
 $(SRC)/transform.$(ARCH).o         \
+$(SRC)/vshimage.$(ARCH).o         \
+$(SRC)/shimage.$(ARCH).o         \
 $(SRC)/imsub.$(ARCH).o		   \
 $(SRC)/imfit.$(ARCH).o		   \
Index: /trunk/Ohana/src/opihi/cmd.astro/cdensify.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/cdensify.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/cdensify.c	(revision 37807)
@@ -97,11 +97,5 @@
   // generate the PSF in a local tangent plane
   Coords coords;
-  coords.crpix1 = coords.crpix2 = 0.0;
-  coords.crval1 = coords.crval2 = 0.0;
-  coords.cdelt1 = coords.cdelt2 = 1.0;
-  coords.pc1_1  = coords.pc2_2  = 1.0;
-  coords.pc1_2  = coords.pc2_1  = 0.0;
-  coords.Npolyterms = 0;
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
 
   r = vr[0].elements.Flt;
Index: /trunk/Ohana/src/opihi/cmd.astro/coords.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/coords.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/coords.c	(revision 37807)
@@ -71,5 +71,5 @@
     if ((mosbuffer = SelectBuffer (MOSAIC, OLDBUFFER, TRUE)) == NULL) goto escape;
     GetCoords (&moscoords, &mosbuffer[0].header);
-    RegisterMosaic (&moscoords);
+    coords.mosaic = &moscoords;
   }
   
Index: /trunk/Ohana/src/opihi/cmd.astro/csystem.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/csystem.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/csystem.c	(revision 37807)
@@ -13,16 +13,24 @@
   if (argc != 5) goto syntax;
 
-  switch (argv[1][0]) {
-    case 'C': input = COORD_CELESTIAL; break;
-    case 'G': input = COORD_GALACTIC; break;
-    case 'E': input = COORD_ECLIPTIC; break;
-    default: goto syntax;
+  if (!strcmp(argv[1], "G2004")) {
+    input = COORD_GALACTIC_REID_2004;
+  } else {
+    switch (argv[1][0]) {
+      case 'C': input = COORD_CELESTIAL; break;
+      case 'G': input = COORD_GALACTIC; break;
+      case 'E': input = COORD_ECLIPTIC; break;
+      default: goto syntax;
+    }
   }
 
-  switch (argv[2][0]) {
-    case 'C': output = COORD_CELESTIAL; break;
-    case 'G': output = COORD_GALACTIC; break;
-    case 'E': output = COORD_ECLIPTIC; break;
-    default: goto syntax;
+  if (!strcmp(argv[2], "G2004")) {
+    output = COORD_GALACTIC_REID_2004;
+  } else {
+    switch (argv[2][0]) {
+      case 'C': output = COORD_CELESTIAL; break;
+      case 'G': output = COORD_GALACTIC; break;
+      case 'E': output = COORD_ECLIPTIC; break;
+      default: goto syntax;
+    }
   }
 
Index: /trunk/Ohana/src/opihi/cmd.astro/fitplx.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/fitplx.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/fitplx.c	(revision 37807)
@@ -104,13 +104,8 @@
   /* project coordinates to a plane centered on the object with units of arcsec */
   Coords coords;
+  InitCoords (&coords, "DEC--SIN");
   coords.crval1 = Rmean;
   coords.crval2 = Dmean;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   double *X, *Y, *t, *pX, *pY, *dX, *dY;
Index: /trunk/Ohana/src/opihi/cmd.astro/fitpm.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/fitpm.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/fitpm.c	(revision 37807)
@@ -100,13 +100,8 @@
   /* project coordinates to a plane centered on the object with units of arcsec */
   Coords coords;
+  InitCoords (&coords, "DEC--SIN");
   coords.crval1 = Rmean;
   coords.crval2 = Dmean;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   double *X, *Y, *t, *dX, *dY;
Index: /trunk/Ohana/src/opihi/cmd.astro/gauss.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/gauss.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/gauss.c	(revision 37807)
@@ -30,4 +30,11 @@
   if (!GetImageData (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   Nborder = 3;
Index: /trunk/Ohana/src/opihi/cmd.astro/getcoords.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/getcoords.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/getcoords.c	(revision 37807)
@@ -54,5 +54,5 @@
   if (!strcmp(&coords.ctype[4], "-WRP")) {
     if (MOSAIC == NULL) {
-      gprint (GP_ERR, "must supply mosaic for WRP coords\n");
+      gprint (GP_ERR, "must supply mosaic for WRP coords with -mosaic [buffer]\n");
       return (FALSE);
     }
@@ -65,5 +65,5 @@
     }
     GetCoords (&moscoords, &mosbuffer[0].header);
-    RegisterMosaic (&moscoords);
+    coords.mosaic = &moscoords;
   }
   
Index: /trunk/Ohana/src/opihi/cmd.astro/init.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/init.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/init.c	(revision 37807)
@@ -61,4 +61,6 @@
 int times                   PROTO((int, char **));
 int transform               PROTO((int, char **));
+int vshimage                PROTO((int, char **));
+int shimage                 PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -121,4 +123,6 @@
   {1, "star",        star,         "star stats at rough coords"},
   {1, "transform",   transform,    "geometric transformation of image"},
+  {1, "vshimage",    vshimage,     "generate images for vector spherical harmonic terms"},
+  {1, "shimage",     shimage,      "generate images for spherical harmonic terms"},
 }; 
 
Index: /trunk/Ohana/src/opihi/cmd.astro/objload.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/objload.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/objload.c	(revision 37807)
@@ -19,4 +19,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   Objtype = 0;
Index: /trunk/Ohana/src/opihi/cmd.astro/region.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/region.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.astro/region.c	(revision 37807)
@@ -17,4 +17,11 @@
   if (!GetGraph (&graphmode, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   NoClear = FALSE;
@@ -80,5 +87,6 @@
   if (!ohana_str_to_radec (&Ra, &Dec, argv[1], argv[2])) return (FALSE);
   Radius = atof (argv[3]);
-  strcpy (graphmode.coords.ctype, "DEC--TAN");
+  InitCoords (&graphmode.coords, "DEC--TAN");
+
   if (argc == 5) {
     if (!strcasecmp (argv[4], "TAN")) 
@@ -99,6 +107,11 @@
       strcpy (graphmode.coords.ctype, "DEC--PAR");
   }
+  
+  graphmode.coords.crval1 = Ra;
+  graphmode.coords.crval2 = Dec;
 
-  
+  graphmode.coords.pc1_1 = (graphmode.flipeast) ? -1 : 1;
+  graphmode.coords.pc2_2 = (graphmode.flipnorth) ? -1 : 1;
+
   /* ask kapa for coordinate limits, to get the right aspect ratio */
   KapaGetLimits (kapa, &dx, &dy);
@@ -132,14 +145,4 @@
   set_int_variable ("NORTH_UP", !graphmode.flipnorth);
 
-  graphmode.coords.pc1_1 = (graphmode.flipeast) ? -1 : 1;
-  graphmode.coords.pc2_2 = (graphmode.flipnorth) ? -1 : 1;
-
-  graphmode.coords.pc1_2 = graphmode.coords.pc2_1 = 0.0;
-  graphmode.coords.crval1 = Ra;
-  graphmode.coords.crval2 = Dec;
-  graphmode.coords.crpix1 = 0.0;
-  graphmode.coords.crpix2 = 0.0;
-  graphmode.coords.cdelt1 = graphmode.coords.cdelt2 = 1.0;
-
   if (!NoClear) KapaClearSections (kapa);
   KapaSetLimits (kapa, &graphmode);
Index: /trunk/Ohana/src/opihi/cmd.astro/shimage.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/shimage.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.astro/shimage.c	(revision 37807)
@@ -0,0 +1,64 @@
+# include "data.h"
+
+int shimage (int argc, char **argv) {
+  
+  int ix, iy;
+  Buffer *FrIm, *FiIm, *src;
+
+  if (argc != 6) {
+    gprint (GP_ERR, "USAGE: shimage (image) l m Fr Fi\n");
+    gprint (GP_ERR, "  set the Fr and Fi values for pixels in the given image for (l,m)\n");
+    return (FALSE);
+  }
+
+  if ((src = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+
+  Coords coords;
+  GetCoords (&coords, &src[0].header);
+  if (!strcmp(&coords.ctype[4], "-WRP")) {
+    fprintf (stderr, "mosaic astrometry not yet supported for vshimage\n");
+    return FALSE;
+  }
+
+  int l = atoi(argv[2]);
+  int m = atoi(argv[3]);
+
+  if ((FrIm = SelectBuffer (argv[4], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((FiIm = SelectBuffer (argv[5], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+
+  int Nx = src[0].header.Naxis[0];
+  int Ny = src[0].header.Naxis[1];
+
+  gfits_free_matrix (&FrIm[0].matrix); gfits_free_header (&FrIm[0].header); CreateBuffer (FrIm, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&FiIm[0].matrix); gfits_free_header (&FiIm[0].header); CreateBuffer (FiIm, Nx, Ny, -32, 1.0, 0.0);
+
+  // l=0 allocates space for a single value
+  SHterms *terms = SHtermsInit (0);
+
+  float *Fr = (float *) FrIm[0].matrix.buffer;
+  float *Fi = (float *) FiIm[0].matrix.buffer;
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+
+      int Npix = ix + iy*Nx;
+
+      double R, D;
+      int status = XY_to_RD (&R, &D, ix, iy, &coords);
+
+      if (!status) {
+	Fr[Npix] = NAN;
+	Fi[Npix] = NAN;
+	continue;
+      }
+      
+      SHtermsForLM (terms, R, D, l, m); 
+
+      Fr[Npix] = terms->Fr[0];
+      Fi[Npix] = terms->Fi[0];
+    }
+  }
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.astro/vshimage.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/vshimage.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.astro/vshimage.c	(revision 37807)
@@ -0,0 +1,74 @@
+# include "data.h"
+
+int vshimage (int argc, char **argv) {
+  
+  int ix, iy;
+  Buffer *Rbim, *Reim, *Dbim, *Deim, *src;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: vsh image l m dRb dRe dDb dDe\n");
+    gprint (GP_ERR, "  set the dRb, dRe, dDb, dDe values for pixels in the given image for (l,m)\n");
+    return (FALSE);
+  }
+
+  if ((src = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+
+  Coords coords;
+  GetCoords (&coords, &src[0].header);
+  if (!strcmp(&coords.ctype[4], "-WRP")) {
+    fprintf (stderr, "mosaic astrometry not yet supported for vshimage\n");
+    return FALSE;
+  }
+
+  int l = atoi(argv[2]);
+  int m = atoi(argv[3]);
+
+  if ((Rbim = SelectBuffer (argv[4], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((Reim = SelectBuffer (argv[5], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((Dbim = SelectBuffer (argv[6], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((Deim = SelectBuffer (argv[7], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+
+  int Nx = src[0].header.Naxis[0];
+  int Ny = src[0].header.Naxis[1];
+
+  gfits_free_matrix (&Rbim[0].matrix); gfits_free_header (&Rbim[0].header); CreateBuffer (Rbim, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&Reim[0].matrix); gfits_free_header (&Reim[0].header); CreateBuffer (Reim, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&Dbim[0].matrix); gfits_free_header (&Dbim[0].header); CreateBuffer (Dbim, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&Deim[0].matrix); gfits_free_header (&Deim[0].header); CreateBuffer (Deim, Nx, Ny, -32, 1.0, 0.0);
+
+  // l=0 allocates space for a single value
+  VSHterms *terms = VSHtermsInit (0);
+
+  float *Rb = (float *) Rbim[0].matrix.buffer;
+  float *Re = (float *) Reim[0].matrix.buffer;
+  float *Db = (float *) Dbim[0].matrix.buffer;
+  float *De = (float *) Deim[0].matrix.buffer;
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+
+      int Npix = ix + iy*Nx;
+
+      double R, D;
+      int status = XY_to_RD (&R, &D, ix, iy, &coords);
+
+      if (!status) {
+	Rb[Npix] = NAN;
+	Re[Npix] = NAN;
+	Db[Npix] = NAN;
+	De[Npix] = NAN;
+	continue;
+      }
+      
+      VSHtermsForLM (terms, R, D, l, m); 
+
+      Rb[Npix] = terms->dR_B[0];
+      Re[Npix] = terms->dR_E[0];
+      Db[Npix] = terms->dD_B[0];
+      De[Npix] = terms->dD_E[0];
+    }
+  }
+  VSHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 37807)
@@ -39,4 +39,5 @@
 $(SRC)/densify.$(ARCH).o	\
 $(SRC)/device.$(ARCH).o	\
+$(SRC)/dft2d.$(ARCH).o	\
 $(SRC)/dimendown.$(ARCH).o	\
 $(SRC)/dimenup.$(ARCH).o	\
@@ -160,4 +161,10 @@
 $(SRC)/vstats.$(ARCH).o		   \
 $(SRC)/xsection.$(ARCH).o          \
+$(SRC)/vsh.$(ARCH).o	   \
+$(SRC)/vshfit.$(ARCH).o	   \
+$(SRC)/shterms.$(ARCH).o	   \
+$(SRC)/shfit.$(ARCH).o	   \
+$(SRC)/shdot.$(ARCH).o	   \
+$(SRC)/shapply.$(ARCH).o	   \
 $(SRC)/wd.$(ARCH).o		   \
 $(SRC)/write_vectors.$(ARCH).o	   \
Index: /trunk/Ohana/src/opihi/cmd.data/center.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/center.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/center.c	(revision 37807)
@@ -16,4 +16,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   // XXX need an option to center the image based on the current plot limits
Index: /trunk/Ohana/src/opihi/cmd.data/dft2d.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/dft2d.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/dft2d.c	(revision 37807)
@@ -0,0 +1,142 @@
+# include "data.h"
+
+// perform a 2D fourier transform.  
+
+static int NxLast = 0;
+static int NyLast = 0;
+static float *Fterms = NULL;
+
+int dft2d (int argc, char **argv) {
+  
+  int ix, iy, wx, wy;
+  Buffer *src = NULL;
+  Buffer *tgt = NULL;
+
+  if (argc != 4) goto usage;
+  
+  if (strcasecmp(argv[2], "to")) goto usage;
+  if ((src = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) goto usage;
+  if ((tgt = SelectBuffer (argv[3], ANYBUFFER, TRUE)) == NULL) goto usage;
+
+  int Nx = src[0].matrix.Naxis[0];
+  int Ny = src[0].matrix.Naxis[1];
+  ResetBuffer (tgt, Nx, Ny, -32, 0.0, 1.0);
+
+  if (Nx % 2) goto usage;
+  if (Ny % 2) goto usage;
+
+  // generate and save the fourier terms, if needed
+  if (Fterms && (NxLast == Nx) && (NyLast == Ny)) goto reuse;
+
+  if (!Fterms) {
+    ALLOCATE (Fterms, float, SQ(Nx*Ny));
+    NxLast = Nx;
+    NyLast = Ny;
+  } else {
+    REALLOCATE (Fterms, float, SQ(Nx*Ny));
+    NxLast = Nx;
+    NyLast = Ny;
+  }
+
+  // x-dir cosine terms
+  float Wx = 2*M_PI/(float)Nx;
+  float Wy = 2*M_PI/(float)Ny;
+  for (wx = 0; wx <= Nx / 2; wx++) {
+    for (wy = 0; wy <= Ny / 2; wy++) {
+      float rx = ((wx == 0) || (wx == Nx/2)) ? (1.0/Nx) : (2.0/Nx);
+      float ry = ((wy == 0) || (wy == Ny/2)) ? (1.0/Ny) : (2.0/Ny);
+      float rn = rx*ry;
+      for (ix = 0; ix < Nx; ix++) {
+	float fxc = cos(wx*Wx*ix);
+	for (iy = 0; iy < Ny; iy++) {
+	  float fyc = cos(wy*Wy*iy);
+	  int Nf = wx + wy*Ny;
+	  int out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxc*fyc*rn;
+	  if (wy == 0) continue;
+	  if (wy == Ny/2) continue;
+	  float fys = sin(wy*Wy*iy);
+	  Nf = wx + (Ny - wy)*Ny;
+	  out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxc*fys*rn;
+	}
+	if (wx == 0) continue;
+	if (wx == Nx/2) continue;
+	float fxs = sin(wx*Wx*ix);
+	for (iy = 0; iy < Ny; iy++) {
+	  float fyc = cos(wy*Wy*iy);
+	  int Nf = (Nx - wx) + wy*Ny;
+	  int out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxs*fyc*rn;
+	  if (wy == 0) continue;
+	  if (wy == Ny/2) continue;
+	  float fys = sin(wy*Wy*iy);
+	  Nf = (Nx - wx) + (Ny - wy)*Ny;
+	  out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxs*fys*rn;
+	}
+      }
+    }
+  }
+
+ reuse:
+  {
+    // generate and save the dot products
+    float *sv = (float *)src->matrix.buffer;
+    float *tv = (float *)tgt->matrix.buffer;
+    for (wx = 0; wx <= Nx/2; wx++) {
+      for (wy = 0; wy <= Ny/2; wy++) {
+	float fsum = 0.0;
+	int Nf = (wx + wy*Nx)*Nx*Ny;
+	for (ix = 0; ix < Nx; ix++) {
+	  for (iy = 0; iy < Ny; iy++) {
+	    float Ft = Fterms[Nf + ix + iy*Nx];
+	    float Fi = sv[ix + iy*Nx];
+	    fsum += Ft*Fi;
+	    // fprintf (stderr, "%d,%d : %d,%d : %f %f %f\n", ix, iy, wx, wy, Ft, Fi, fsum);
+	  }
+	}
+	tv[wx + wy*Nx] = fsum;
+      }
+    }
+  }
+  return (TRUE);
+
+ usage:
+  gprint (GP_ERR, "USAGE: dft2d (input) to (output)\n");
+  gprint (GP_ERR, "  NOTE: Nx & Ny must both be even\n");
+  return (FALSE);
+}
+
+
+/*** 
+
+     2D direct fourier transform:
+
+     we have an image src of size [Nx,Ny].  in each direction,
+     we can generate the following fourier components:
+
+     cos(0*2pi*x/Nx), cos(1*2pi*x/Nx),... cos((Nx/2)  *2pi*x/Nx), 
+     sin((Nx/2-1)*2pi*x/Nx), sin((Nx/2-2)*2pi*x/Nx),.. sin(1*2pi*x/Nx)
+
+     Note that there are 2 fewer sin elements than there are cos
+     elements:
+       cos(0) = 1 -> DC term
+       sin(0) = 0
+       sin(pi*x) = 0 for integer values of x
+		     
+     we represent the output with the highest frequency in the middle and the DC element
+     in the 0,0 corner
+
+     to generate the output elements, we need to take the dot product 
+     of the input image with an image for the given x,y frequency
+ 
+     thus, for an Nx*Ny input image we need a cube of fourier terms of dimension
+     Nx*Ny*(Nx*Ny) (only one of which is trivial)
+
+     In the assumption that we will likely re-do the same size image multiple times, I
+     store the fourier factor array once it is generated.
+
+     NOTE: do I need to require Nx & Ny even?
+
+ ***/
Index: /trunk/Ohana/src/opihi/cmd.data/dimenup.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/dimenup.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/dimenup.c	(revision 37807)
@@ -23,8 +23,5 @@
     return (FALSE);
   }
-
-  gfits_free_matrix (&buf[0].matrix);
-  gfits_free_header (&buf[0].header);
-  CreateBuffer (buf, Nx, Ny, -32, 0.0, 1.0);
+  ResetBuffer (buf, Nx, Ny, -32, 0.0, 1.0);
 
   out = (float *) buf[0].matrix.buffer;
Index: /trunk/Ohana/src/opihi/cmd.data/erase.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/erase.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/erase.c	(revision 37807)
@@ -15,4 +15,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if (argc < 2) {
Index: /trunk/Ohana/src/opihi/cmd.data/fit2d.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/fit2d.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/fit2d.c	(revision 37807)
@@ -138,4 +138,6 @@
     y = yvec[0].elements.Flt;
     z = zvec[0].elements.Flt;
+    if (Weight) dz = dzvec[0].elements.Flt;
+
     for (i = 0; i < xvec[0].Nelements; i++, x++, y++, z++) {
       if (mask[i]) continue;
Index: /trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 37807)
@@ -1,3 +1,4 @@
 # include "data.h"
+// XXX adding a comment
 
 int accum            PROTO((int, char **));
@@ -31,4 +32,5 @@
 int densify          PROTO((int, char **));
 int device           PROTO((int, char **));
+int dft2d            PROTO((int, char **));
 int dimendown        PROTO((int, char **));
 int dimenup          PROTO((int, char **));
@@ -147,4 +149,10 @@
 int vpop             PROTO((int, char **));
 int vsmooth          PROTO((int, char **));
+int vsh              PROTO((int, char **));
+int vshfit           PROTO((int, char **));
+int shterms          PROTO((int, char **));
+int shfit            PROTO((int, char **));
+int shdot            PROTO((int, char **));
+int shapply          PROTO((int, char **));
 int wd               PROTO((int, char **));
 int write_vectors    PROTO((int, char **));
@@ -189,4 +197,5 @@
   {1, "densify",      densify,          "create an image histogram from a set of vectors"},
   {1, "device",       device,           "set / get current graphics device"},
+  {1, "dft2d",        dft2d,            "2D discrete fourier transform"},
   {1, "dimendown",    dimendown,        "convert image to vector"},
   {1, "dimenup",      dimenup,          "convert vector to image"},
@@ -314,4 +323,10 @@
   {1, "vstats",       vstats,           "statistics on a vector"},
   {1, "vzload",       vzload,           "load vectors as overlay on image display (scaled points)"},
+  {1, "vsh",          vsh,              "Vector Spherical Harmonics"},
+  {1, "vshfit",       vshfit,           "Vector Spherical Harmonics fits"},
+  {1, "shterms",      shterms,          "Spherical Harmonics terms"},
+  {1, "shfit",        shfit,            "Spherical Harmonics fits"},
+  {1, "shdot",        shdot,            "Spherical Harmonics dot product"},
+  {1, "shapply",      shapply,          "Spherical Harmonics fit application"},
   {1, "wd",           wd,               "write an image to a file"},
   {1, "write",        write_vectors,    "write vectors to datafile"},
Index: /trunk/Ohana/src/opihi/cmd.data/load.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/load.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/load.c	(revision 37807)
@@ -23,4 +23,11 @@
   if (!GetImageData (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   ISCEL = FALSE;
Index: /trunk/Ohana/src/opihi/cmd.data/point.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/point.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/point.c	(revision 37807)
@@ -21,4 +21,11 @@
   if (!GetImageData (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   celestial = FALSE;
Index: /trunk/Ohana/src/opihi/cmd.data/read_vectors.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 37807)
@@ -5,4 +5,5 @@
 
 void read_vectors_cleanup ();
+int read_table_sizes (Header *header);
 
 int datafile (int argc, char **argv) {
@@ -331,15 +332,14 @@
 
   off_t Nbytes;
-  int i, j, N, Nextend, Ny, Binary, vecType, padIfShort;
-  char type[16], ID[80], *CCDKeyword;
+  int i, j, N, Ny, Binary, vecType;
+  char type[16], ID[80];
   FTable table;
   Header header;
   Vector **vec;
-  int FITS_TRANSPOSE;
 
   table.buffer = NULL;
   header.buffer = NULL;
 
-  FITS_TRANSPOSE = FALSE;
+  int FITS_TRANSPOSE = FALSE;
   if ((N = get_argument (argc, argv, "-transpose"))) {
     remove_argument (N, &argc, argv);
@@ -347,5 +347,5 @@
   }
 
-  CCDKeyword = NULL;
+  char *CCDKeyword = NULL;
   if ((N = get_argument (argc, argv, "-keyword"))) {
     remove_argument (N, &argc, argv);
@@ -354,5 +354,5 @@
   }
 
-  padIfShort = FALSE;
+  int padIfShort = FALSE;
   if ((N = get_argument (argc, argv, "-pad-if-short"))) {
     remove_argument (N, &argc, argv);
@@ -360,8 +360,33 @@
   }
 
-  Nextend = -1;
+  int getSizes = FALSE;
+  if ((N = get_argument (argc, argv, "-sizes"))) {
+    remove_argument (N, &argc, argv);
+    getSizes = TRUE;
+  }
+
+  int Nextend = -1;
   if ((N = get_argument (argc, argv, "-extnum"))) {
     remove_argument (N, &argc, argv);
     Nextend = atoi (extname);
+  }
+
+  int start = 0;
+  int Nrows = -1; // -1 : read entire table
+  if ((N = get_argument (argc, argv, "-range"))) {
+    remove_argument (N, &argc, argv);
+    start = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+    Nrows = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  // if CharAsVectors, char fields will be saved as vectors NAME:0 -- NAME:n for n characters
+  // else char fields will be saved as $NAME:0 - $NAME:m for m rows
+  // if (Ny > 10000), force CharAsVectors
+  int CharAsVectors = FALSE;
+  if ((N = get_argument (argc, argv, "-char-vectors"))) {
+    remove_argument (N, &argc, argv);
+    CharAsVectors = TRUE;
   }
 
@@ -397,6 +422,24 @@
     }
     if (!gfits_load_header (f, &header)) ESCAPE ("error reading header for extension %d\n", Nextend);
-    if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension %d\n", Nextend);
-
+
+    if (getSizes) {
+      read_table_sizes (&header);
+      if (CCDKeyword != NULL) free (CCDKeyword); 
+      gfits_free_header (&header); 
+      return TRUE;
+    }
+
+    if (Nrows == -1) {
+      Nrows = header.Naxis[1] - start;
+    }
+    if (start < 0) ESCAPE ("invalid range: start < 0\n");
+    if (start >= header.Naxis[1]) ESCAPE ("invalid range: start >= Ny (%d)\n", header.Naxis[1]);
+    if (Nrows < 0) ESCAPE ("invalid range: Nrows < 0\n");
+    if (start + Nrows > header.Naxis[1]) ESCAPE ("invalid range: start + Nrows > Ny (%d)\n", header.Naxis[1]);
+
+    // Ny = 100, start = 0, Nrows = -1 -> Nrows => 100
+    // Ny = 100, start = 10, Nrows = 90
+
+    if (!gfits_fread_ftable_range (f, padIfShort, &table, start, Nrows)) ESCAPE ("error reading table for extension %d\n", Nextend);
   } else {
     if (CCDKeyword == NULL) {
@@ -428,5 +471,17 @@
 	continue;
       }
-      if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension\n");
+
+      if (Nrows == -1) {
+	Nrows = header.Naxis[1] - start;
+      }
+      if (start < 0) ESCAPE ("invalid range: start < 0\n");
+      if (start >= header.Naxis[1]) ESCAPE ("invalid range: start >= Ny (%d)\n", header.Naxis[1]);
+      if (Nrows < 0) ESCAPE ("invalid range: Nrows < 0\n");
+      if (start + Nrows > header.Naxis[1]) ESCAPE ("invalid range: start + Nrows > Ny (%d)\n", header.Naxis[1]);
+
+      if (!gfits_fread_ftable_range (f, padIfShort, &table, start, Nrows)) ESCAPE ("error reading table for extension %d\n", Nextend);
+
+      // if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension\n");
+
       break;
     }
@@ -465,5 +520,5 @@
     if (!FITS_TRANSPOSE) {
       // read string column into a list rather than a vector
-      if (!strcmp (type, "char")) {
+      if (!strcmp (type, "char") && !CharAsVectors && (Ny < 3000)) {
 	char *fieldName = argv[i];
 	char *Ptr = data;
@@ -537,2 +592,19 @@
   if (vec) free (vec);
 }
+
+// read -fits foo -sizes -- Nx, Ny, Nfields -> $table:Nx, $table:Ny, $table:$Nfields
+// read -fits foo -fields 
+
+int read_table_sizes (Header *header) {
+  
+  int Nfields;
+
+  gfits_scan (header, "TFIELDS", "%d", 1, &Nfields);
+
+  set_int_variable ("table:Nx", header->Naxis[0]);
+  set_int_variable ("table:Nx", header->Naxis[0]);
+  set_int_variable ("table:Nfields", Nfields);
+
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/opihi/cmd.data/relocate.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/relocate.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/relocate.c	(revision 37807)
@@ -17,4 +17,11 @@
   FREE (name);
 
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
+
   if (argc != 3) {
     gprint (GP_ERR, "USAGE: relocate x y [-n]\n");
Index: /trunk/Ohana/src/opihi/cmd.data/resize.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/resize.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/resize.c	(revision 37807)
@@ -17,4 +17,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if ((N = get_argument (argc, argv, "-by-image"))) {
Index: /trunk/Ohana/src/opihi/cmd.data/save.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/save.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/save.c	(revision 37807)
@@ -16,4 +16,11 @@
   FREE (name);
 
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
+
   celestial = FALSE;
   if ((N = get_argument (argc, argv, "-c"))) {
Index: /trunk/Ohana/src/opihi/cmd.data/shapply.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/shapply.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/shapply.c	(revision 37807)
@@ -0,0 +1,63 @@
+# include "data.h"
+
+int shapply (int argc, char **argv) {
+  
+  int i, j;
+  Vector *Rvec, *Dvec, *Frvec, *Fivec, *Vrvec, *Vivec;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: shapply R D lmax Fr Fi Vr Vi\n");
+    gprint (GP_ERR, "  apply the Ylm values (Fr,Fi) at the given R,D values to get the fit at those positions\n");
+    return (FALSE);
+  }
+
+  if ((Rvec = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if (Rvec->Nelements != Dvec->Nelements) {
+    gprint (GP_ERR, "R and D sizes do not match\n");
+    return FALSE;
+  }
+
+  int lmax = atoi(argv[3]);
+
+  if ((Frvec = SelectVector (argv[4], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[5], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if (Frvec->Nelements != Fivec->Nelements) { 
+    gprint (GP_ERR, "Fr and Fi sizes do not match\n");
+    return FALSE;
+  }
+
+  if ((Vrvec = SelectVector (argv[6], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Vivec = SelectVector (argv[7], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  SHterms *terms = SHtermsInit (lmax);
+  if (Frvec->Nelements != terms->Nterms) {
+    gprint (GP_ERR, "Lmax (%d) terms (%d) does not match size of Fr,Fi vectors (%d) \n", lmax, terms->Nterms, Frvec->Nelements);
+    return FALSE;
+  }
+
+  ResetVector (Vrvec, OPIHI_FLT, Rvec->Nelements);
+  ResetVector (Vivec, OPIHI_FLT, Rvec->Nelements);
+
+  // measure the dot product \sum(F_i * Ylm_i)
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    SHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double *Fr = Frvec->elements.Flt;
+    double *Fi = Fivec->elements.Flt;
+
+    double Vr = 0.0;
+    double Vi = 0.0;
+    for (j = 0; j < terms->Nterms; j++) {
+      Vr += Fr[j] * terms->Fr[j];
+      Vi += Fi[j] * terms->Fi[j];
+    }
+    Vrvec->elements.Flt[i] = Vr;
+    Vivec->elements.Flt[i] = Vi;
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/shdot.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/shdot.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/shdot.c	(revision 37807)
@@ -0,0 +1,69 @@
+# include "data.h"
+
+int shdot (int argc, char **argv) {
+  
+  int i, j;
+  Vector *Rvec, *Dvec, *Fvec, *Lvec, *Mvec, *Frvec, *Fivec;
+  double *Fr, *Fi;
+
+  if (argc != 9) {
+    gprint (GP_ERR, "USAGE: shdot R D value lmax Lout Mout Fr Fi\n");
+    gprint (GP_ERR, "  find the dot product of the scalar field value (at points R,D) to spherical harmonics up to the given lmax\n");
+    return (FALSE);
+  }
+
+  if ((Rvec = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fvec = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[4]);
+
+  if ((Lvec  = SelectVector (argv[5], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Mvec  = SelectVector (argv[6], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Frvec = SelectVector (argv[7], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[8], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  SHterms *terms = SHtermsInit (lmax);
+
+  ALLOCATE_ZERO (Fr, double, terms->Nterms);
+  ALLOCATE_ZERO (Fi, double, terms->Nterms);
+
+  ResetVector (Frvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Fivec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Lvec,  OPIHI_FLT, terms->Nterms);
+  ResetVector (Mvec,  OPIHI_FLT, terms->Nterms);
+
+  // measure the dot product \sum(F_i * Ylm_i)
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    SHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double Fv = Fvec->elements.Flt[i];
+
+    for (j = 0; j < terms->Nterms; j++) {
+      Fr[j] += Fv * terms->Fr[j];
+      Fi[j] += Fv * terms->Fi[j];
+    }
+  }
+
+  // for (j = 0; j < terms->Nterms; j++) {
+  //   fprintf (stderr, "%d : %d %d : %f %f\n", j, terms->l[j], terms->m[j], Fr[j], Fi[j]);
+  // }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    Fr[j] /= Fvec->Nelements;
+    Fi[j] /= Fvec->Nelements;
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    // fprintf (stderr, "%d : %d %d : %f %f\n", j, terms->l[j], terms->m[j], 4*M_PI*Fr[j], 4*M_PI*Fi[j]);
+    Lvec[0].elements.Flt[j]  = terms->l[j];
+    Mvec[0].elements.Flt[j]  = terms->m[j];
+    Frvec[0].elements.Flt[j] = 4*M_PI*Fr[j];
+    Fivec[0].elements.Flt[j] = 4*M_PI*Fi[j];
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/shfit.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/shfit.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/shfit.c	(revision 37807)
@@ -0,0 +1,163 @@
+# include "data.h"
+
+int shfit (int argc, char **argv) {
+  
+  int i, j, k;
+  Vector *Rvec, *Dvec, *Fvec, *Lvec, *Mvec, *Frvec, *Fivec;
+  double *Fr, *Fi;
+
+  if (argc != 9) {
+    gprint (GP_ERR, "USAGE: shfit R D value lmax Lout Mout Fr Fi\n");
+    gprint (GP_ERR, "  fit the scalar field value (at points R,D) to spherical harmonics up to the given lmax\n");
+    return (FALSE);
+  }
+
+  if ((Rvec = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fvec = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[4]);
+
+  if ((Lvec  = SelectVector (argv[5], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Mvec  = SelectVector (argv[6], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Frvec = SelectVector (argv[7], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[8], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  SHterms *terms = SHtermsInit (lmax);
+
+  ALLOCATE_ZERO (Fr, double, terms->Nterms);
+  ALLOCATE_ZERO (Fi, double, terms->Nterms);
+
+  ResetVector (Frvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Fivec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Lvec,  OPIHI_FLT, terms->Nterms);
+  ResetVector (Mvec,  OPIHI_FLT, terms->Nterms);
+
+  // we only fit the linearlly independent terms: Re(m >= 0), Im(m > 0)
+  int Nre = 0;
+  int Nim = 0;
+  for (i = 0; i < terms->Nterms; i++) {
+    if (terms->m[i] >= 0) Nre ++;
+    if (terms->m[i] >  0) Nim ++;
+  }
+  int *Jre = NULL;
+  int *Jim = NULL;
+  ALLOCATE (Jre, int, Nre);
+  ALLOCATE (Jim, int, Nim);
+
+  Nre = Nim = 0;
+  for (i = 0; i < terms->Nterms; i++) {
+    if (terms->m[i] >= 0) {
+      Jre[Nre] = i;
+      Nre ++;
+    }
+    if (terms->m[i] >  0) {
+      Jim[Nim] = i;
+      Nim ++;
+    }
+  }
+
+  double **Are, **bre, **Aim, **bim;
+  ALLOCATE (Are, double *, Nre);
+  ALLOCATE (bre, double *, Nre);
+  ALLOCATE (Aim, double *, Nim);
+  ALLOCATE (bim, double *, Nim);
+  for (i = 0; i < Nre; i++) {
+    ALLOCATE_ZERO (Are[i], double, Nre);
+    ALLOCATE_ZERO (bre[i], double, 1);
+  }
+  for (i = 0; i < Nim; i++) {
+    ALLOCATE_ZERO (Aim[i], double, Nim);
+    ALLOCATE_ZERO (bim[i], double, 1);
+  }
+
+  // measure the dot product \sum(F_i * Ylm_i) and the cross terms (\sum(Y_lm * Y_jk))
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    SHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double Fv = Fvec->elements.Flt[i];
+
+    for (j = 0; j < Nre; j++) {
+      int jre = Jre[j];
+      bre[j][0] += Fv * terms->Fr[jre];
+    }
+    for (j = 0; j < Nim; j++) {
+      int jim = Jim[j];
+      bim[j][0] += Fv * terms->Fi[jim];
+    }
+
+    for (j = 0; j < Nre; j++) {
+      int jre = Jre[j];
+      for (k = j; k < Nre; k++) {
+	int kre = Jre[k];
+	Are[j][k] += terms->Fr[jre] * terms->Fr[kre];
+      }
+    }
+
+    for (j = 0; j < Nim; j++) {
+      int jim = Jim[j];
+      for (k = j; k < Nim; k++) {
+	int kim = Jim[k];
+	Aim[j][k] += terms->Fi[jim] * terms->Fi[kim];
+      }
+    }
+  }
+
+  for (j = 1; j < Nre; j++) {
+    for (k = 0; k < j; k++) {
+      Are[j][k] = Are[k][j];
+    }	
+  }
+  for (j = 1; j < Nim; j++) {
+    for (k = 0; k < j; k++) {
+      Aim[j][k] = Aim[k][j];
+    }	
+  }
+
+  fprintf (stderr, "--- Are --- : bre \n");
+  for (j = 0; j < Nre; j++) {
+    fprintf (stderr, "%10.6f ", Are[j][j]);
+    fprintf (stderr, " : %10.6f\n", bre[j][0]);
+  }
+
+  fprintf (stderr, "--- Aim --- : bim \n");
+  for (i = 0; i < Nim; i++) {
+    fprintf (stderr, "%10.6f : ", Aim[i][i]);
+    fprintf (stderr, " : %10.6f\n", bim[i][0]);
+  }
+
+  if (!dgaussjordan (Are, bre, Nre, 1)) {
+    gprint (GP_ERR, "failed to fit data : ill-conditioned matrix\n");
+    return FALSE;
+  }
+  if (!dgaussjordan (Aim, bim, Nim, 1)) {
+    gprint (GP_ERR, "failed to fit data : ill-conditioned matrix\n");
+    return FALSE;
+  }
+
+  for (j = 0; j < terms->Nterms; j++) { 
+    Fr[j] = 0.0;
+    Fi[i] = 0.0;
+  }
+  for (j = 0; j < Nre; j++) {
+    int jre = Jre[j];
+    Fr[jre] = bre[j][0];
+  }
+  for (j = 0; j < Nim; j++) {
+    int jim = Jim[j];
+    Fi[jim] = bim[j][0];
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    // fprintf (stderr, "%d : %d %d : %f %f\n", j, terms->l[j], terms->m[j], 4*M_PI*Fr[j], 4*M_PI*Fi[j]);
+    Lvec[0].elements.Flt[j]  = terms->l[j];
+    Mvec[0].elements.Flt[j]  = terms->m[j];
+    Frvec[0].elements.Flt[j] = Fr[j];
+    Fivec[0].elements.Flt[j] = Fi[j];
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/shterms.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/shterms.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/shterms.c	(revision 37807)
@@ -0,0 +1,43 @@
+# include "data.h"
+
+int shterms (int argc, char **argv) {
+  
+  int i;
+  Vector *Frvec, *Fivec, *lvec, *mvec;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: shterms Fr Fi l m lmax R D\n");
+    gprint (GP_ERR, "  set the Fr and Fi values for sh terms up to lmax at R,D\n");
+    return (FALSE);
+  }
+
+  if ((Frvec = SelectVector (argv[1], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[2], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((lvec  = SelectVector (argv[3], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((mvec  = SelectVector (argv[4], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[5]);
+
+  SHterms *terms = SHtermsInit (lmax);
+
+  double R = atof(argv[6]);
+  double D = atof(argv[7]);
+
+  ResetVector (Frvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Fivec, OPIHI_FLT, terms->Nterms);
+  ResetVector (lvec,  OPIHI_FLT, terms->Nterms);
+  ResetVector (mvec,  OPIHI_FLT, terms->Nterms);
+
+  SHtermsForRD (terms, R, D); 
+
+  for (i = 0; i < terms->Nterms; i++) {
+    Frvec[0].elements.Flt[i] = terms->Fr[i];
+    Fivec[0].elements.Flt[i] = terms->Fi[i];
+    lvec[0].elements.Flt[i]  = terms->l[i];
+    mvec[0].elements.Flt[i]  = terms->m[i];
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/tv.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/tv.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/tv.c	(revision 37807)
@@ -18,4 +18,13 @@
   if (!GetImage (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    remove_argument (N, &argc, argv);
+    channel = GetKapaChannelFromString (argv[N]);
+    remove_argument (N, &argc, argv);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   /* shell exits on pipe close, FIX */
Index: /trunk/Ohana/src/opihi/cmd.data/tvchannel.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/tvchannel.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/tvchannel.c	(revision 37807)
@@ -22,23 +22,31 @@
   }
 
-  Nchannel = atoi (argv[1]);
+  Nchannel = GetKapaChannelFromString (argv[1]);
+  if (!Nchannel) return FALSE;
+
+  KiiSetChannel (kapa, Nchannel - 1);
+  return (TRUE);
+}
+
+int GetKapaChannelFromString (char *string) {
+
+  int Nchannel = atoi (string);
   if (Nchannel == 0) {
     // try the string values R/Red, G/Green, B/Blue
-    if (!strcasecmp (argv[1], "R") || !strcasecmp (argv[1], "RED")) {
+    if (!strcasecmp (string, "R") || !strcasecmp (string, "RED")) {
       Nchannel = 1;
     }
-    if (!strcasecmp (argv[1], "G") || !strcasecmp (argv[1], "GREEN")) {
+    if (!strcasecmp (string, "G") || !strcasecmp (string, "GREEN")) {
       Nchannel = 2;
     }
-    if (!strcasecmp (argv[1], "B") || !strcasecmp (argv[1], "BLUE")) {
+    if (!strcasecmp (string, "B") || !strcasecmp (string, "BLUE")) {
       Nchannel = 3;
     }
   }
-  if ((Nchannel < 1) || (Nchannel > 3)) {
-    gprint (GP_ERR, "invalid channel : use 1 - 3 or (R)ed, (G)reen, (B)lue\n");
-    return (FALSE);
+  if ((Nchannel < 1) || (Nchannel > 10)) {
+    gprint (GP_ERR, "invalid channel : use 1 - 10 or (R)ed, (G)reen, (B)lue\n");
+    gprint (GP_ERR, "   (R)ed, (G)reen, (B)lue == (1,2,3)\n");
+    return (0);
   }
-    
-  KiiSetChannel (kapa, Nchannel - 1);
-  return (TRUE);
+  return Nchannel;
 }
Index: /trunk/Ohana/src/opihi/cmd.data/tvcontour.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/tvcontour.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/tvcontour.c	(revision 37807)
@@ -71,4 +71,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if ((argc != 4) && (argc != 5)) {
Index: /trunk/Ohana/src/opihi/cmd.data/tvgrid.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/tvgrid.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/tvgrid.c	(revision 37807)
@@ -24,4 +24,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if (argc != 3) {
Index: /trunk/Ohana/src/opihi/cmd.data/vload.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/vload.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/vload.c	(revision 37807)
@@ -18,4 +18,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   type = KII_OVERLAY_BOX;
Index: /trunk/Ohana/src/opihi/cmd.data/vsh.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/vsh.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/vsh.c	(revision 37807)
@@ -0,0 +1,43 @@
+# include "data.h"
+
+int vsh (int argc, char **argv) {
+  
+  int i;
+  Vector *Rbvec, *Revec, *Dbvec, *Devec;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: vsh dRb dRe dDb dDe lmax R D\n");
+    gprint (GP_ERR, "  set the dRb, dRe, dDb, dDe values for vsh terms up to lmax at R,D\n");
+    return (FALSE);
+  }
+
+  if ((Rbvec = SelectVector (argv[1], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Revec = SelectVector (argv[2], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dbvec = SelectVector (argv[3], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Devec = SelectVector (argv[4], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[5]);
+
+  VSHterms *terms = VSHtermsInit (lmax);
+
+  double R = atof(argv[6]);
+  double D = atof(argv[7]);
+
+  ResetVector (Rbvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Revec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Dbvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Devec, OPIHI_FLT, terms->Nterms);
+
+  VSHtermsForRD (terms, R, D); 
+
+  for (i = 0; i < terms->Nterms; i++) {
+    Rbvec[0].elements.Flt[i] = terms->dR_B[i];
+    Revec[0].elements.Flt[i] = terms->dR_E[i];
+    Dbvec[0].elements.Flt[i] = terms->dD_B[i];
+    Devec[0].elements.Flt[i] = terms->dD_E[i];
+  }
+
+  VSHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/vshfit.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/vshfit.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/cmd.data/vshfit.c	(revision 37807)
@@ -0,0 +1,62 @@
+# include "data.h"
+
+int vshfit (int argc, char **argv) {
+  
+  int i, j;
+  Vector *Rvec, *Dvec, *dRvec, *dDvec;
+  double *Re, *Rb, *De, *Db;
+
+  if (argc != 6) {
+    gprint (GP_ERR, "USAGE: vshfit Rvec Dvec dRvec dDvec lmax\n");
+    gprint (GP_ERR, "  fit the vector field dRvec, dDvec (at points Rvec,Dvec) to vector spherical harmonics up to the given lmax\n");
+    return (FALSE);
+  }
+
+  if ((Rvec  = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec  = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((dRvec = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((dDvec = SelectVector (argv[4], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[5]);
+
+  VSHterms *terms = VSHtermsInit (lmax);
+
+  ALLOCATE_ZERO (Re, double, terms->Nterms);
+  ALLOCATE_ZERO (Rb, double, terms->Nterms);
+  ALLOCATE_ZERO (De, double, terms->Nterms);
+  ALLOCATE_ZERO (Db, double, terms->Nterms);
+
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    VSHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double dR = dRvec->elements.Flt[i];
+    double dD = dDvec->elements.Flt[i];
+
+    for (j = 0; j < terms->Nterms; j++) {
+      Rb[j] += dR * terms->dR_B[j];
+      Re[j] += dR * terms->dR_E[j];
+      Db[j] += dD * terms->dD_B[j];
+      De[j] += dD * terms->dD_E[j];
+    }
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    fprintf (stderr, "%d : %d %d : %f %f %f %f\n", j, terms->l[j], terms->m[j], Rb[j], Re[j], Db[j], De[j]);
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    Rb[j] /= terms->Nterms;
+    Re[j] /= terms->Nterms;
+    Db[j] /= terms->Nterms;
+    De[j] /= terms->Nterms;
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    fprintf (stderr, "%d : %d %d : %f %f %f %f\n", j, terms->l[j], terms->m[j], Rb[j], Re[j], Db[j], De[j]);
+  }
+
+  VSHtermsFree (terms);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/vzload.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/vzload.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/cmd.data/vzload.c	(revision 37807)
@@ -18,4 +18,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   MAX_OUTPUT_SIZE = 10.0;
Index: /trunk/Ohana/src/opihi/dvo/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/dvo/Makefile	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/Makefile	(revision 37807)
@@ -46,4 +46,5 @@
 $(SRC)/badimages.$(ARCH).o	  	\
 $(SRC)/catdir.$(ARCH).o             	\
+$(SRC)/catname.$(ARCH).o             	\
 $(SRC)/cmatch.$(ARCH).o	  	\
 $(SRC)/cmpload.$(ARCH).o	  	\
Index: /trunk/Ohana/src/opihi/dvo/avextract.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/avextract.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/avextract.c	(revision 37807)
@@ -5,5 +5,5 @@
   off_t i, j, n, m;
   int N, Npts, NPTS, last, next, state, Nfields, Nreturn, Ncstack, Nstack;
-  int Nsecfilt, VERBOSE, needMeasures;
+  int Nsecfilt, VERBOSE;
   char **cstack, name[1024];
   void *Signal;
@@ -137,5 +137,5 @@
   // this does all the work of re-packaging the command, calling it on the remote machines, then loading in the results
   if (PARALLEL && !HOST_ID) {
-      int status = HostTableParallelOps (skylist, argc, argv, RESULT_FILE, TRUE, 0, VERBOSE);
+    int status = HostTableParallelOps (skylist, argc, argv, RESULT_FILE, TRUE, 0, VERBOSE);
 
     dbFreeFields (fields, Nfields);
@@ -163,11 +163,8 @@
   }
 
-  // check the requested fields : are all average/secfilt entries, or do we need measures?
-  needMeasures = FALSE;
-  for (i = 0; !needMeasures && (i < Nfields); i++) {
-    if (fields[i].photcode == NULL) continue; // non-measure fields do not have a photcode
-    if (fields[i].photcode[0].type == PHOT_REF) needMeasures = TRUE;
-    if (fields[i].photcode[0].type == PHOT_DEP) needMeasures = TRUE;
-  }
+  // check the requested fields
+  int needMeasure = dbFieldNeedMeasure (fields, Nfields);
+  int needLensobj = dbFieldNeedLensobj (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, TRUE);
 
   // grab data from all selected sky regions
@@ -189,5 +186,7 @@
     catalog.filename = (HOST_ID || PARALLEL_LOCAL) ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_SECF;
-    catalog.catflags |= needMeasures ? LOAD_MEAS : SKIP_MEAS;
+    catalog.catflags |= needMeasure ? LOAD_MEAS : SKIP_MEAS;
+    catalog.catflags |= needLensobj ? LOAD_LENSOBJ : SKIP_LENSOBJ;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = 0;
 
@@ -205,10 +204,22 @@
       // XXX for measure values, this could be optimized for one loop over measures...
 
-      m = catalog.average[j].measureOffset;
-
       dbExtractAveragesInitAve ();  // reset counters for saved fields (costs very little)
 
+      Average *average = &catalog.average[j];
+
+      m = average->measureOffset;
+      Measure *measure = needMeasure ? &catalog.measure[m] : NULL;
+
+      m = average->lensobjOffset;
+      Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+      m = average->starparOffset;
+      StarPar *starpar = needStarpar ? &catalog.starpar[m] : NULL;
+
+      m = j*Nsecfilt;
+      SecFilt *secfilt = &catalog.secfilt[m];
+
       for (n = 0; n < Nfields; n++) {
-	values[n] = dbExtractAverages (&catalog.average[j], &catalog.secfilt[j*Nsecfilt], &catalog.measure[m], &fields[n]);
+	values[n] = dbExtractAverages (average, secfilt, measure, lensobj, starpar, &fields[n]);
       }
 
Index: /trunk/Ohana/src/opihi/dvo/avmatch.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/avmatch.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/avmatch.c	(revision 37807)
@@ -149,11 +149,8 @@
   }
 
-  // check the requested fields : are all average/secfilt entries, or do we need measures?
-  int needMeasures = FALSE;
-  for (i = 0; !needMeasures && (i < Nfields); i++) {
-    if (fields[i].photcode == NULL) continue; // assert this?
-    if (fields[i].photcode[0].type == PHOT_REF) needMeasures = TRUE;
-    if (fields[i].photcode[0].type == PHOT_DEP) needMeasures = TRUE;
-  }
+  // check the requested fields
+  int needMeasure = dbFieldNeedMeasure (fields, Nfields);
+  int needLensobj = dbFieldNeedLensobj (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, TRUE);
 
   /* create output storage vectors */
@@ -194,5 +191,7 @@
     catalog.filename = HOST_ID ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_SECF;
-    catalog.catflags |= needMeasures ? LOAD_MEAS : SKIP_MEAS;
+    catalog.catflags |= needMeasure ? LOAD_MEAS    : SKIP_MEAS;
+    catalog.catflags |= needLensobj ? LOAD_LENSOBJ : SKIP_LENSOBJ;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = 0;
 
@@ -219,10 +218,23 @@
       if (Ncat == -2) continue; // no matches to this point
 
-      m = catalog.average[Ncat].measureOffset;
+      m = Ncat;
+      Average *average = &catalog.average[m];
+
+      m = average->measureOffset;
+      Measure *measure = needMeasure ? &catalog.measure[m] : NULL;
+
+      m = average->lensobjOffset;
+      Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+      m = average->starparOffset;
+      StarPar *starpar = needStarpar ? &catalog.starpar[m] : NULL;
+
+      m = Ncat*Nsecfilt;
+      SecFilt *secfilt = &catalog.secfilt[m];
 
       // reset counters for saved fields, extract fields
       dbExtractAveragesInitAve (); 
       for (n = 0; n < Nfields; n++) {
-	values[n] = dbExtractAverages (&catalog.average[Ncat], &catalog.secfilt[Ncat*Nsecfilt], &catalog.measure[m], &fields[n]);
+	values[n] = dbExtractAverages (average, secfilt, measure, lensobj, starpar, &fields[n]);
       }
 
Index: /trunk/Ohana/src/opihi/dvo/catname.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/catname.c	(revision 37807)
+++ /trunk/Ohana/src/opihi/dvo/catname.c	(revision 37807)
@@ -0,0 +1,104 @@
+# include "dvoshell.h"
+
+int catname (int argc, char **argv) {
+  
+  int i, N;
+  struct stat filestat;
+
+  int ShowHost = FALSE;
+  if ((N = get_argument (argc, argv, "-host"))) {
+    remove_argument (N, &argc, argv);
+    ShowHost = TRUE;
+  }
+  int ShowBackup = FALSE;
+  if ((N = get_argument (argc, argv, "-backup"))) {
+    remove_argument (N, &argc, argv);
+    ShowBackup = TRUE;
+  }
+  int FullName = FALSE;
+  if ((N = get_argument (argc, argv, "-full-name"))) {
+    remove_argument (N, &argc, argv);
+    FullName = TRUE;
+  }
+  int ShowFlags = FALSE;
+  if ((N = get_argument (argc, argv, "-flags"))) {
+    remove_argument (N, &argc, argv);
+    ShowFlags = TRUE;
+  }
+  int PARALLEL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel"))) {
+    remove_argument (N, &argc, argv);
+    PARALLEL = TRUE;
+  }
+
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: catname name [-host] [-backup] [-flags]\n");
+    return (FALSE);
+  }
+
+  char *CATDIR = GetCATDIR();
+  if (!CATDIR) {
+    gprint (GP_ERR, "CATDIR is not set\n");
+    return FALSE;
+  }
+  SkyTable *sky = GetSkyTable ();
+  if (!sky) {
+    gprint (GP_ERR, "failed to load sky table for database\n");
+    return FALSE;
+  }
+  HostTable *table = HostTableLoad (CATDIR, sky->hosts);
+  if (!table) {
+    gprint (GP_ERR, "ERROR: failure reading Host Table %s for database %s\n", sky->hosts, CATDIR);
+    return FALSE;
+  }    
+  SkyList *skylist = SkyListByName (sky, argv[1]);
+
+  // prepare to handle interrupt signals
+  signal (SIGINT, handle_interrupt);
+  interrupt = FALSE;
+
+  for (i = 0; (i < skylist[0].Nregions) && !interrupt; i++) {
+    char *filename = NULL;
+    if (PARALLEL) {
+      SkyRegion *region = skylist[0].regions[i];
+      int hostID = (region->hostFlags & DATA_USE_BCK) ? region->backupID : region->hostID;
+      int index = table->index[hostID];
+    
+      char hostfile[1024];
+      snprintf (hostfile, 1024, "%s/%s.cpt", table->hosts[index].pathname, region->name);
+      filename = hostfile;
+    } else {
+      filename = skylist[0].filename[i];
+    }
+
+    gprint (GP_ERR, "%3d %s", i, skylist[0].regions[i][0].name);
+    if (stat (filename, &filestat) != -1) {
+      gprint (GP_ERR, " +");
+    } else {
+      gprint (GP_ERR, " -");
+    } 
+    if (ShowHost) {
+      gprint (GP_ERR, "  %3d", skylist[0].regions[i][0].hostID);
+    } else {
+      gprint (GP_ERR, "     ");
+    }
+    if (FullName) {
+      gprint (GP_ERR, "  %s", filename);
+    }
+    if (ShowBackup) {
+      gprint (GP_ERR, "  %3d", skylist[0].regions[i][0].backupID);
+    } else {
+      gprint (GP_ERR, "     ");
+    }
+    if (ShowFlags) {
+      gprint (GP_ERR, "  0x%04x", skylist[0].regions[i][0].hostFlags);
+    } else {
+      gprint (GP_ERR, "        ");
+    }
+    gprint (GP_ERR, "\n");
+    set_str_variable ("CATNAME", filename);
+  }
+
+  return (TRUE);
+  SkyListFree (skylist);
+}
Index: /trunk/Ohana/src/opihi/dvo/cmpload.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/cmpload.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/cmpload.c	(revision 37807)
@@ -24,4 +24,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   Objtype = 0;
Index: /trunk/Ohana/src/opihi/dvo/dvo_host_utils.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/dvo_host_utils.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/dvo_host_utils.c	(revision 37807)
@@ -137,4 +137,5 @@
 
   // determine the sky region 
+  // XXX EAM 20141230 : this needs to respect the -region selection
   double Rmin, Rmax, Dmin, Dmax;
   get_skyregion (&Rmin, &Rmax, &Dmin, &Dmax);
Index: /trunk/Ohana/src/opihi/dvo/find_matches.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/find_matches.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/find_matches.c	(revision 37807)
@@ -38,4 +38,5 @@
    * far from the projection pole. We use the center of the region (catalog) for crval1,2.
    */
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -44,11 +45,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
-  strcpy (tcoords.ctype, "DEC--ARC");
 
   // this region includes a boundary layer of size RADIUS
Index: /trunk/Ohana/src/opihi/dvo/gimages.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/gimages.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/gimages.c	(revision 37807)
@@ -85,5 +85,5 @@
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
   image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   int DistortImage = wordhash ("-DIS");
@@ -102,5 +102,4 @@
       } 
     }      
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
 
     typehash = wordhash (&image[i].coords.ctype[4]);
@@ -129,12 +128,8 @@
       if (fabs(Ro - Ra) > 120.0) continue;
 
+      InitCoords (&local, "DEC--TAN");
       local.crval1 = Ro;
       local.crval2 = Do;
-      local.crpix1 = local.crpix2 = 0.0;
       local.cdelt1 = local.cdelt2 = 1.0/3600.0;
-      local.pc1_1 = local.pc2_2 = 1.0;
-      local.pc2_1 = local.pc1_2 = 0.0;
-      local.Npolyterms = 1;
-      strcpy (local.ctype, "DEC--TAN");
 
       if (typehash == DistortImage) {
Index: /trunk/Ohana/src/opihi/dvo/images.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/images.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/images.c	(revision 37807)
@@ -131,5 +131,5 @@
   /* it is not an error for the database not to have any images */
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (TRUE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   if (SOLO_MOSAIC && photcode) {
@@ -152,4 +152,6 @@
   SetVector (&Yvec, OPIHI_FLT, NPTS);
 
+  Image *mosaic = NULL;
+
   ALLOCATE (plist, int, NPTS);
   n = N = 0;
@@ -157,6 +159,8 @@
     if (ByName && strncmp (image[i].name, name, strlen(name))) continue;
     if (TimeSelect && ((image[i].tzero < tzero) || (image[i].tzero+image[i].trate*image[i].NY > tzero + trange))) continue;
-    if (!(Nmosaic = FindMosaicForImage (image, Nimage, i))) continue;
-    Nmosaic --; // XXX kind of a hack: FindMosaicForImage returns 0 or the mosaic seq number + 1
+
+    mosaic = image[i].parent;
+    Nmosaic = (SOLO_MOSAIC && mosaic) ? mosaic - image : -1;
+
     if (photcode) {
       if ( photcodeEquiv && (photcode[0].code != GetPhotcodeEquivCodebyCode(image[i].photcode))) continue;
@@ -170,15 +174,15 @@
     typehash = wordhash (&image[i].coords.ctype[4]);
 
-    if (photcode && SOLO_MOSAIC) {
+    if (photcode && SOLO_MOSAIC && (Nmosaic >= 0)) {
       // mosaic (DIS) images are not currently given a photcode : plot these via the WRP entries
       /* DIS images represent a field, not a chip */
       if (typehash != ChipImage) continue;
       if (foundMosaic[Nmosaic]) continue;
-      x[0] = -0.5*image[Nmosaic].NX; y[0] = -0.5*image[Nmosaic].NY;
-      x[1] = +0.5*image[Nmosaic].NX; y[1] = -0.5*image[Nmosaic].NY;
-      x[2] = +0.5*image[Nmosaic].NX; y[2] = +0.5*image[Nmosaic].NY;
-      x[3] = -0.5*image[Nmosaic].NX; y[3] = +0.5*image[Nmosaic].NY;
+      x[0] = -0.5*mosaic->NX; y[0] = -0.5*mosaic->NY;
+      x[1] = +0.5*mosaic->NX; y[1] = -0.5*mosaic->NY;
+      x[2] = +0.5*mosaic->NX; y[2] = +0.5*mosaic->NY;
+      x[3] = -0.5*mosaic->NX; y[3] = +0.5*mosaic->NY;
       for (j = 0; j < Npts; j++) {
-	status = XY_to_RD (&r[j], &d[j], x[j], y[j], &image[Nmosaic].coords);
+	status = XY_to_RD (&r[j], &d[j], x[j], y[j], &mosaic->coords);
 	if (!status) break;
 	r[j] = ohana_normalize_angle (r[j]);
@@ -198,6 +202,7 @@
 
     /* DIS images represent a field, not a chip */
-    if ((typehash == DistortImage) && !WITH_MOSAIC) continue;
-    if ((typehash != DistortImage) &&  SOLO_MOSAIC) continue;
+    if ((typehash == DistortImage) && !WITH_MOSAIC) continue; // do not plot the mosaic images
+    if ((typehash != DistortImage) &&  SOLO_MOSAIC) continue; // plot only the mosaic images
+
     if (typehash == DistortImage) {
       x[0] = -0.5*image[i].NX; y[0] = -0.5*image[i].NY;
Index: /trunk/Ohana/src/opihi/dvo/imbox.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imbox.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/imbox.c	(revision 37807)
@@ -43,10 +43,18 @@
   SetVector (&Yvec, OPIHI_FLT, NPTS);
 
+  mosaic.ctype[0] = 0;  
+
   while (gfits_fread_header (f, &header)) {
     if (!GetCoords (&coords, &header)) goto skip;
     if (!SOLO_PHU && !strcmp (&coords.ctype[4], "-DIS")) {
       mosaic = coords;
-      RegisterMosaic (&mosaic);
       goto skip;
+    }
+    if (!strcmp (&coords.ctype[4], "-WRP")) {
+      if (!mosaic.ctype[0]) {
+	fprintf (stderr, "PHU mosaic not found\n");
+	return FALSE;
+      }
+      coords.mosaic = &mosaic;
     }
 
Index: /trunk/Ohana/src/opihi/dvo/imdata.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imdata.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/imdata.c	(revision 37807)
@@ -87,5 +87,5 @@
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
   image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
   GetTimeFormat (&TimeReference, &TimeFormat);
 
@@ -103,5 +103,4 @@
     I = subset[i];
 
-    if (!FindMosaicForImage (image, Nimage, I)) continue;
     skyset = SkyListByImage (sky, -1, &image[I]);
 
Index: /trunk/Ohana/src/opihi/dvo/imdense.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imdense.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/imdense.c	(revision 37807)
@@ -22,5 +22,5 @@
 
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   Rmin = graphmode.coords.crval1 - 182.0;
@@ -42,5 +42,4 @@
     /* project this image to screen display coords */
     status = FALSE;
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     XY_to_RD (&r, &d, x, y, &image[i].coords);
     r = ohana_normalize_angle (r);
Index: /trunk/Ohana/src/opihi/dvo/imextract.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imextract.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/imextract.c	(revision 37807)
@@ -90,5 +90,5 @@
 
   if ((image = LoadImagesDVO (&Nimage)) == NULL) goto escape;
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
   dbExtractImagesInit (); 
 
Index: /trunk/Ohana/src/opihi/dvo/imlist.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imlist.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/imlist.c	(revision 37807)
@@ -38,10 +38,4 @@
   }
 
-  // int RegionSelect = FALSE;
-  // if ((N = get_argument (argc, argv, "-region"))) {
-  //   remove_argument (N, &argc, argv);
-  //   RegionSelect = TRUE;
-  // }
-
   PhotcodeValue = NULL;
   PhotcodeSelect = FALSE;
@@ -76,6 +70,13 @@
   }
 
+  int MAX_LIST = -1;
+  if ((N = get_argument (argc, argv, "-max-list"))) {
+    remove_argument (N, &argc, argv);
+    MAX_LIST = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
   if (argc != 1) {
-    gprint (GP_ERR, "USAGE: image [-time start range] [-region] [-name string]\n");
+    gprint (GP_ERR, "USAGE: image [-time start range] [-region] [-name string] [-photcode code] [-Nphotcode N] [-max-list N]\n");
     return (FALSE);
   }
@@ -83,9 +84,9 @@
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
   image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
-  BuildChipMatch (image, Nimage);
+  MAX_LIST = MAX_LIST < 0 ? Nsubset : MIN(MAX_LIST, Nsubset);
 
   GetTimeFormat (&TimeReference, &TimeFormat);
 
-  for (j = 0; j < Nsubset; j++) {
+  for (j = 0; j < MAX_LIST; j++) {
     i = subset[j];
     if (NameSelect && (strstr (image[i].name, name) == (char *) NULL)) continue;
@@ -97,5 +98,4 @@
       }
     }
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     t = TimeValue (image[i].tzero, TimeReference, TimeFormat);
     if (!strcmp(&image[i].coords.ctype[4], "-DIS")) {
Index: /trunk/Ohana/src/opihi/dvo/imstats.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imstats.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/imstats.c	(revision 37807)
@@ -31,5 +31,5 @@
 
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   /* assign vector values */
@@ -44,5 +44,4 @@
     else 
       Yvec.elements.Flt[i] = image[i].dMcal;
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     XY_to_RD (&r, &d, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
     gprint (GP_ERR, "%d %8.4f %8.4f %10d %6d  %5.3f %6.3f %6.3f\n", 
Index: /trunk/Ohana/src/opihi/dvo/init.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/init.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/init.c	(revision 37807)
@@ -7,4 +7,5 @@
 int calmextract     PROTO((int, char **));
 int catlog          PROTO((int, char **));
+int catname         PROTO((int, char **));
 int catdir_define   PROTO((int, char **));
 int ccd             PROTO((int, char **));
@@ -68,4 +69,5 @@
 //  {1, "calextract",  calextract,   "extract photometry calibration"},
 //  {1, "calmextract", calmextract,  "extract photometry calibration"},
+  {1, "catname",     catname,      "list catalog files by name"},
   {1, "catdir",      catdir_define,"re-define CATDIR"},
 //  {1, "ccd",         ccd,          "plot color-color diagram"},
Index: /trunk/Ohana/src/opihi/dvo/mextract.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/mextract.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/mextract.c	(revision 37807)
@@ -211,4 +211,7 @@
   }
 
+  // int needLensing = dbFieldNeedLensing (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, FALSE);
+
   // grab data from all selected sky regions
   Signal = signal (SIGINT, handle_interrupt);
@@ -227,4 +230,6 @@
     catalog.filename = HOST_ID ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_SECF;
+    // catalog.catflags |= needLensing ? LOAD_LENSING : SKIP_LENSING;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = Nsecfilt;
 
@@ -259,6 +264,18 @@
 	// extract the relevant values for this measurement
 	dbExtractMeasuresInitMeas (); // reset counters for saved fields  (costs very little
+
+	Average *average = &catalog.average[j];
+
+	int Nstarpar = average->starparOffset;
+	StarPar *starpar = needStarpar ? &catalog.starpar[Nstarpar] : NULL;
+
+	// int Nlensing = average->lensobjOffset;
+	// Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+	int Nsec = j*Nsecfilt;
+	SecFilt *secfilt = &catalog.secfilt[Nsec];
+
 	for (n = 0; n < Nfields; n++) {
-	  values[n] = dbExtractMeasures (&catalog.average[j], &catalog.secfilt[j*Nsecfilt], &catalog.measure[m], &fields[n]);
+	  values[n] = dbExtractMeasures (average, secfilt, &catalog.measure[m], NULL, starpar, &fields[n]);
 	}
 	// fprintf (stderr, "object: ave: %f, cat: %f, averef %d\n", fields[n].name, values[2], values[3], catalog.measure[m].averef);
Index: /trunk/Ohana/src/opihi/dvo/mmatch.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/mmatch.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/mmatch.c	(revision 37807)
@@ -233,4 +233,7 @@
   ALLOCATE (index, off_t, Nelem);
 
+  // int needLensing = dbFieldNeedLensing (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, FALSE);
+
   // grab data from all selected sky regions
   Signal = signal (SIGINT, handle_interrupt);
@@ -246,4 +249,5 @@
     catalog.filename = HOST_ID ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_SECF | LOAD_MEAS;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = Nsecfilt;
 
@@ -282,6 +286,18 @@
 	// extract the relevant values for this measurement
 	dbExtractMeasuresInitMeas (); // reset counters for saved fields  (costs very little
+
+	Average *average = &catalog.average[Ncat];
+
+	int Nstarpar = average->starparOffset;
+	StarPar *starpar = needStarpar ? &catalog.starpar[Nstarpar] : NULL;
+
+	// int Nlensing = average->lensobjOffset;
+	// Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+	int Nsec = Ncat*Nsecfilt;
+	SecFilt *secfilt = &catalog.secfilt[Nsec];
+
 	for (n = 0; n < Nfields; n++) {
-	  values[n] = dbExtractMeasures (&catalog.average[Ncat], &catalog.secfilt[Ncat*Nsecfilt], &catalog.measure[m], &fields[n]);
+	  values[n] = dbExtractMeasures (average, secfilt, &catalog.measure[m], NULL, starpar, &fields[n]);
 	}
 
Index: /trunk/Ohana/src/opihi/dvo/mmextract.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/mmextract.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/mmextract.c	(revision 37807)
@@ -174,4 +174,7 @@
   }
 
+  // int needLensing = dbFieldNeedLensing (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, FALSE);
+
   // grab data from all selected sky regions
   Signal = signal (SIGINT, handle_interrupt);
@@ -213,7 +216,19 @@
 	// extract the relevant values for this measurement 
 	dbExtractMeasuresInitMeas (); // reset counters for saved fields 
+
+	Average *average = &catalog.average[j];
+
+	int Nstarpar = average->starparOffset;
+	StarPar *starpar = needStarpar ? &catalog.starpar[Nstarpar] : NULL;
+
+	// int Nlensing = average->lensobjOffset;
+	// Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+	int Nsec = j*Nsecfilt;
+	SecFilt *secfilt = &catalog.secfilt[Nsec];
+
 	for (n = 0; n < Nfields; n++) {
 	  // values needs to be a pointer to a type with FLT and INT (with a union, we would save a bit of memory...)
-	  values[n] = dbExtractMeasures (&catalog.average[j], &catalog.secfilt[j*Nsecfilt], &catalog.measure[m], &fields[n]);
+	  values[n] = dbExtractMeasures (average, secfilt, &catalog.measure[m], NULL, starpar, &fields[n]);
 	}
 	// fprintf (stderr, "object: ave: %f, cat: %f, averef %d\n", fields[n].name, values[2], values[3], catalog.measure[m].averef);
Index: /trunk/Ohana/src/opihi/dvo/objectcoverage.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/objectcoverage.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/objectcoverage.c	(revision 37807)
@@ -147,15 +147,12 @@
   strcpy (buf[0].file, "(empty)");
 
+  InitCoords (&coords, projection);
   coords.crval1 = RaCenter;
   coords.crval2 = DecCenter;
   coords.crpix1 = 0.5*Nx;
   coords.crpix2 = 0.5*Ny;
-  strcpy (coords.ctype, projection);
-  coords.pc1_1 = -1.0;
+  coords.pc1_1 = -1.0; // sky parity
   coords.pc2_2 = +1.0;
-  coords.pc1_2 =  0.0;
-  coords.pc2_1 =  0.0;
   coords.cdelt1 = coords.cdelt2 = pixscale;
-  coords.Npolyterms = 0;
 
   PutCoords (&coords, &buf[0].header);
@@ -166,6 +163,5 @@
     for (xs = 0; xs < Nx; xs++) {
       status = XY_to_RD (&r, &d, (double)(xs), (double)(ys), &coords);
-      status &= (r >= 0);
-      status &= (r <= 360);
+      r = ohana_normalize_angle (r);
       if (status) {
 	V[ys*Nx + xs] = ShowDensity ?  0 : 2;
Index: /trunk/Ohana/src/opihi/dvo/showtile.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/showtile.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/showtile.c	(revision 37807)
@@ -28,11 +28,5 @@
 
   /* reference for coords is this image */
-  coords.crpix1 = coords.crpix2 = 0.0;
-  coords.crval1 = coords.crval2 = 0.0;
-  coords.cdelt1 = coords.cdelt2 = 1.0;
-  coords.pc1_1  = coords.pc2_2  = 1.0;
-  coords.pc1_2  = coords.pc2_1  = 0.0;
-  coords.Npolyterms = 0;
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
   
   /* fill in top-left region */
Index: /trunk/Ohana/src/opihi/dvo/simage.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/simage.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/simage.c	(revision 37807)
@@ -42,37 +42,8 @@
   }
   /* get astrometry information */
-  strcpy (coords.ctype, "NONE");
-  gfits_scan (&header, "CTYPE2",   "%s",  1, coords.ctype);
-  if (strcmp (coords.ctype, "DEC--PLY")) {
-    gprint (GP_ERR, "ERROR: wrong astrometric info in header\n");
+  if (!GetCoords (&coords, &header)) {
+    gprint (GP_ERR, "ERROR: can't get coord info from header\n");
     return (FALSE);
   }
-  gfits_scan (&header, "CDELT1",   "%f", 1, &coords.cdelt1); 
-  gfits_scan (&header, "CDELT2",   "%f", 1, &coords.cdelt2);
-  gfits_scan (&header, "CRVAL1",   "%lf", 1, &coords.crval1);
-  gfits_scan (&header, "CRVAL2",   "%lf", 1, &coords.crval2);  
-  gfits_scan (&header, "CRPIX1",   "%f", 1, &coords.crpix1);
-  gfits_scan (&header, "CRPIX2",   "%f", 1, &coords.crpix2);
-  gfits_scan (&header, "PC001001", "%f", 1, &coords.pc1_1);
-  gfits_scan (&header, "PC001002", "%f", 1, &coords.pc1_2);
-  gfits_scan (&header, "PC002001", "%f", 1, &coords.pc2_1);
-  gfits_scan (&header, "PC002002", "%f", 1, &coords.pc2_2);
-  /* RA Terms */
-  gfits_scan (&header, "PCA1X2Y0", "%f", 1, &coords.polyterms[0][0]);
-  gfits_scan (&header, "PCA1X1Y1", "%f", 1, &coords.polyterms[1][0]);
-  gfits_scan (&header, "PCA1X0Y2", "%f", 1, &coords.polyterms[2][0]);
-  gfits_scan (&header, "PCA1X3Y0", "%f", 1, &coords.polyterms[3][0]);
-  gfits_scan (&header, "PCA1X2Y1", "%f", 1, &coords.polyterms[4][0]);
-  gfits_scan (&header, "PCA1X1Y2", "%f", 1, &coords.polyterms[5][0]);
-  gfits_scan (&header, "PCA1X0Y3", "%f", 1, &coords.polyterms[6][0]);
-  /* Dec Terms */			    
-  gfits_scan (&header, "PCA2X2Y0", "%f", 1, &coords.polyterms[0][1]);
-  gfits_scan (&header, "PCA2X1Y1", "%f", 1, &coords.polyterms[1][1]);
-  gfits_scan (&header, "PCA2X0Y2", "%f", 1, &coords.polyterms[2][1]);
-  gfits_scan (&header, "PCA2X3Y0", "%f", 1, &coords.polyterms[3][1]);
-  gfits_scan (&header, "PCA2X2Y1", "%f", 1, &coords.polyterms[4][1]);
-  gfits_scan (&header, "PCA2X1Y2", "%f", 1, &coords.polyterms[5][1]);
-  gfits_scan (&header, "PCA2X0Y3", "%f", 1, &coords.polyterms[6][1]);
-  coords.Npolyterms = 2; /* how many do we use? */
 
   /* find number of stars */
Index: /trunk/Ohana/src/opihi/dvo/skycoverage.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/skycoverage.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/skycoverage.c	(revision 37807)
@@ -204,15 +204,12 @@
   strcpy (buf[0].file, "(empty)");
 
+  InitCoords (&coords, projection);
   coords.crval1 = RaCenter;
   coords.crval2 = DecCenter;
   coords.crpix1 = 0.5*Nx;
   coords.crpix2 = 0.5*Ny;
-  strcpy (coords.ctype, projection);
-  coords.pc1_1 = -1.0;
+  coords.pc1_1 = -1.0; // sky parity
   coords.pc2_2 = +1.0;
-  coords.pc1_2 =  0.0;
-  coords.pc2_1 =  0.0;
   coords.cdelt1 = coords.cdelt2 = pixscale;
-  coords.Npolyterms = 0;
 
   PutCoords (&coords, &buf[0].header);
@@ -222,5 +219,5 @@
       return (FALSE);
   }
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   V = (float *)buf[0].matrix.buffer;
@@ -230,6 +227,6 @@
     for (xs = 0; xs < Nx; xs++) {
       status = XY_to_RD (&r, &d, (double)(xs), (double)(ys), &coords);
-      status &= (r >= 0);
-      status &= (r <= 360);
+      r = ohana_normalize_angle (r);
+
       // are we in a part of the projection covering the sky or not?
       if (status) {
@@ -284,6 +281,4 @@
       }
     }
-
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
 
     typehash = wordhash (&image[i].coords.ctype[4]);
Index: /trunk/Ohana/src/opihi/dvo/subpix.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/subpix.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/dvo/subpix.c	(revision 37807)
@@ -115,8 +115,7 @@
   /* load all images, extract those touching Ra, Dec */
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   for (i = 0; i < Nimage; i++) {
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     status = RD_to_XY (&X, &Y, Ra, Dec, &image[i].coords);
     if (!status || (X < 0) || (X > image[i].NX) || (Y < 0) || (Y > image[i].NY)) continue;
@@ -134,5 +133,4 @@
   for (i = 0; i < Nsub; i++) {
     I = index[i];
-    if (!FindMosaicForImage (image, Nimage, I)) continue;
     Timage = image[I].tzero;
     for (j = 0; j < Nmeasure; j++) {
Index: /trunk/Ohana/src/opihi/include/astro.h
===================================================================
--- /trunk/Ohana/src/opihi/include/astro.h	(revision 37806)
+++ /trunk/Ohana/src/opihi/include/astro.h	(revision 37807)
@@ -12,25 +12,3 @@
 void FreeAstro (void);
 
-typedef struct {
-  int    isIdentity;	      // identity transformation 
-  double phi;		      // saved in radians
-  double Xo;		      // saved in radians
-  double xo;		      // saved in degrees
-  double sin_phi_cos_Xo;      // pre-computed values
-  double sin_phi_sin_Xo;      // pre-computed values
-  double cos_phi_cos_Xo;      // pre-computed values
-  double cos_phi_sin_Xo;      // pre-computed values
-  double cos_phi;	      // pre-computed values
-  double sin_phi;	      // pre-computed values
-  double cos_Xo;	      // pre-computed values
-  double sin_Xo;	      // pre-computed values
-} CoordTransform;
-
-#ifndef MOVED_TO_LIBDVO
-typedef enum {COORD_NONE, COORD_CELESTIAL, COORD_GALACTIC, COORD_ECLIPTIC} CoordTransformSystem;
-#endif
-
-CoordTransform *InitTransform (CoordTransformSystem input, CoordTransformSystem output);
-int ApplyTransform (double *x, double *y, double X, double Y, CoordTransform *transform);
-
 # endif
Index: /trunk/Ohana/src/opihi/include/data.h
===================================================================
--- /trunk/Ohana/src/opihi/include/data.h	(revision 37806)
+++ /trunk/Ohana/src/opihi/include/data.h	(revision 37807)
@@ -224,3 +224,6 @@
 void ListMedImages ();
 
+// in tvchannel.c
+int GetKapaChannelFromString (char *string);
+
 # endif
Index: /trunk/Ohana/src/opihi/include/dvomath.h
===================================================================
--- /trunk/Ohana/src/opihi/include/dvomath.h	(revision 37806)
+++ /trunk/Ohana/src/opihi/include/dvomath.h	(revision 37807)
@@ -187,4 +187,5 @@
 int           ListBuffersToList     PROTO((char *name));
 int           CreateBuffer          PROTO((Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale));
+int           ResetBuffer           PROTO((Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale));
 Buffer       *SelectBuffer          PROTO((char *name, int mode, int verbose));
 void          dump_buffers          PROTO((int n));  /* deprecated? */
Index: /trunk/Ohana/src/opihi/lib.shell/BufferOps.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.shell/BufferOps.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/lib.shell/BufferOps.c	(revision 37807)
@@ -88,4 +88,12 @@
 }
 
+int ResetBuffer (Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale) {
+
+  gfits_free_matrix (&buf[0].matrix);
+  gfits_free_header (&buf[0].header);
+  CreateBuffer (buf, Nx, Ny, bitpix, bzero, bscale);
+  return TRUE;
+}
+
 int CreateBuffer (Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale) {
 
Index: /trunk/Ohana/src/opihi/pantasks/controller_status.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/controller_status.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/pantasks/controller_status.c	(revision 37807)
@@ -3,10 +3,17 @@
 int controller_status (int argc, char **argv) {
 
-  int status;
-  char command[1024];
+  int i, status;
+  char command[1024], tmpline[1024];
   IOBuffer buffer;
 
-  if (argc != 1) {
-    gprint (GP_ERR, "USAGE: controller status\n");
+  if ((argc > 1) && (!strcasecmp(argv[1], "help")))  {
+    gprint (GP_ERR, "USAGE: controller status [options]\n");
+    gprint (GP_ERR, "  OPTIONS:\n");
+    gprint (GP_ERR, "  -cmd command\n");
+    gprint (GP_ERR, "  -host hostname\n");
+    gprint (GP_ERR, "  -state state\n");
+    gprint (GP_ERR, "  -age seconds\n");
+    gprint (GP_ERR, "  +jobs [-nohost]\n");
+    gprint (GP_ERR, "  +host [-nojobs]\n");
     return (FALSE);
   }
@@ -19,6 +26,10 @@
   }
 
+  snprintf (command, 1024, "status");
+  for (i = 1; i < argc; i++) {
+    snprintf (tmpline, 1024, "%s %s", command, argv[i]);
+    strcpy (command, tmpline);
+  }
 
-  sprintf (command, "status");
   InitIOBuffer (&buffer, 0x100);
 
Index: /trunk/Ohana/src/opihi/pantasks/test/runmacro.sh
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/test/runmacro.sh	(revision 37807)
+++ /trunk/Ohana/src/opihi/pantasks/test/runmacro.sh	(revision 37807)
@@ -0,0 +1,94 @@
+
+## a basic test of memory allocation
+
+exec rm -f tmp.txt
+verbose off
+$Npass = 0
+
+macro sample.macro
+  echo "in macro"
+  exec date >> tmp.txt
+end
+
+# a basic task which just runs 'echo'
+task	       basic
+  command      date "+starting at %H:%M:%S.%N"
+  host         local
+
+  periods      -poll 0.1
+  periods      -exec 0.2
+  periods      -timeout 2
+  
+  trange       -reset
+  trange       18:38:45 18:39:10 -nmax 2
+  # trange       23:54:45 23:59:59
+  # trange       00:43:15 00:43:20
+  # trange       00:42:20 00:42:30
+  # trange       14:41:30 14:41:40
+
+  stdout tmp.txt
+  stderr tmp.txt
+
+  task.exec
+    sample.macro       
+  end
+
+  # success
+  task.exit    0
+    $Npass ++
+  end
+
+  # default exit status
+  task.exit    default
+    echo       "basic: exit status: $EXIT"
+  end
+
+  # operation times out?
+  task.exit    timeout
+    echo       "basic: timeout"
+  end
+end
+
+macro memcheck.init
+ list word -x "ps -p $PID -o rss"
+ $startmem = $word:1
+end
+macro memcheck
+ list word -x "ps -p $PID -o rss"
+ $endmem = $word:1
+ # echo growth: {$endmem - $startmem}
+end
+
+macro done
+ stop
+ date -var dtime -seconds -reftime $start
+ if ($VERBOSE > 3)
+  status
+  exec wc -l tmp.txt
+ end
+
+ $answer = `wc -l tmp.txt`
+ list word -split $answer
+ if ($word:0 != $Npass) 
+   echo "missing lines in tmp.txt: $word:0"
+ end
+ if ($Npass/$dtime < 4.2) 
+   echo "tasks running too slow: {$Npass/$dtime}"
+ end
+
+ memcheck
+
+ if ($endmem - $startmem > 10)
+   $PASS = 0
+   echo "failed memcheck"
+   echo "growth: {$endmem-$startmem}"
+   echo "kB/loop: {($endmem-$startmem)/10000}"
+ end
+end
+
+memcheck.init
+echo "starting memcheck"
+echo "wait a few seconds and type 'done'"
+
+run
+date -var start -seconds
Index: /trunk/Ohana/src/opihi/pantasks/test/trange.sh
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/test/trange.sh	(revision 37807)
+++ /trunk/Ohana/src/opihi/pantasks/test/trange.sh	(revision 37807)
@@ -0,0 +1,85 @@
+
+## a basic test of memory allocation
+
+exec rm -f tmp.txt
+verbose off
+$Npass = 0
+
+# a basic task which just runs 'echo'
+task	       basic
+  command      date +%H:%M:%S.%N
+  host         local
+
+  periods      -poll 0.1
+  periods      -exec 0.2
+  periods      -timeout 2
+  
+  #trange       00:54:45 00:54:50 -nmax 100
+  trange       -reset
+  trange       23:54:45 23:59:59
+  # trange       00:43:15 00:43:20
+  # trange       00:42:20 00:42:30
+  # trange       14:41:30 14:41:40
+
+  stdout tmp.txt
+  stderr tmp.txt
+
+  # success
+  task.exit    0
+    $Npass ++
+  end
+
+  # default exit status
+  task.exit    default
+    echo       "basic: exit status: $EXIT"
+  end
+
+  # operation times out?
+  task.exit    timeout
+    echo       "basic: timeout"
+  end
+end
+
+macro memcheck.init
+ list word -x "ps -p $PID -o rss"
+ $startmem = $word:1
+end
+macro memcheck
+ list word -x "ps -p $PID -o rss"
+ $endmem = $word:1
+ # echo growth: {$endmem - $startmem}
+end
+
+macro done
+ stop
+ date -var dtime -seconds -reftime $start
+ if ($VERBOSE > 3)
+  status
+  exec wc -l tmp.txt
+ end
+
+ $answer = `wc -l tmp.txt`
+ list word -split $answer
+ if ($word:0 != $Npass) 
+   echo "missing lines in tmp.txt: $word:0"
+ end
+ if ($Npass/$dtime < 4.2) 
+   echo "tasks running too slow: {$Npass/$dtime}"
+ end
+
+ memcheck
+
+ if ($endmem - $startmem > 10)
+   $PASS = 0
+   echo "failed memcheck"
+   echo "growth: {$endmem-$startmem}"
+   echo "kB/loop: {($endmem-$startmem)/10000}"
+ end
+end
+
+memcheck.init
+echo "starting memcheck"
+echo "wait a few seconds and type 'done'"
+
+run
+date -var start -seconds
Index: /trunk/Ohana/src/opihi/pcontrol/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/pcontrol/Makefile	(revision 37806)
+++ /trunk/Ohana/src/opihi/pcontrol/Makefile	(revision 37807)
@@ -11,5 +11,5 @@
 
 # programs may add their own internal requirements here
-LIBS1         = -lkapa -lFITS -lohana
+LIBS1         = -ldvo -lkapa -lFITS -lohana
 LIBS2         = -lbasiccmd -lshell -ldata 
 FULL_CFLAGS   = $(BASE_CFLAGS)
Index: /trunk/Ohana/src/opihi/pcontrol/status.c
===================================================================
--- /trunk/Ohana/src/opihi/pcontrol/status.c	(revision 37806)
+++ /trunk/Ohana/src/opihi/pcontrol/status.c	(revision 37807)
@@ -85,5 +85,5 @@
 
 usage:
-  gprint (GP_ERR, "USAGE: status [-cmd command] [-host hostname] [-state state] [-age seconds] [+jobs,-nohost] [+host,-nojobs]\n");
+  gprint (GP_LOG, "USAGE: status [-cmd command] [-host hostname] [-state state] [-age seconds] [+jobs,-nohost] [+host,-nojobs]\n");
   return FALSE;
 }
Index: /trunk/Ohana/src/photdbc/include/photdbc.h
===================================================================
--- /trunk/Ohana/src/photdbc/include/photdbc.h	(revision 37806)
+++ /trunk/Ohana/src/photdbc/include/photdbc.h	(revision 37807)
@@ -31,7 +31,9 @@
 int    PARALLEL_MANUAL;
 int    PARALLEL_SERIAL;
+char  *PARALLEL_OUTHOSTS;
 
 int          HOST_ID;
 char        *HOSTDIR;
+char        *HOSTDIR_OUTPUT;
 
 // need to get RADIUS from Config 
@@ -40,4 +42,6 @@
 
 /* global variables */
+int    SKIP_IMAGES;
+int    ONLY_IMAGES;
 int    SHOW_PARAMS;
 int    VERBOSE;
Index: /trunk/Ohana/src/photdbc/src/CopyToHostLocation.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/CopyToHostLocation.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/CopyToHostLocation.c	(revision 37807)
@@ -54,6 +54,7 @@
     }
 
-    char extname[4][16] = {"cpm", "cpt", "cps", "cpn"};
-    for (j = 0; success && (j < 4); j++) {
+    # define NEXTNAME 7
+    char extname[NEXTNAME][16] = {"cpm", "cpt", "cps", "cpn", "cpx", "cpy", "cpz"};
+    for (j = 0; success && (j < NEXTNAME); j++) {
 
       // set the in and out table names
Index: /trunk/Ohana/src/photdbc/src/args.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/args.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/args.c	(revision 37807)
@@ -15,4 +15,19 @@
     remove_argument (N, &argc, argv);
     SHOW_PARAMS = TRUE;
+  }
+
+  SKIP_IMAGES = FALSE;
+  if ((N = get_argument (argc, argv, "-skip-images"))) {
+    SKIP_IMAGES = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  ONLY_IMAGES = FALSE;
+  if ((N = get_argument (argc, argv, "-only-images"))) {
+    if (SKIP_IMAGES) {
+      fprintf (stderr, "-skip-images and -only-images cannot be used together\n");
+    }
+    ONLY_IMAGES = TRUE;
+    remove_argument (N, &argc, argv);
   }
 
@@ -131,4 +146,14 @@
   }
 
+  // XXX for the moment, make this selection manual.  it needs to be automatic 
+  // based on the state of the SkyTable
+  HOSTDIR_OUTPUT = NULL;
+  PARALLEL_OUTHOSTS = NULL;
+  if ((N = get_argument (argc, argv, "-parallel-output"))) {
+    remove_argument (N, &argc, argv);
+    PARALLEL_OUTHOSTS = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
   if (argc != 2) usage();
 
@@ -171,4 +196,11 @@
   if (!HOSTDIR) usage();
 
+  HOSTDIR_OUTPUT = NULL;
+  if ((N = get_argument (argc, argv, "-hostdir-output"))) {
+    remove_argument (N, &argc, argv);
+    HOSTDIR_OUTPUT = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
   VERBOSE = FALSE;
   if ((N = get_argument (argc, argv, "-v"))) {
@@ -289,4 +321,10 @@
   fprintf (stderr, " photdbc (output)\n\n");
 
+  fprintf (stderr, " parallel options:\n");
+  fprintf (stderr, "  -parallel:\n");
+  fprintf (stderr, "  -parallel-output HostTable : HostTable should be relative path in OUTPUT directory\n");
+  fprintf (stderr, "  -parallel-manual\n");
+  fprintf (stderr, "  -parallel-serial\n\n");
+
   fprintf (stderr, " allowed filters / restrictions include:\n\n");
 
@@ -303,5 +341,9 @@
   fprintf (stderr, " option options:\n");
   fprintf (stderr, " -v : verbose mode\n");
-  fprintf (stderr, " -params : list the current parameters\n\n");
+  fprintf (stderr, " -params : list the current parameters\n");
+  fprintf (stderr, " -skip-images\n");
+  fprintf (stderr, " -only-images\n");
+  fprintf (stderr, " -set-format (catformat)\n");
+  fprintf (stderr, " -set-mode (catmode)\n");
 
   fprintf (stderr, "ptolemy.rc config values used by this program:\n");
Index: /trunk/Ohana/src/photdbc/src/copy_images.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/copy_images.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/copy_images.c	(revision 37807)
@@ -52,5 +52,33 @@
   }
 
+  BuildChipMatch (image, Nimage);
+  // MARKTIME("build chip match: %f sec\n", dtime);
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", CATDIR);
+  AstromOffsetTable *table_src = AstromOffsetMapLoad (mapfile, VERBOSE);
+
+  // assign images.coords.offsetMap -> table->map[i]
+  if (table_src) {
+    AstromOffsetTableMatchChips (image, Nimage, table_src);
+  }
+
   subset = select_images (skylist, image, Nimage, &LineNumber, &Nsubset, FALSE);
+
+  if (table_src) {
+    AstromOffsetTable *table_tgt = AstromOffsetTableInit ();
+    
+    off_t i;
+    for (i = 0; i < Nsubset; i++) {
+      if (!subset[i].coords.offsetMap) continue;
+      if (subset[i].coords.Npolyterms != -1) continue; // assert on this?
+      AstromOffsetTableAddMapFromImage (table_tgt, &subset[i]);
+    }
+    snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", outdir);
+    AstromOffsetMapSave (table_tgt, mapfile);
+    free (table_tgt->imageIDtoTableSeq);
+    free (table_tgt->map);
+    free (table_tgt);
+  }
 
   dvo_image_addrows (&out, subset, Nsubset);
@@ -73,4 +101,7 @@
   SavePhotcodesFITS (filename);
 
+  // free the astrom offset tables
+  AstromOffsetTableFree (table_src);
+
   return (TRUE);
 }
Index: /trunk/Ohana/src/photdbc/src/find_images.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/find_images.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/find_images.c	(revision 37807)
@@ -19,11 +19,8 @@
 
   /* we make positional comparisons in the projection of catalog */
+  InitCoords (&tcoords, "DEC--TAN");
   tcoords.crval1 = 0.5*(region[0].RA[0]  + region[0].RA[1]);
   tcoords.crval2 = 0.5*(region[0].DEC[0] + region[0].DEC[1]);
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1  = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2  = tcoords.pc2_1 = 0.0;
-  strcpy (tcoords.ctype, "DEC--TAN");
 
   timage = gfits_table_get_Image (&db[0].ftable, &Ntimage, &db[0].swapped);
Index: /trunk/Ohana/src/photdbc/src/join_stars.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/join_stars.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/join_stars.c	(revision 37807)
@@ -50,12 +50,8 @@
 
   /* coordinate system for projection */
+  InitCoords (&tcoords, "DEC--TAN");
   tcoords.crval1 = Rmid;
   tcoords.crval2 = Dmid;
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1  = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2  = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 0;
-  strcpy (tcoords.ctype, "DEC--TAN");
   
   /* project & sort coordinates in local linear frame */
Index: /trunk/Ohana/src/photdbc/src/photdbc.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/photdbc.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/photdbc.c	(revision 37807)
@@ -15,8 +15,12 @@
 
   // load and copy the image table
-  copy_images (argv[1], skylist);
+  if (!SKIP_IMAGES) {
+    copy_images (argv[1], skylist);
+  }
 
   // hostID is 0 for master program
-  photdbc_catalogs (argv[1], skylist, 0);
+  if (!ONLY_IMAGES) {
+    photdbc_catalogs (argv[1], skylist, 0);
+  }
 
   char *skyfile = SkyTableFilename (argv[1]);
Index: /trunk/Ohana/src/photdbc/src/photdbc_catalogs.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/photdbc_catalogs.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/photdbc_catalogs.c	(revision 37807)
@@ -42,4 +42,10 @@
     snprintf (outfile, DVO_MAX_PATH, "%s/%s.cpt", outroot, skylist[0].regions[i]->name);
     outcatalog.filename = outfile;
+
+    if (HOSTDIR_OUTPUT) {
+      // parallel-ouput puts the output in a per-host directory (keeps the source sky partition)
+      snprintf (outfile, DVO_MAX_PATH, "%s/%s.cpt", HOSTDIR_OUTPUT, skylist[0].regions[i]->name);
+    }
+
     if (outcatalog.filename == NULL) Shutdown ("error with input catalog name");
 
@@ -95,4 +101,12 @@
   }    
 
+  HostTable *table_output = NULL;
+  if (PARALLEL_OUTHOSTS) {
+    table_output = HostTableLoad (outroot, PARALLEL_OUTHOSTS);
+    if (!table_output) {
+      fprintf (stderr, "%s not found in %s, please create\n", PARALLEL_OUTHOSTS, outroot);
+    }
+  }
+
   int i;
   for (i = 0; i < table->Nhosts; i++) {
@@ -123,4 +137,11 @@
     if (CATFORMAT)          { snprintf (tmpline, DVO_MAX_PATH, "%s -set-format %s",    command, CATFORMAT); 	                strcpy (command, tmpline); }
     if (CATMODE)            { snprintf (tmpline, DVO_MAX_PATH, "%s -set-mode %s",      command, CATMODE);        	        strcpy (command, tmpline); }
+
+    if (PARALLEL_OUTHOSTS) {
+      tmppath = abspath (table_output->hosts[i].pathname, DVO_MAX_PATH);
+      free  (table_output->hosts[i].pathname);
+      table_output->hosts[i].pathname = tmppath;
+      snprintf (tmpline, DVO_MAX_PATH, "%s -hostdir-output %s", command, table_output->hosts[i].pathname); strcpy (command, tmpline); 
+    }
 
     fprintf (stderr, "command: %s\n", command);
Index: /trunk/Ohana/src/photdbc/src/select_images.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/select_images.c	(revision 37806)
+++ /trunk/Ohana/src/photdbc/src/select_images.c	(revision 37807)
@@ -34,9 +34,6 @@
 
   // the comparison is made in the catalog local projection. below we set crval1,2
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
+  InitCoords (&tcoords, "DEC--TAN");
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1  = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2  = tcoords.pc2_1 = 0.0;
-  strcpy (tcoords.ctype, "DEC--TAN");
 
   ALLOCATE (skycoords, SkyRegionCoords, skylist[0].Nregions);
@@ -92,6 +89,4 @@
 
   if (VERBOSE) fprintf (stderr, "finding images\n");
-  BuildChipMatch (timage, Ntimage);
-  MARKTIME("build chip match: %f sec\n", dtime);
 
   nimage = 0;
@@ -115,10 +110,4 @@
 keep_image:
 
-    // this adds 1.3 sec for 3M images
-    if (!FindMosaicForImage (timage, Ntimage, i)) {
-      fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
-
     /* define image corners - note the DIS images (mosaic phu) are special */
     if (!strcmp(&timage[i].coords.ctype[4], "-DIS")) {
Index: /trunk/Ohana/src/relastro/Makefile
===================================================================
--- /trunk/Ohana/src/relastro/Makefile	(revision 37806)
+++ /trunk/Ohana/src/relastro/Makefile	(revision 37807)
@@ -60,5 +60,4 @@
 $(SRC)/CoordOps.$(ARCH).o            \
 $(SRC)/extra.$(ARCH).o            \
-$(SRC)/FixProblemImages.$(ARCH).o    \
 $(SRC)/StarMaps.$(ARCH).o    \
 $(SRC)/high_speed_catalogs.$(ARCH).o  \
@@ -75,6 +74,14 @@
 $(SRC)/relastro_parallel_regions.$(ARCH).o \
 $(SRC)/MeanPosIO.$(ARCH).o \
+$(SRC)/MeasPosIO.$(ARCH).o \
 $(SRC)/share_mean_pos.$(ARCH).o \
+$(SRC)/share_meas_pos.$(ARCH).o \
 $(SRC)/share_images_pos.$(ARCH).o \
+$(SRC)/share_icrf_obj.$(ARCH).o \
+$(SRC)/ICRF.$(ARCH).o \
+$(SRC)/ICRFobjIO.$(ARCH).o \
+$(SRC)/FrameCorrection.$(ARCH).o \
+$(SRC)/FrameCorrectionIO.$(ARCH).o \
+$(SRC)/FrameCorrectionUtils.$(ARCH).o \
 $(SRC)/ImagePosIO.$(ARCH).o \
 $(SRC)/ImageTable.$(ARCH).o \
@@ -82,4 +89,5 @@
 $(SRC)/indexCatalogs.$(ARCH).o \
 $(SRC)/syncfile.$(ARCH).o \
+$(SRC)/client_logger.$(ARCH).o \
 $(SRC)/relastroVisual.$(ARCH).o
 
@@ -117,5 +125,4 @@
 $(SRC)/relastro_objects.$(ARCH).o    \
 $(SRC)/save_catalogs.$(ARCH).o       \
-$(SRC)/CoordOps.$(ARCH).o            \
 $(SRC)/extra.$(ARCH).o            \
 $(SRC)/high_speed_catalogs.$(ARCH).o  \
@@ -128,16 +135,8 @@
 $(SRC)/plot_scatter.$(ARCH).o	     \
 $(SRC)/plotstuff.$(ARCH).o	     \
+$(SRC)/ICRF.$(ARCH).o \
 $(SRC)/relastroVisual.$(ARCH).o \
 $(SRC)/syncfile.$(ARCH).o \
 $(SRC)/BrightCatalog.$(ARCH).o 
-
-#$(SRC)/FitChip.$(ARCH).o             \
-#$(SRC)/FitMosaic.$(ARCH).o           \
-#$(SRC)/UpdateChips.$(ARCH).o         \
-#$(SRC)/UpdateMosaic.$(ARCH).o        \
-#$(SRC)/UpdateSimple.$(ARCH).o        \
-#$(SRC)/write_coords.$(ARCH).o        \
-#$(SRC)/FixProblemImages.$(ARCH).o    \
-#$(SRC)/StarMaps.$(ARCH).o    \
 
 $(RELASTRO_CLIENT): $(INC)/relastro.h
Index: /trunk/Ohana/src/relastro/doc/pv2.txt
===================================================================
--- /trunk/Ohana/src/relastro/doc/pv2.txt	(revision 37806)
+++ /trunk/Ohana/src/relastro/doc/pv2.txt	(revision 37807)
@@ -1,2 +1,20 @@
+
+2014.10.28
+
+Going over the sequence of steps in relastro.  I'm concerned that the
+iteration is not modifying the measure.R,D values, preventing any
+actual iterative improvements.
+
+* UpdateObjects : takes measure.R,D to calculate average.R,D and
+  motion terms
+
+* ImageOps : 
+  * getImageRaw : takes measure.X,Y, applies image.coords to get (L,M),
+    		  (P,Q), etc, calculates 
+  * getImageRef : takes average.R,D, applies image.coords to get
+    		  (P,Q), (L,M), etc
+  * FitChip : fit raw.X,Y to ref.L,M, plus residual map (dX,dY)
+  
+  *** modified raw.L,M values are then dropped and not used to update measure.R.D
 
 2014.07.03
Index: /trunk/Ohana/src/relastro/include/relastro.h
===================================================================
--- /trunk/Ohana/src/relastro/include/relastro.h	(revision 37806)
+++ /trunk/Ohana/src/relastro/include/relastro.h	(revision 37807)
@@ -34,9 +34,17 @@
 
 typedef struct {
-  float R;
-  float D;
+  double R;
+  double D;
   unsigned int objID;
   unsigned int catID;
 } MeanPos;
+
+typedef struct {
+  double R;
+  double D;
+  unsigned int objID;
+  unsigned int catID;
+  unsigned int imageID;
+} MeasPos;
 
 typedef struct {
@@ -47,4 +55,6 @@
   int nFitAstrom;
   int flags;
+  float refColorBlue;
+  float refColorRed;
 } ImagePos;
 
@@ -142,4 +152,28 @@
 } FitStats;
 
+typedef struct {
+  double scale;
+  double **Roff;
+  double **Doff;
+  double  *dR;
+  int *Nra;
+  int Ndec;
+} FrameCorrectionType;
+
+typedef struct {
+  // AstromOffsetMap *map;
+  Coords *coords; // carries a pointer to the AstromOffsetMap
+  FrameCorrectionType *frame;
+} FrameCorrectionSet;
+
+
+typedef struct {
+  double *Rave;
+  double *Dave;
+  double *dRoff;
+  double *dDoff;
+  int Nicrfobj;
+} ICRFobj;
+
 /* global variables set in parameter file */
 # define DVO_MAX_PATH 1024
@@ -172,4 +206,5 @@
 double MIN_ERROR;
 
+int    IMFIT_TOO_FEW; // need more than this number of stars to fit an image
 int    IMFIT_CLIP_NITER; // number of clipping iterations to perform in FitChip
 double IMFIT_CLIP_NSIGMA; // number of sigma to clip in FitChip
@@ -188,6 +223,13 @@
 
 int    USE_FIXED_PIXCOORDS;
+int    USE_GALAXY_MODEL;
+int    USE_ICRF_CORRECT;
+
+int    USE_ICRF_LOCAL;
+int    USE_ICRF_SHFIT;
+int    USE_ICRF_POLE;
 
 int    RESET;
+int    RESET_IMAGES;
 int    RESET_BAD_IMAGES;
 int    NLOOP;
@@ -204,4 +246,5 @@
 int    PLOTDELAY;
 int    CHIPORDER;
+int    CHIPMAP;
 
 int MaxDensityUse;
@@ -242,4 +285,5 @@
 int    CLIP_THRESH;
 int USE_BASIC_CHECK;
+int APPLY_OFFSETS; // in parallel-regions mode, do not launch UpdateObjectOffsets unless -apply-offsets is called
 
 int ExcludeBogus;
@@ -397,5 +441,5 @@
 void FitSimple (StarData *raw, StarData *ref, int Nmatch, Coords *coords);
 void initObjectData (Catalog *catalog, int Ncatalog);
-int UpdateObjects (Catalog *catalog, int Ncatalog);
+int UpdateObjects (Catalog *catalog, int Ncatalog, int Nloop);
 int UpdateSimple (Catalog *catalog, int Ncatalog);
 int UpdateChips (Catalog *catalog, int Ncatalog);
@@ -417,5 +461,7 @@
 StarData *getImageRef (Catalog *catalog, int Ncatalog, off_t im, off_t *Nstars, CoordMode mode);
 StarData *getImageRaw (Catalog *catalog, int Ncatalog, off_t im, off_t *Nstars, CoordMode mode);
+int setImageRaw (Catalog *catalog, int Ncatalog, off_t im, StarData *raw, off_t Nraw, CoordMode mode);
 off_t getImageByID (off_t ID);
+int updateImageRaw (Catalog *catalog, int Ncatalog, off_t im);
 
 Mosaic *getmosaics (off_t *N);
@@ -447,15 +493,8 @@
 int relastroGetVisual(void);
 
-int FixProblemImages (SkyList *skylist);
 int *getCatlist (int *N, off_t im);
 
-void initCoords (void);
-void getOffsets (double *dPos, off_t *nPos, off_t N);
-void saveOffsets (double dPos, off_t nPos, off_t N);
-void setBadCoords (off_t N);
-int badCoords (off_t N);
-Coords *getCoords (off_t N);
-int saveCoords (Coords *coords, off_t N);
-void resetImageRaw (Catalog *catalog, int Ncatalog, off_t im);
+void SaveCoords (Coords *tgt, Coords *src);
+void RestoreCoords (Coords *tgt, Coords *src, Image *image);
 
 int high_speed_catalogs (SkyTable *sky, SkyList *skylist, int hostID, char *hostpath);
@@ -468,4 +507,5 @@
 int checkStarMap(int N);
 int createStarMap (Catalog *catalog, int Ncatalog);
+int printStarMap(int N, char *filename);
 
 int GetScatterRawRef(float *dLsig, float *dMsig, float *dRsig, int *nKeep, StarData *raw, StarData *ref, int Nstars, float SigmaLimit);
@@ -519,4 +559,12 @@
 MeanPos *MeanPosLoad(char *filename, off_t *nmeanpos);
 
+int share_meas_pos (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop);
+int slurp_meas_pos (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop);
+int set_meas_pos (MeasPos *measpos, Average *average, MeasureTiny *measure);
+MeasPos *merge_meas_pos (MeasPos *target, int *ntarget, MeasPos *source, int Nsource);
+
+int MeasPosSave(char *filename, MeasPos *measpos, off_t Nmeaspos);
+MeasPos *MeasPosLoad(char *filename, off_t *nmeaspos);
+
 int indexCatalogs (Catalog *catalog, int Ncatalog);
 int catID_and_objID_to_seq (int catID, int objID, int *catSeq, off_t *objSeq);
@@ -528,5 +576,5 @@
 
 int share_image_pos (RegionHostTable *regionHosts, int nloop);
-int slurp_image_pos (RegionHostTable *regionHosts, int nloop);
+int slurp_image_pos (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop);
 ImagePos *merge_image_pos (ImagePos *target, int *ntarget, ImagePos *source, int Nsource);
 int set_image_pos (ImagePos *image_pos, Image *image);
@@ -547,2 +595,66 @@
 int isGPC1stack (int photcode);
 int isGPC1warp (int photcode);
+
+int save_astrom_table ();
+AstromOffsetTable *get_astrom_table ();
+void put_astrom_table (AstromOffsetTable *myTable);
+
+int fit_map (AstromOffsetMap *map, StarData *raw, StarData *ref, int Npts);
+
+// ICRF QSOs : these must be marked in the database (flag on average, flag on measure)
+
+void ICRFinit ();
+int ICRFsave (int cat, int ave, int meas);
+int ICRFdata (int n, int *cat, int *ave, int *meas);
+int ICRFmax ();
+int select_catalog_ICRF (Catalog *catalog, int Ncatalog);
+
+void ICRFobjFree (ICRFobj *icrfobj);
+ICRFobj *get_ICRF_data (Catalog *catalog, int Ncatalog);
+ICRFobj *merge_icrf_obj (ICRFobj *target, ICRFobj *source);
+ICRFobj *slurp_icrf_obj (RegionHostTable *regionHosts, int nloop);
+int share_icrf_obj (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop);
+
+int ICRFobjSave(char *filename, ICRFobj *icrfobj);
+ICRFobj *ICRFobjLoad(char *filename);
+
+int SHfitWithMask (double *R, double *D, double *value, int *mask, int Npts, SHterms *fit);
+
+FrameCorrectionType *FrameCorrectionInit (double scale);
+void FrameCorrectionFree (FrameCorrectionType *frame);
+
+int FrameCorrectionParallelMaster (RegionHostTable *regionHosts);
+int FrameCorrectionParallelSlave (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop);
+int FrameCorrectionSerial (Catalog *catalog, int Ncatalog);
+
+FrameCorrectionSet *FrameCorrectionMeasure (ICRFobj *icrfobj);
+
+int FrameCorrectionFromSH (FrameCorrectionType *frame, SHterms *dR, SHterms *dD);
+
+Coords *FrameCorrectionImageToMap (Header *header, Matrix *matrix, Coords *coords, int raDirection);
+int FrameCorrectionMapToImage (Header *header, Matrix *matrix, Coords *coords, int raDirection);
+int FrameCorrectionSHtoImage (Header *header, Matrix *matrix, FrameCorrectionType *frame, int raDirection);
+FrameCorrectionType *FrameCorrectionImageToSH (Header *header, Matrix *matrix, FrameCorrectionType *frame, int raDirection);
+int FrameCorrectionFromSH (FrameCorrectionType *frame, SHterms *dR, SHterms *dD);
+void FrameCorrectionFree (FrameCorrectionType *frame);
+FrameCorrectionType *FrameCorrectionInit (double scale);
+void FrameCorrectionSetFree (FrameCorrectionSet *set);
+FrameCorrectionSet *FrameCorrectionSetInit ();
+
+int FrameCorrectionSetSave(char *filename, FrameCorrectionSet *set);
+FrameCorrectionSet *FrameCorrectionSetLoad(char *filename);
+
+int FrameCorrectionFitSH (ICRFobj *icrfobj, SHterms *dRc, SHterms *dDc);
+int FrameCorrectionFitLocal (ICRFobj *icrfobj, Coords *coords);
+int FrameCorrectionApply (Catalog *catalog, int Ncatalog, FrameCorrectionType *frame, Coords *coords);
+
+int dump_stardata_pts (StarData *raw, int Npts, char *filename);
+void printNcatTotal ();
+
+int HarvestRegionHosts (RegionHostTable *regionHosts);
+
+void lockUpdateChips ();
+void unlockUpdateChips ();
+
+int client_logger_init (char *dirname);
+int client_logger_message (char *format,...);
Index: /trunk/Ohana/src/relastro/src/BrightCatalog.c
===================================================================
--- /trunk/Ohana/src/relastro/src/BrightCatalog.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/BrightCatalog.c	(revision 37807)
@@ -43,11 +43,8 @@
   if (!gfits_fread_header (f, &header)) {
     if (VERBOSE) fprintf (stderr, "can't read image subset header\n");
-    fclose (f);
     goto escape;
   }
   if (!gfits_fread_matrix (f, &matrix, &header)) {
     if (VERBOSE) fprintf (stderr, "can't read image subset matrix\n");
-    gfits_free_header (&header);
-    fclose (f);
     goto escape;
   }
@@ -70,4 +67,6 @@
     GET_COLUMN(R,         "RA",   	double);
     GET_COLUMN(D,         "DEC",  	double);
+    GET_COLUMN(RoffGAL,   "ROFF_GAL",  	float);
+    GET_COLUMN(DoffGAL,   "DOFF_GAL",  	float);
     GET_COLUMN(M,         "MAG_SYS",  	float);
     GET_COLUMN(Mcal,      "MAG_CAL",  	float);
@@ -94,4 +93,6 @@
       measure[i].R         = R[i];
       measure[i].D         = D[i];
+      measure[i].RoffGAL   = RoffGAL[i];
+      measure[i].DoffGAL   = DoffGAL[i];
       measure[i].M         = M[i];
       measure[i].Mcal      = Mcal[i];
@@ -115,4 +116,6 @@
     free (R       );
     free (D       );
+    free (RoffGAL );
+    free (DoffGAL );
     free (M       );
     free (Mcal    );
@@ -310,5 +313,5 @@
   if (catalog) free (catalog);
 
-  fclose (f);
+  if (f) fclose (f);
   return NULL;
 }
@@ -343,6 +346,8 @@
     gfits_create_table_header (&theader, "BINTABLE", "MEASURE_TINY");
 
-    gfits_define_bintable_column (&theader, "E", "RA_OFF",   "ra offset",                  "arcsec", 1.0, 0.0);
-    gfits_define_bintable_column (&theader, "E", "DEC_OFF",  "dec offset",                 "arcsec", 1.0, 0.0);
+    gfits_define_bintable_column (&theader, "D", "RA",       "ra",                         "degrees", 1.0, 0.0);
+    gfits_define_bintable_column (&theader, "D", "DEC",      "dec",                        "degrees", 1.0, 0.0);
+    gfits_define_bintable_column (&theader, "E", "ROFF_GAL", "ra offset",                  "arcsec", 1.0, 0.0);
+    gfits_define_bintable_column (&theader, "E", "DOFF_GAL", "dec offset",                 "arcsec", 1.0, 0.0);
     gfits_define_bintable_column (&theader, "E", "MAG_SYS",  "magnitude (sys)",             NULL,    1.0, 0.0);
     gfits_define_bintable_column (&theader, "E", "MAG_CAL",  "magnitude (cal)",             NULL,    1.0, 0.0);
@@ -368,4 +373,6 @@
     double *R         ; ALLOCATE (R        ,  double, catalog->Nmeasure);
     double *D         ; ALLOCATE (D        ,  double, catalog->Nmeasure);
+    float  *RoffGAL   ; ALLOCATE (RoffGAL  ,  float,  catalog->Nmeasure);
+    float  *DoffGAL   ; ALLOCATE (DoffGAL  ,  float,  catalog->Nmeasure);
     float  *M         ; ALLOCATE (M        ,  float,  catalog->Nmeasure);
     float  *Mcal      ; ALLOCATE (Mcal     ,  float,  catalog->Nmeasure);
@@ -390,4 +397,6 @@
       R[i]        = measure[i].R        ;
       D[i]        = measure[i].D        ;
+      RoffGAL[i]  = measure[i].RoffGAL  ;
+      DoffGAL[i]  = measure[i].DoffGAL  ;
       M[i]  	  = measure[i].M        ;
       Mcal[i]     = measure[i].Mcal     ;
@@ -411,4 +420,6 @@
     gfits_set_bintable_column (&theader, &ftable, "RA",   	R,         catalog->Nmeasure);
     gfits_set_bintable_column (&theader, &ftable, "DEC",  	D,         catalog->Nmeasure);
+    gfits_set_bintable_column (&theader, &ftable, "ROFF_GAL",   RoffGAL,   catalog->Nmeasure);
+    gfits_set_bintable_column (&theader, &ftable, "DOFF_GAL",   DoffGAL,   catalog->Nmeasure);
     gfits_set_bintable_column (&theader, &ftable, "MAG_SYS",  	M,         catalog->Nmeasure);
     gfits_set_bintable_column (&theader, &ftable, "MAG_CAL",  	Mcal,      catalog->Nmeasure);
@@ -430,4 +441,6 @@
     free (R       );
     free (D       );
+    free (RoffGAL );
+    free (DoffGAL );
     free (M       );
     free (Mcal    );
Index: /trunk/Ohana/src/relastro/src/ConfigInit.c
===================================================================
--- /trunk/Ohana/src/relastro/src/ConfigInit.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/ConfigInit.c	(revision 37807)
@@ -25,4 +25,5 @@
   if (!ScanConfig (config, "RELASTRO_IMFIT_CLIP_NSIGMA",   "%lf", 0, &IMFIT_CLIP_NSIGMA))   IMFIT_CLIP_NSIGMA = 3.0;
   if (!ScanConfig (config, "RELASTRO_IMFIT_SYS_SIGMA_LIM", "%lf", 0, &IMFIT_SYS_SIGMA_LIM)) IMFIT_SYS_SIGMA_LIM = 0.01;
+  if (!ScanConfig (config, "RELASTRO_IMFIT_TOO_FEW",       "%d",  0, &IMFIT_TOO_FEW))       IMFIT_TOO_FEW = 5;
 
   if (!ScanConfig (config, "PM_DT_MIN",              "%lf", 0, &PM_DT_MIN))        PM_DT_MIN = 0.25;   
@@ -36,4 +37,9 @@
 
   if (!ScanConfig (config, "USE_FIXED_PIXCOORDS", "%d", 0, &USE_FIXED_PIXCOORDS))  USE_FIXED_PIXCOORDS = FALSE;
+  if (!ScanConfig (config, "USE_GALAXY_MODEL",    "%d", 0, &USE_GALAXY_MODEL))     USE_GALAXY_MODEL = FALSE;
+  if (!ScanConfig (config, "USE_ICRF_CORRECT",    "%d", 0, &USE_ICRF_CORRECT))     USE_ICRF_CORRECT = FALSE;
+  if (!ScanConfig (config, "USE_ICRF_LOCAL",      "%d", 0, &USE_ICRF_LOCAL))       USE_ICRF_LOCAL = FALSE;
+  if (!ScanConfig (config, "USE_ICRF_SHFIT",      "%d", 0, &USE_ICRF_SHFIT))       USE_ICRF_SHFIT = FALSE;
+  if (!ScanConfig (config, "USE_ICRF_POLE",       "%d", 0, &USE_ICRF_POLE))        USE_ICRF_POLE = FALSE;
 
   // force CATDIR to be absolute (so parallel mode will work)
Index: /trunk/Ohana/src/relastro/src/FitChip.c
===================================================================
--- /trunk/Ohana/src/relastro/src/FitChip.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/FitChip.c	(revision 37807)
@@ -14,5 +14,5 @@
 
   for (Niter = 0; Niter < IMFIT_CLIP_NITER; Niter ++) {
-    
+
     // measure the scatter for the unmarked (good) measurements with dM < limit
     // SIGMA_LIM here is redundant with ImageOps.c:915
@@ -49,15 +49,25 @@
     // use that count to set order_use below
 
+    // 20140925 : the same logic below is used to constrain the dimensions of the image map
+
     // figures to assess the fitting process:
     // x vs dx, x vs dy, y vs dx, y vs dy : 
     // residual vector field 
     // dx vs mag, dy vs mag
+    if (relastroGetVisual()) {
+      fprintf (stderr, "fitting chip %s, %d matched stars\n", image->name, Nmatch);
+    }
     relastroVisualPlotChipFit(raw, ref, dRmax, Nmatch);
+
+    // for polynomial fits, I used (5,30,60) = (5,6,7) stars per term
 
     // set the maximum order for the polynomial (based on number of stars kept above)
     int order_use = 0;
-    if (Nkeep >  5) order_use = 1; // 4 stars per polynomial term (per dimension)
-    if (Nkeep > 30) order_use = 2; // 5 stars per polynomial term (per dimension)
-    if (Nkeep > 60) order_use = 3; // 6 stars per polynomial term (per dimension)
+    if (Nkeep >   5) order_use = 1; //  5 stars per cell
+    if (Nkeep >  24) order_use = 2; //  6 stars per cell
+    if (Nkeep >  63) order_use = 3; //  7 stars per cell
+    if (Nkeep > 128) order_use = 4; //  8 stars per cell
+    if (Nkeep > 225) order_use = 5; //  9 stars per cell
+    if (Nkeep > 360) order_use = 6; // 10 stars per cell
     if (order_use < 1) {
       if (VERBOSE2) fprintf (stderr, "insufficient measurements (%d) for linear fit\n", Nkeep);
@@ -65,5 +75,14 @@
       return FALSE;
     }
-    image[0].coords.Npolyterms = order_use;
+    if (CHIPMAP) {
+      order_use = MIN(MIN(order_use, CHIPMAP), 6); // can only go up to 6th order map (can be user limited)
+    } else {
+      order_use = MIN(order_use, 3); // can only go up to 3rd order for polynomials
+    }
+
+    if (VERBOSE2) fprintf (stderr, "using %d for %s\n", order_use, image[0].name);
+
+    // when fitting the map, first fit a linear model (below? change Npolyterms to -1)
+    image[0].coords.Npolyterms = CHIPMAP ? 1 : order_use;
 
     if (fit) fit_free (fit);
@@ -91,8 +110,36 @@
     }
 
+    // apply fit to get the fitted X,Y coordinates.  we need these to fit the residual map below
     for (i = 0; i < Nmatch; i++) {
+      // we have not yet fitted the map, so Npolyterms needs to be 1 here:
+      LM_to_XY (&ref[i].X, &ref[i].Y, ref[i].L, ref[i].M, &image[0].coords);
+    }
+
+    if (CHIPMAP) {
+      if (image[0].coords.offsetMap == NULL) {
+	// allocate a new table and assign to this image
+	// need to lock threads here because 'table' is a static with 
+	// modifiable elements
+	lockUpdateChips ();
+	AstromOffsetTable *table = get_astrom_table ();
+	AstromOffsetTableNewMap(table, order_use, order_use, image);
+	unlockUpdateChips ();
+      }
+      // XXX Need to check / update the order of the map here.  if we already have a map, it might have been
+      // allocated with too small arrays
+      AstromOffsetMapSetOrder (image[0].coords.offsetMap, order_use, order_use, image);
+      image[0].coords.offsetMap->keep = TRUE; // if we are trying or re-trying this map, we should keep it unless it fails
+      fit_map (image[0].coords.offsetMap, raw, ref, Nmatch);
+      image[0].coords.Npolyterms = -1;
+    }
+
+    if (!strcmp(image[0].name, "o5745g0403o.356777.cm.982487.smf[XY17.hdr]")) {
+      fprintf (stderr, "test image\n");
+    }
+
+    for (i = 0; i < Nmatch; i++) {
+      // if we have fitted the map above, Npolyterms needs to be -1 here:
       XY_to_LM (&raw[i].L, &raw[i].M, raw[i].X, raw[i].Y, &image[0].coords);
     }
-
   }
 
@@ -223,19 +270,19 @@
    L,M -> X,Y (polynomial transformation : WRP)
 
-Some details about this function:
-
-- the initial value of the clipping radius is set based on the distribution of the dR
-  values for the bright sources.
-
-- NITER clipping passes are performed
-
-- the per-star astrometric errors are included in the fit.  The photcode table controls
-  whether only the reported positional errors are used, or if the magnitudes errors are
-  scaled to determine the error, and if there is a systematic floor for all sources.
-
-- input detections are pre-filtered on the basis of the photFlags, etc, in bcatalog
-
-- outlying detections are clipped in the iterative passes, but the clipped detections are
-  not recorded
+   Some details about this function:
+
+   - the initial value of the clipping radius is set based on the distribution of the dR
+   values for the bright sources.
+
+   - NITER clipping passes are performed
+
+   - the per-star astrometric errors are included in the fit.  The photcode table controls
+   whether only the reported positional errors are used, or if the magnitudes errors are
+   scaled to determine the error, and if there is a systematic floor for all sources.
+
+   - input detections are pre-filtered on the basis of the photFlags, etc, in bcatalog
+
+   - outlying detections are clipped in the iterative passes, but the clipped detections are
+   not recorded
 
 */
@@ -243,13 +290,72 @@
 /* example using fit_apply() :
 
-  f = fopen ("test3.dat", "w");
-
-  // apply new coords to raw (X,Y -> L,M)
-  for (i = 0; i < Nmatch; i++) {
-    fprintf (f, "%f %f  %f %f  ", raw[i].X, raw[i].Y, raw[i].L, raw[i].M);
-    fit_apply (newfit, &L1, &M1, raw[i].X - coords[0].crpix1, raw[i].Y - coords[0].crpix2);
-    fprintf (f, "%f %f\n", L1, M1);
+   f = fopen ("test3.dat", "w");
+
+   // apply new coords to raw (X,Y -> L,M)
+   for (i = 0; i < Nmatch; i++) {
+   fprintf (f, "%f %f  %f %f  ", raw[i].X, raw[i].Y, raw[i].L, raw[i].M);
+   fit_apply (newfit, &L1, &M1, raw[i].X - coords[0].crpix1, raw[i].Y - coords[0].crpix2);
+   fprintf (f, "%f %f\n", L1, M1);
+   }
+   fclose (f);
+*/
+
+int fit_map (AstromOffsetMap *map, StarData *raw, StarData *ref, int Npts) {
+
+  // we are actually fitting the residual after the linear fit has been taken off
+  
+  // fit the linear terms as above
+  // calculate dX (raw.X - ref.X) and dY 
+
+  int i, N;
+
+  float *x, *y, *dX, *dY;
+  ALLOCATE (x,  float, Npts);
+  ALLOCATE (y,  float, Npts);
+  ALLOCATE (dX, float, Npts);
+  ALLOCATE (dY, float, Npts);
+
+  N = 0;
+  for (i = 0; i < Npts; i++) {
+    if (raw[i].mask) continue;
+    x[N] = raw[i].X;
+    y[N] = raw[i].Y;
+    dX[N] = ref[i].X - raw[i].X;
+    dY[N] = ref[i].Y - raw[i].Y;
+    N++;
+  }
+
+  // in coordsops.c:XY_to_LM, the map is defined to carry dX,dY so that:
+  // (L,M) = f(X',Y') : (X',Y') = (X,Y) + (dX,dY)
+
+  AstromOffsetMapFit (map, x, y, dX, N, TRUE);
+  AstromOffsetMapFit (map, x, y, dY, N, FALSE);
+
+  AstromOffsetMapRepair (map, TRUE);
+  AstromOffsetMapRepair (map, FALSE);
+
+  free (x);
+  free (y);
+  free (dX);
+  free (dY);
+
+  return TRUE;
+}
+
+int dump_stardata_pts (StarData *raw, int Npts, char *filename) {
+
+  FILE *f = fopen (filename, "w");
+  if (!f) { 
+    fprintf (stderr, "failed to open file %s\n", filename);
+    return FALSE;
+  }
+
+  int i;
+  for (i = 0; i < Npts; i++) {
+    fprintf (f, "%4d %10.6f %10.6f  %10.6f %10.6f  %9.2f %9.2f  %9.2f %9.2f  %9.4f %3d\n", 
+	     i, raw[i].R, raw[i].D, raw[i].P, raw[i].Q, raw[i].L, raw[i].M, raw[i].X, raw[i].Y, raw[i].dPos, raw[i].mask);
   }
   fclose (f);
-*/
-
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/relastro/src/FrameCorrection.c
===================================================================
--- /trunk/Ohana/src/relastro/src/FrameCorrection.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/FrameCorrection.c	(revision 37807)
@@ -0,0 +1,617 @@
+# include "relastro.h"
+
+/* The sequence of calibration is something like this:
+
+ * apply the current image.coords to measure.X,Y to get measure.R,D
+ * use the measure.R,D values to find the mean position average.R,D
+ * fit the measure.X,Y positions to average.R,D to get image.coords
+
+ At some point in this sequence, we want to compare our current average.R,D (for specific
+ objects) to the ICRF values for those objects to get a correction set in R,D.  
+
+ Apply that correction to get a new set of average.R,D values.  The impact of the
+ adjustment will then be to modify the image.coords to compensate for the offset.
+
+*/
+
+int FrameCorrectionParallelMaster (RegionHostTable *regionHosts) {
+
+  if (!USE_ICRF_CORRECT) return TRUE;
+
+  int i;
+  for (i = 0; i < NLOOP; i++) {
+    if ((i > 1) || !USE_GALAXY_MODEL) {
+      // if GALAXY_MODEL is selected, we want to delay the frame correction until we have 
+      // applied the galaxy model a couple of times.
+
+      ICRFobj *icrfobj = slurp_icrf_obj (regionHosts, i);
+      FrameCorrectionSet *set = FrameCorrectionMeasure (icrfobj);
+      
+      char *filename = make_filename (CATDIR, "master", 0, "frame.corr.fits");
+      FrameCorrectionSetSave (filename, set);
+      free (filename);
+      
+      char *syncfile = make_filename (CATDIR, "master", 0, "frame.corr.sync");
+      update_sync_file (syncfile, i);
+      free (syncfile);
+    }
+  }
+
+  int status = HarvestRegionHosts (regionHosts);
+  return status;
+}
+
+int FrameCorrectionParallelSlave (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop) {
+
+  if (!USE_ICRF_CORRECT) return TRUE;
+
+  share_icrf_obj (catalog, Ncatalog, regionHosts, nloop); 
+
+  char *syncfile = make_filename (CATDIR, "master", 0, "frame.corr.sync");
+  check_sync_file (syncfile, nloop);
+  free (syncfile);
+    
+  char *filename = make_filename (CATDIR, "master", 0, "frame.corr.fits");
+  FrameCorrectionSet *set = FrameCorrectionSetLoad (filename);
+  free (filename);
+
+  // Now apply the correction to all of the average.R,D values
+  FrameCorrectionApply (catalog, Ncatalog, set->frame, set->coords);
+  FrameCorrectionSetFree(set);
+
+  return TRUE;
+}
+
+int FrameCorrectionSerial (Catalog *catalog, int Ncatalog) {
+
+  if (!USE_ICRF_CORRECT) return TRUE;
+
+  ICRFobj *icrfobj = get_ICRF_data (catalog, Ncatalog);
+  if (!icrfobj->Nicrfobj) {
+    fprintf (stderr, "no matched ICRF quasars for reference correction, skipping\n");
+    return FALSE;
+  }
+
+  FrameCorrectionSet *set = FrameCorrectionMeasure (icrfobj);
+  ICRFobjFree (icrfobj);
+
+  // write out an image to represent the correction
+  static int version = 0;
+  char filename[1024];
+  snprintf (filename, 1024, "%s/frame.%03d.corr.fits", CATDIR, version);
+  FrameCorrectionSetSave (filename, set);
+  version ++;
+
+  // Now apply the correction to all of the average.R,D values
+  FrameCorrectionApply (catalog, Ncatalog, set->frame, set->coords);
+  FrameCorrectionSetFree(set);
+
+  return TRUE;
+}
+
+FrameCorrectionSet *FrameCorrectionMeasure (ICRFobj *icrfobj) {
+
+  // we have only a few thousand ICRF reference points.  I need to have a lookup table to
+  // go from ICRF object to the catalog entry.  
+
+  // we can have 2 kinds of corrections:
+  // 'frame' is a map in spherical coords of the corrections based on spherical harmonics
+  // 'map' is a correction based on a local projection to a linear coordinate system.  I
+  // am going to use this for the pole, but also for SAS for testing.
+  // We need to be sure only one of the two is applied. For the pole, the boundary is a
+  // fixed line of DEC; for the SAS, the boundary is the projection 'image'
+
+  FrameCorrectionSet *set = FrameCorrectionSetInit();
+  if (!USE_ICRF_LOCAL && !USE_ICRF_SHFIT) {
+    fprintf (stderr, "ERROR: no ICRF correction mode selected\n");
+    exit (0);
+  }
+
+  if (USE_ICRF_LOCAL) {
+    // for the local frame correction, we are going to convert R,D into 
+    // X,Y linear coordinates in an 'image' centered on the field center. 
+    // we can then use an AstromOffsetMap structure to carry the correction values
+
+    // what parameters define a local frame correction?
+    // * Ro, Do -- map/projection center
+    // * scale  -- degrees / pixel
+    // * Nx, Ny -- size of map in pixels
+    // * Rmin,Rmax,Dmin,Dmax
+
+    // example parameters for SAS: 320 - 340, -10 - +10
+    double Ro = 330.0;
+    double Do =   0.0;
+    double scale = 2.0; // degrees per patch
+    int Nx = 10;
+    int Ny = 10;
+    if (USE_ICRF_POLE) {
+       Ro =  0.0;
+       Do = 90.0;
+       scale = 5.0; // degrees per patch
+       Nx = 4;
+       Ny = 4;
+    }
+
+    
+    Coords *coords = NULL;
+    ALLOCATE (coords, Coords, 1);
+
+    InitCoords (coords, "DEC--SIN");
+    coords->cdelt1 = coords->cdelt2 = scale;
+    coords->crval1 = Ro; // SAS center
+    coords->crval2 = Do;
+    coords->crpix1 = 0.5*Nx; // middle of projection is middle of map
+    coords->crpix2 = 0.5*Ny; // middle of projection is middle of map
+
+    // map is a component of coords
+    coords->offsetMap = AstromOffsetMapInit (Nx, Ny);
+    coords->offsetMap->dX = 1.0; // scale from projection (in arcsec) to correction patches
+    coords->offsetMap->dY = 1.0;
+
+    FrameCorrectionFitLocal (icrfobj, coords);
+    set->coords = coords;
+  } 
+
+  if (USE_ICRF_SHFIT) {
+    // Lmax in recipe
+    int Lmax = 5;
+
+    // dR and dD will carry the fit coefficients in RA and DEC
+    SHterms *dRc = SHtermsInit (Lmax);
+    SHterms *dDc = SHtermsInit (Lmax);
+
+    INITTIME;
+
+    FrameCorrectionFitSH (icrfobj, dRc, dDc);
+    MARKTIME ("done with FitSH: %f sec\n", dtime);
+
+    double pltscale = 0.5; // degrees per pixel
+    FrameCorrectionType *frame = FrameCorrectionInit (pltscale);
+  
+    FrameCorrectionFromSH (frame, dRc, dDc);
+    MARKTIME ("done with FromSH: %f sec\n", dtime);
+
+    SHtermsFree (dRc);
+    SHtermsFree (dDc);
+
+    set->frame = frame;
+  }
+
+  return set;
+}
+
+// I need to generate a collection of values dR,dD(R,D), where dR = average.R - ICRF.R, etc.  
+// I will then fit the set of dR,dD values to a set of spherical harmonics (up to Lmax).
+// I will then generate correction images dR,dD(R,D)  
+// I will then correct all values average.R,D by the interpolated values from the images
+// average.R' = average.R - dR(R,d), average.D' = average.D - dD(R,d)
+
+int FrameCorrectionFitSH (ICRFobj *icrfobj, SHterms *dRc, SHterms *dDc) {
+
+  int i;
+
+  myAssert (dRc->lmax == dDc->lmax, "dR and dD must match\n");
+
+  fprintf (stderr, "start Fit %d SH l-modes for %d QSOs\n", dRc->lmax, icrfobj->Nicrfobj);
+
+  int *mask = NULL;
+  ALLOCATE (mask, int, icrfobj->Nicrfobj);
+  memset (mask, 0, icrfobj->Nicrfobj*sizeof(int));
+
+  double *dPosObs, *dPosSrt = NULL;
+  ALLOCATE (dPosObs, double, icrfobj->Nicrfobj);
+  ALLOCATE (dPosSrt, double, icrfobj->Nicrfobj);
+
+  int Niter;
+  for (Niter = 0; Niter < 3; Niter ++) {
+
+    SHfitWithMask (icrfobj->Rave, icrfobj->Dave, icrfobj->dRoff, mask, icrfobj->Nicrfobj, dRc);
+    SHfitWithMask (icrfobj->Rave, icrfobj->Dave, icrfobj->dDoff, mask, icrfobj->Nicrfobj, dDc);
+
+    // the bit below is for testing
+
+    // allocate an SHterms structure to hold the Ylm values 
+    SHterms *Ylm = SHtermsInit (dRc->lmax);
+
+    static int version = 0;
+    char filename[1024];
+    snprintf (filename, 1024, "%s/sh.%02d.dat", CATDIR, version);
+    FILE *f = fopen (filename, "w");
+    myAssert (f, "oops");
+    version ++;
+
+    int Nkeep = 0;
+    for (i = 0; i < icrfobj->Nicrfobj; i++) {
+    
+      SHtermsForRD (Ylm, icrfobj->Rave[i], icrfobj->Dave[i]);
+      
+      double dRfit = 0.0;
+      double dDfit = 0.0;
+
+      int j;
+      for (j = 0; j < Ylm->Nterms; j++) {
+	dRfit += dRc->Fr[j]*Ylm->Fr[j] + dRc->Fi[j]*Ylm->Fi[j];
+	dDfit += dDc->Fr[j]*Ylm->Fr[j] + dDc->Fi[j]*Ylm->Fi[j];
+	
+      }
+      fprintf (f, "%12.8f %12.8f %7.3f %7.3f : %7.3f %7.3f : %d\n", 
+	       icrfobj->Rave[i], icrfobj->Dave[i], icrfobj->dRoff[i], icrfobj->dDoff[i], dRfit, dDfit, mask[i]);
+      dPosObs[i] = hypot(icrfobj->dRoff[i] - dRfit, icrfobj->dDoff[i] - dDfit);
+      if (mask[i]) continue;
+      if (isnan(dPosObs[i])) continue;
+      dPosSrt[Nkeep] = dPosObs[i];
+      Nkeep ++;
+    }
+    fclose (f);
+    
+    dsort (dPosSrt, Nkeep);
+    double dPmax = 1.5*dPosSrt[(int)(0.95*Nkeep)];
+
+    Nkeep = 0;
+    for (i = 0; i < icrfobj->Nicrfobj; i++) {
+      if (dPosObs[i] > dPmax) mask[i] = 1;
+      if (isnan(dPosObs[i])) mask[i] = 1;
+      if (!mask[i]) Nkeep++;
+    }
+    fprintf (stderr, "keeping %d of %d points\n", Nkeep, icrfobj->Nicrfobj);
+  }
+
+  free (mask);
+  free (dPosObs);
+  free (dPosSrt);
+
+  return TRUE;
+}
+
+int SHfitWithMask (double *R, double *D, double *value, int *mask, int Npts, SHterms *fit) {
+
+  int i, j, k;
+
+  // allocate an SHterms structure to hold the Ylm values 
+  SHterms *Ylm = SHtermsInit (fit->lmax);
+
+  // we only fit the linearly independent terms: Re(m >= 0), Im(m > 0)
+  int NtermRE = 0;
+  int NtermIM = 0;
+  for (i = 0; i < Ylm->Nterms; i++) {
+    if (Ylm->m[i] >= 0) NtermRE ++;
+    if (Ylm->m[i] >  0) NtermIM ++;
+  }
+  int *Nre = NULL;
+  int *Nim = NULL;
+  ALLOCATE (Nre, int, NtermRE);
+  ALLOCATE (Nim, int, NtermIM);
+
+  NtermRE = NtermIM = 0;
+  for (i = 0; i < Ylm->Nterms; i++) {
+    if (Ylm->m[i] >= 0) {
+      Nre[NtermRE] = i;
+      NtermRE ++;
+    }
+    if (Ylm->m[i] >  0) {
+      Nim[NtermIM] = i;
+      NtermIM ++;
+    }
+  }
+
+  double **Are, **bre, **Aim, **bim;
+  ALLOCATE (Are, double *, NtermRE);
+  ALLOCATE (bre, double *, NtermRE);
+  ALLOCATE (Aim, double *, NtermIM);
+  ALLOCATE (bim, double *, NtermIM);
+  for (i = 0; i < NtermRE; i++) {
+    ALLOCATE_ZERO (Are[i], double, NtermRE);
+    ALLOCATE_ZERO (bre[i], double, 1);
+  }
+  for (i = 0; i < NtermIM; i++) {
+    ALLOCATE_ZERO (Aim[i], double, NtermIM);
+    ALLOCATE_ZERO (bim[i], double, 1);
+  }
+  
+  // measure the dot product \sum(F_i * Ylm_i) and the cross terms (\sum(Y_lm * Y_jk))
+  int Nfit = 0;
+  for (i = 0; i < Npts; i++) {
+    if (mask[i]) continue;
+    Nfit ++;
+
+    // set the values of Ylm(R[i],D[i])
+    SHtermsForRD (Ylm, R[i], D[i]); 
+
+    double Fv = value[i];
+
+    for (j = 0; j < NtermRE; j++) {
+      int jre = Nre[j];
+      bre[j][0] += Fv * Ylm->Fr[jre];
+    }
+    for (j = 0; j < NtermIM; j++) {
+      int jim = Nim[j];
+      bim[j][0] += Fv * Ylm->Fi[jim];
+    }
+
+    for (j = 0; j < NtermRE; j++) {
+      int jre = Nre[j];
+      for (k = j; k < NtermRE; k++) {
+	int kre = Nre[k];
+	Are[j][k] += Ylm->Fr[jre] * Ylm->Fr[kre];
+      }
+    }
+
+    for (j = 0; j < NtermIM; j++) {
+      int jim = Nim[j];
+      for (k = j; k < NtermIM; k++) {
+	int kim = Nim[k];
+	Aim[j][k] += Ylm->Fi[jim] * Ylm->Fi[kim];
+      }
+    }
+  }
+
+  for (j = 1; j < NtermRE; j++) {
+    for (k = 0; k < j; k++) {
+      Are[j][k] = Are[k][j];
+    }	
+  }
+  for (j = 1; j < NtermIM; j++) {
+    for (k = 0; k < j; k++) {
+      Aim[j][k] = Aim[k][j];
+    }	
+  }
+
+  if (!dgaussjordan (Are, bre, NtermRE, 1)) {
+    gprint (GP_ERR, "failed to fit data : ill-conditioned matrix\n");
+    return FALSE;
+  }
+  if (!dgaussjordan (Aim, bim, NtermIM, 1)) {
+    gprint (GP_ERR, "failed to fit data : ill-conditioned matrix\n");
+    return FALSE;
+  }
+
+  for (j = 0; j < fit->Nterms; j++) { 
+    fit->Fr[j] = 0.0;
+    fit->Fi[i] = 0.0;
+  }
+  for (j = 0; j < NtermRE; j++) {
+    int jre = Nre[j];
+    // fit->Fr[jre] = bre[j][0] * fit->Nterms / (float) Nfit; // XXX EAM : why is this factor needed?
+    fit->Fr[jre] = bre[j][0];
+  }
+  for (j = 0; j < NtermIM; j++) {
+    int jim = Nim[j];
+    // fit->Fi[jim] = bim[j][0] * fit->Nterms / (float) Nfit; // XXX EAM : why is this factor needed?
+    fit->Fi[jim] = bim[j][0];
+  }
+
+  SHtermsFree (Ylm);
+
+  for (i = 0; i < NtermRE; i++) {
+    free (Are[i]);
+    free (bre[i]);
+  }
+  for (i = 0; i < NtermIM; i++) {
+    free (Aim[i]);
+    free (bim[i]);
+  }
+  free (Are);
+  free (bre);
+  free (Aim);
+  free (bim);
+
+  return TRUE;
+}
+
+int FrameCorrectionFitLocal (ICRFobj *icrfobj, Coords *coords) {
+
+  int i;
+  double Xave, Yave, Xmeas, Ymeas;
+  float *X, *Y, *dX, *dY;
+  float *dXf, *dYf, *dPos;
+  double *Rave, *Dave, *Rmeas, *Dmeas;
+
+  int Nicrf = icrfobj->Nicrfobj;
+
+  AstromOffsetMap *map = coords->offsetMap;
+
+  int Npts = 0;
+  ALLOCATE (X,  float, Nicrf);
+  ALLOCATE (Y,  float, Nicrf);
+  ALLOCATE (dX, float, Nicrf);
+  ALLOCATE (dY, float, Nicrf);
+  ALLOCATE (dXf, float, Nicrf);
+  ALLOCATE (dYf, float, Nicrf);
+
+  ALLOCATE (dPos, float, Nicrf);
+
+  // arrays to store the points actually in the local region
+  ALLOCATE (Rave, double, Nicrf);
+  ALLOCATE (Dave, double, Nicrf);
+  ALLOCATE (Rmeas, double, Nicrf);
+  ALLOCATE (Dmeas, double, Nicrf);
+
+  int *mask = NULL;
+  ALLOCATE (mask, int, Nicrf);
+  memset (mask, 0, Nicrf*sizeof(int));
+
+  // select the ICRF QSOS and save the necessary data
+  for (i = 0; i < Nicrf; i++) {
+
+    // this local correction is defined for (Rmin < R < Rmax, Dmin < D < Dmax)
+    int status = RD_to_XY (&Xave, &Yave, icrfobj->Rave[i], icrfobj->Dave[i], coords);
+    if (!status) continue;
+    if (Xave < 0.0) continue;
+    if (Xave > map->Nx) continue;
+    if (Yave < 0.0) continue;
+    if (Yave > map->Ny) continue;
+
+    double Rm = icrfobj->Rave[i] - icrfobj->dRoff[i] / 3600.0 / cos(icrfobj->Rave[i]*RAD_DEG);
+    double Dm = icrfobj->Dave[i] - icrfobj->dDoff[i] / 3600.0;
+    RD_to_XY (&Xmeas, &Ymeas, Rm, Dm, coords);
+
+    // record these in arcsec or degree?
+    // correct for cos(D) or not?
+    X[Npts] = Xave;
+    Y[Npts] = Yave;
+    dX[Npts] = Xave - Xmeas;
+    dY[Npts] = Yave - Ymeas;
+    Rave[Npts] = icrfobj->Rave[i];
+    Dave[Npts] = icrfobj->Dave[i];
+    Rmeas[Npts] = Rm;
+    Dmeas[Npts] = Dm;
+    Npts ++;
+  }
+
+  float *Xfit, *Yfit, *dXfit, *dYfit;
+  ALLOCATE (Xfit, float, Npts);
+  ALLOCATE (Yfit, float, Npts);
+  ALLOCATE (dXfit, float, Npts);
+  ALLOCATE (dYfit, float, Npts);
+
+  int Niter;
+  for (Niter = 0; Niter < 3; Niter ++) {
+    int Nkeep = 0;
+    for (i = 0; i < Npts; i++) {
+      if (mask[i]) continue;
+      
+      Xfit[Nkeep] = X[i];
+      Yfit[Nkeep] = Y[i];
+      dXfit[Nkeep] = dX[i];
+      dYfit[Nkeep] = dY[i];
+      Nkeep ++;
+    }
+
+    AstromOffsetMapFit (map, Xfit, Yfit, dXfit, Nkeep, TRUE);
+    AstromOffsetMapFit (map, Xfit, Yfit, dYfit, Nkeep, FALSE);
+
+    for (i = 0; i < Npts; i++) {
+      dXf[i] = AstromOffsetMapValue (map, X[i], Y[i], TRUE);
+      dYf[i] = AstromOffsetMapValue (map, X[i], Y[i], FALSE);
+    }
+
+    // generate a histogram of the dPos = sqrt((dXf - dX)^2 + (dYf - dY)^2) values
+    Nkeep = 0;
+    for (i = 0; i < Npts; i++) {
+      if (mask[i]) continue;
+      float dP = hypot((dXf[i] - dX[i]), (dYf[i] - dY[i]));
+      if (isnan(dP)) continue; // not all points are in the map
+      dPos[Nkeep] = dP;
+      Nkeep ++;
+    }
+    fsort (dPos, Nkeep);
+    
+    float dPmax = 1.5*dPos[(int)(0.95*Nkeep)];
+
+    // mask points based on dPmax
+    Nkeep = 0;
+    for (i = 0; i < Npts; i++) {
+      float dP = hypot((dXf[i] - dX[i]), (dYf[i] - dY[i]));
+      if (dP > dPmax) mask[i] = 1;
+      if (isnan(dP)) mask[i] = 1;
+      if (!mask[i]) Nkeep++;
+    }
+    fprintf (stderr, "keeping %d of %d points\n", Nkeep, Npts);
+  }
+  free (Xfit);
+  free (Yfit);
+  free (dXfit);
+  free (dYfit);
+
+  // *** save the icrf points and fit residuals
+  { 
+    static int version = 0;
+    char filename[1024];
+    snprintf (filename, 1024, "%s/map.%02d.dat", CATDIR, version);
+    FILE *f = fopen (filename, "w");
+    version ++;
+
+    for (i = 0; i < Npts; i++) {
+      fprintf (f, "%3d %12.8f %12.8f  %12.8f %12.8f  %f %f : %f %f : %f %f : %d\n", i, Rave[i], Dave[i], Rmeas[i], Dmeas[i], X[i], Y[i], dX[i], dY[i], dXf[i], dYf[i], mask[i]);
+    }    
+    fclose (f);
+  }
+
+  free (X);
+  free (Y);
+  free (dX);
+  free (dY);
+  free (dXf);
+  free (dYf);
+  free (dPos);
+  free (Rave);
+  free (Dave);
+  free (Rmeas);
+  free (Dmeas);
+  free (mask);
+
+  return TRUE;
+}
+
+int FrameCorrectionApply (Catalog *catalog, int Ncatalog, FrameCorrectionType *frame, Coords *coords) {
+
+  double Xave, Yave;
+
+  AstromOffsetMap *map = coords ? coords->offsetMap : NULL;
+
+  int i, j;
+  for (i = 0; i < Ncatalog; i++) {
+    for (j = 0; j < catalog[i].Naverage; j++) {
+      
+      Average *average = &catalog[i].average[j];
+
+      double R = average->R;
+      double D = average->D;
+
+      int inLocalRegion = FALSE;
+      if (USE_ICRF_LOCAL) {
+	myAssert (coords, "no local frame coords defined");
+
+	// this local correction is defined for (Rmin < R < Rmax, Dmin < D < Dmax)
+	inLocalRegion = RD_to_XY (&Xave, &Yave, R, D, coords);
+	if (USE_ICRF_POLE) {
+	  inLocalRegion = (D > 85.0);
+	} else {
+	  inLocalRegion = (inLocalRegion && (Xave > 0.0));
+	  inLocalRegion = (inLocalRegion && (Xave < map->Nx));
+	  inLocalRegion = (inLocalRegion && (Yave > 0.0));
+	  inLocalRegion = (inLocalRegion && (Yave < map->Ny));
+	}
+      }
+
+      if (inLocalRegion) {
+	myAssert (map, "no local frame map defined");
+	double dX = AstromOffsetMapValue (map, Xave, Yave, TRUE);
+	double dY = AstromOffsetMapValue (map, Xave, Yave, FALSE);
+	if (!isnan(dX) && !isnan(dY)) {
+	  double Xmeas = Xave - dX;
+	  double Ymeas = Yave - dY;
+	  XY_to_RD (&average->R, &average->D, Xmeas, Ymeas, coords);
+	}
+	continue;
+      }
+      
+      if (USE_ICRF_SHFIT) {
+	myAssert (frame, "no frame correction defined");
+
+	int iD = (D + 89.0) / frame->scale;
+	if (iD < 0) continue;
+	if (iD >= frame->Ndec) continue;
+
+	int iR = R / frame->dR[iD];
+	if (iR < 0) continue;
+	if (iR >= frame->Nra[iD]) continue;
+
+	double dR = frame->Roff[iD][iR];
+	double dD = frame->Doff[iD][iR];
+
+	// XXX tighten this up??
+	// do not apply if the fabs(offset) is > 8 arcsec)
+	if ((fabs(dR) > 8.0) || (fabs(dD) > 8.0)) {
+	  fprintf (stderr, "skip: %10.6f %10.6f : %7.3f %7.3f\n", R, D, dR, dD);
+	  continue;
+	}
+
+	average->R -= dR / 3600.0 / cos(average->R*RAD_DEG);
+	average->D -= dD / 3600.0;
+      }
+    }
+  }
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/relastro/src/FrameCorrectionIO.c
===================================================================
--- /trunk/Ohana/src/relastro/src/FrameCorrectionIO.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/FrameCorrectionIO.c	(revision 37807)
@@ -0,0 +1,251 @@
+# include "relastro.h"
+
+// STATUS is value expected for success
+# define CHECK_STATUS(STATUS,MSG,...)					\
+  if (!(STATUS)) {							\
+    fprintf (stderr, MSG, __VA_ARGS__);					\
+    return FALSE;							\
+  }
+
+// the frame correction consists of N image correction maps
+// there are local maps and AITOFF maps
+FrameCorrectionSet *FrameCorrectionSetLoad(char *filename) {
+
+  Header header;
+  Matrix matrix;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  // FrameCorrectionSet consists:
+  // PHU header listing the contents:
+  // SHMAP_dR : EXTNAME or NONE
+  // SHMAP_dD : EXTNAME or NONE
+  // LOCAL_dR : EXTNAME or NONE
+  // LOCAL_dD : EXTNAME or NONE
+
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset header\n");
+    fclose (f);
+    return NULL;
+  }
+
+  char SHMAP_DR[80], SHMAP_DD[80], LOCAL_DR[80], LOCAL_DD[80];
+  if (!gfits_scan (&header, "SHMAP_DR", "%s", 1, SHMAP_DR)) {
+    fprintf (stderr, "ERROR: FrameCorrectionSet %s missing required field SHMAP_DR\n", filename);
+    exit (2);
+  }
+  if (!gfits_scan (&header, "SHMAP_DD", "%s", 1, SHMAP_DD)) {
+    fprintf (stderr, "ERROR: FrameCorrectionSet %s missing required field SHMAP_DD\n", filename);
+    exit (2);
+  }
+  if (!gfits_scan (&header, "LOCAL_DR", "%s", 1, LOCAL_DR)) {
+    fprintf (stderr, "ERROR: FrameCorrectionSet %s missing required field LOCAL_DR\n", filename);
+    exit (2);
+  }
+  if (!gfits_scan (&header, "LOCAL_DD", "%s", 1, LOCAL_DD)) {
+    fprintf (stderr, "ERROR: FrameCorrectionSet %s missing required field LOCAL_DD\n", filename);
+    exit (2);
+  }
+  gfits_free_header (&header);
+    
+  FrameCorrectionSet *set = FrameCorrectionSetInit ();
+
+  if (strcmp(SHMAP_DR, "NONE")) {
+    if (!strcmp (SHMAP_DD, "NONE")) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s has defined SHMAP_DR but not SHMAP_DD\n", filename);
+      exit (2);
+    }
+    if (!gfits_find_Xheader(f, &header, SHMAP_DR)) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s missing SHMAP_DR extension %s\n", filename, SHMAP_DR);
+      exit (2);
+    }
+    if (!gfits_fread_matrix (f, &matrix, &header)) {
+      if (VERBOSE) fprintf (stderr, "can't read SHMAP_DR extension image\n");
+      gfits_free_header (&header);
+      fclose (f);
+      return NULL;
+    }
+    // convert image to frame
+    set->frame = FrameCorrectionImageToSH (&header, &matrix, NULL, TRUE);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+
+    if (!gfits_find_Xheader(f, &header, SHMAP_DD)) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s missing SHMAP_DD extension %s\n", filename, SHMAP_DD);
+      exit (2);
+    }
+    if (!gfits_fread_matrix (f, &matrix, &header)) {
+      if (VERBOSE) fprintf (stderr, "can't read SHMAP_DD extension image\n");
+      gfits_free_header (&header);
+      fclose (f);
+      return NULL;
+    }
+    set->frame = FrameCorrectionImageToSH (&header, &matrix, set->frame, TRUE);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+  } else {
+    if (strcmp (SHMAP_DD, "NONE")) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s has defined SHMAP_DD but not SHMAP_DR\n", filename);
+      exit (2);
+    }
+  }
+
+  if (strcmp(LOCAL_DR, "NONE")) {
+    if (!strcmp (LOCAL_DD, "NONE")) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s has defined LOCAL_DR but not LOCAL_DD\n", filename);
+      exit (2);
+    }
+    if (!gfits_find_Xheader(f, &header, LOCAL_DR)) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s missing LOCAL_DR extension %s\n", filename, LOCAL_DR);
+      exit (2);
+    }
+    if (!gfits_fread_matrix (f, &matrix, &header)) {
+      if (VERBOSE) fprintf (stderr, "can't read LOCAL_DR extension image\n");
+      gfits_free_header (&header);
+      fclose (f);
+      return NULL;
+    }
+    // convert image to frame
+    set->coords = FrameCorrectionImageToMap (&header, &matrix, NULL, TRUE);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+
+    if (!gfits_find_Xheader(f, &header, LOCAL_DD)) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s missing LOCAL_DD extension %s\n", filename, LOCAL_DD);
+      exit (2);
+    }
+    if (!gfits_fread_matrix (f, &matrix, &header)) {
+      if (VERBOSE) fprintf (stderr, "can't read LOCAL_DD extension image\n");
+      gfits_free_header (&header);
+      fclose (f);
+      return NULL;
+    }
+    set->coords = FrameCorrectionImageToMap (&header, &matrix, set->coords, FALSE);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+  } else {
+    if (strcmp (LOCAL_DD, "NONE")) {
+      fprintf (stderr, "ERROR: FrameCorrectionSet %s has defined LOCAL_DD but not LOCAL_DR\n", filename);
+      exit (2);
+    }
+  }
+  fclose (f);
+  return set;
+}
+
+int FrameCorrectionSetSave(char *filename, FrameCorrectionSet *set) {
+
+  int status;
+  Header header;
+  Matrix matrix;
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open meanmag file for output %s\n", filename);
+    return FALSE;
+  }
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  // add entries to the PHU
+  if (set->frame) {
+    gfits_modify (&header, "SHMAP_DR", "%s", 1, "SHMAP_DR");
+    gfits_modify (&header, "SHMAP_DD", "%s", 1, "SHMAP_DD");
+  } else {
+    gfits_modify (&header, "SHMAP_DR", "%s", 1, "NONE");
+    gfits_modify (&header, "SHMAP_DD", "%s", 1, "NONE");
+  }
+  if (set->coords) {
+    gfits_modify (&header, "LOCAL_DR", "%s", 1, "LOCAL_DR");
+    gfits_modify (&header, "LOCAL_DD", "%s", 1, "LOCAL_DD");
+  } else {
+    gfits_modify (&header, "LOCAL_DR", "%s", 1, "NONE");
+    gfits_modify (&header, "LOCAL_DD", "%s", 1, "NONE");
+  }
+
+  // write the PHU header (& empty matrix) to disk 
+  status = gfits_fwrite_header  (f, &header);
+  CHECK_STATUS (status, "ERROR: cannot write header for frame correction %s\n", filename);
+
+  status = gfits_fwrite_matrix  (f, &matrix);
+  CHECK_STATUS (status, "ERROR: cannot write matrix for correction %s\n", filename);
+
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  if (set->frame) {
+    // create and save the RA correction
+    FrameCorrectionSHtoImage (&header, &matrix, set->frame, TRUE);
+    gfits_modify (&header, "EXTNAME", "%s", 1, "SHMAP_DR");
+
+    status = gfits_fwrite_header  (f, &header);
+    CHECK_STATUS (status, "ERROR: cannot write header for SHMAP_DR %s\n", filename);
+
+    status = gfits_fwrite_matrix  (f, &matrix);
+    CHECK_STATUS (status, "ERROR: cannot write matrix for SHMAP_DR %s\n", filename);
+
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+
+    // create and save the DEC correction
+    FrameCorrectionSHtoImage (&header, &matrix, set->frame, FALSE);
+    gfits_modify (&header, "EXTNAME", "%s", 1, "SHMAP_DD");
+
+    status = gfits_fwrite_header  (f, &header);
+    CHECK_STATUS (status, "ERROR: cannot write header for SHMAP_DD %s\n", filename);
+
+    status = gfits_fwrite_matrix  (f, &matrix);
+    CHECK_STATUS (status, "ERROR: cannot write matrix for SHMAP_DD %s\n", filename);
+
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+  }
+
+  if (set->coords) {
+    // create and save the RA correction
+    FrameCorrectionMapToImage (&header, &matrix, set->coords, TRUE);
+    gfits_modify (&header, "EXTNAME", "%s", 1, "LOCAL_DR");
+
+    status = gfits_fwrite_header  (f, &header);
+    CHECK_STATUS (status, "ERROR: cannot write header for meanpos %s\n", filename);
+
+    status = gfits_fwrite_matrix  (f, &matrix);
+    CHECK_STATUS (status, "ERROR: cannot write matrix for meanpos %s\n", filename);
+
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+
+    // create and save the DEC correction
+    FrameCorrectionMapToImage (&header, &matrix, set->coords, FALSE);
+    gfits_modify (&header, "EXTNAME", "%s", 1, "LOCAL_DD");
+
+    status = gfits_fwrite_header  (f, &header);
+    CHECK_STATUS (status, "ERROR: cannot write header for meanpos %s\n", filename);
+
+    status = gfits_fwrite_matrix  (f, &matrix);
+    CHECK_STATUS (status, "ERROR: cannot write matrix for meanpos %s\n", filename);
+
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+  }
+
+  int fd = fileno (f);
+
+  status = fflush (f);
+  CHECK_STATUS (!status, "ERROR: cannot flush file meanpos %s\n", filename);
+
+  status = fsync (fd);
+  CHECK_STATUS (!status, "ERROR: cannot flush file meanpos %s\n", filename);
+
+  status = fclose (f);
+  CHECK_STATUS (!status, "ERROR: problem closing meanpos file %s\n", filename);
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/relastro/src/FrameCorrectionUtils.c
===================================================================
--- /trunk/Ohana/src/relastro/src/FrameCorrectionUtils.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/FrameCorrectionUtils.c	(revision 37807)
@@ -0,0 +1,277 @@
+# include "relastro.h"
+
+// functions related to structure management
+
+FrameCorrectionSet *FrameCorrectionSetInit () {
+
+  FrameCorrectionSet *set = NULL;
+  ALLOCATE (set, FrameCorrectionSet, 1);
+
+  set->frame = NULL;
+  set->coords = NULL;
+  return set;
+}
+
+void FrameCorrectionSetFree (FrameCorrectionSet *set) {
+
+  if (!set) return;
+
+  FrameCorrectionFree(set->frame);
+  if (set->coords) AstromOffsetMapFree (set->coords->offsetMap);
+  free (set->coords);
+
+  free (set);
+  return;
+}
+
+FrameCorrectionType *FrameCorrectionInit (double scale) {
+
+  int i;
+
+  FrameCorrectionType *frame = NULL;
+  ALLOCATE (frame, FrameCorrectionType, 1);
+  
+  frame->scale = scale; // degrees / pixel
+  frame->Ndec = 180.0 / scale;
+  if (frame->Ndec * scale < 180.0) frame->Ndec ++;
+
+  // binD = (DEC + 89.0) / scale
+  ALLOCATE (frame->Nra,  int, frame->Ndec);
+  ALLOCATE (frame->dR,   double, frame->Ndec);
+  ALLOCATE (frame->Roff, double *, frame->Ndec);
+  ALLOCATE (frame->Doff, double *, frame->Ndec);
+
+  // I am going to avoid corrections at |D| = 90 
+
+  // It may be sleazy, but since I'm using SH, I'm going to just correct the region at |D|
+  // > 80 with a 2D grid of corrections to X,Y projections
+
+  for (i = 0; i < frame->Ndec; i++) {
+    double D = i * scale - 89.0;
+    if (D > 90.0) {
+      frame->dR[i] = 0.0;
+      frame->Nra[i] = 0;
+      frame->Roff[i] = NULL;
+      frame->Doff[i] = NULL;
+      continue;
+    }
+    frame->dR[i] = scale / cos(RAD_DEG*D);
+    frame->Nra[i] = MAX (0, 360.0 / frame->dR[i]);
+    if (frame->Nra[i] * frame->dR[i] < 360.0) frame->Nra[i] ++;
+
+    ALLOCATE (frame->Roff[i], double, frame->Nra[i]);
+    ALLOCATE (frame->Doff[i], double, frame->Nra[i]);
+    // ohana_memcheck_func (TRUE);
+    // fprintf (stderr, "alloc : %d : %lx %lx\n", i, (long unsigned int)(size_t *) frame->Roff[i], (long unsigned int) (size_t *) frame->Doff[i]);
+  }
+  return frame;
+}
+
+void FrameCorrectionFree (FrameCorrectionType *frame) {
+
+  if (!frame) return;
+
+  int i;
+
+  for (i = 0; i < frame->Ndec; i++) {
+    if (frame->Roff[i]) free (frame->Roff[i]);
+    if (frame->Doff[i]) free (frame->Doff[i]);
+  }
+
+  if (frame->dR)   free (frame->dR);
+  if (frame->Nra)  free (frame->Nra);
+  if (frame->Roff) free (frame->Roff);
+  if (frame->Doff) free (frame->Doff);
+
+  free (frame);
+  
+  return;
+}
+
+int FrameCorrectionFromSH (FrameCorrectionType *frame, SHterms *dR, SHterms *dD) {
+
+  myAssert (dR->lmax == dD->lmax, "dR and dD must match\n");
+
+  // allocate an SHterms structure to hold the Ylm values 
+  SHterms *SH = SHtermsInit (dR->lmax);
+
+  int i, j, k;
+  // Now I need to apply the correction coefficients to SH images to generate an image
+  for (i = 0; i < frame->Ndec; i++) {
+    double D = i * frame->scale - 89.0;
+    for (j = 0; j < frame->Nra[i]; j++) {
+      double R = j * frame->dR[i];
+      
+      SHtermsForRD (SH, R, D);
+      
+      frame->Roff[i][j] = 0.0;
+      frame->Doff[i][j] = 0.0;
+
+      for (k = 0; k < SH->Nterms; k++) {
+	frame->Roff[i][j] += dR->Fr[k]*SH->Fr[k] + dR->Fi[k]*SH->Fi[k];
+	frame->Doff[i][j] += dD->Fr[k]*SH->Fr[k] + dD->Fi[k]*SH->Fi[k];
+      }
+    }      
+  }
+  SHtermsFree (SH);
+  return TRUE;
+}
+
+// write out the maps as images
+FrameCorrectionType *FrameCorrectionImageToSH (Header *header, Matrix *matrix, FrameCorrectionType *frame, int raDirection) {
+
+  int i, j;
+
+  if (!frame) {
+    double pltscale;
+    if (!gfits_scan (header, "PLTSCALE", "%lf", 1, &pltscale)) {
+      fprintf (stderr, "failed to find PLTSCALE in header\n");
+      exit (2);
+    }
+    frame = FrameCorrectionInit (pltscale);
+  }
+
+  Coords coords;
+  GetCoords (&coords, header);
+
+  int Nx = header->Naxis[0];
+  // int Ny = header->Naxis[1];
+
+  float   *buffer = (float *)matrix->buffer;
+  double **value  = (raDirection) ? frame->Roff : frame->Doff;
+
+  // Now I need to apply the correction coefficients to SH images to generate an image
+  for (i = 0; i < frame->Ndec; i++) {
+    double D = i * frame->scale - 89.0;
+    for (j = 0; j < frame->Nra[i]; j++) {
+      double R = j * frame->dR[i];
+      
+      value[i][j] = 0.0;
+
+      double X, Y;
+      if (!RD_to_XY (&X, &Y, R, D, &coords)) continue;
+
+      int ix = X;
+      int iy = Y;
+      value[i][j] = buffer[ix + Nx*iy];
+    }      
+  }
+  return frame;
+}
+
+int FrameCorrectionSHtoImage (Header *header, Matrix *matrix, FrameCorrectionType *frame, int raDirection) {
+
+  Coords coords;
+    
+  gfits_init_header (header);
+  header->bitpix = -32;
+  header->Naxes = 2;
+  header->Naxis[0] = 370.0 / frame->scale;
+  header->Naxis[1] = 190.0 / frame->scale;
+
+  int Nx = header->Naxis[0];
+  int Ny = header->Naxis[1];
+
+  gfits_create_header (header);
+  gfits_create_matrix (header, matrix);
+
+  gfits_modify (header, "PLTSCALE", "%lf", 1, frame->scale);
+
+  InitCoords (&coords, "DEC--AIT");
+  coords.cdelt1 = coords.cdelt2 = frame->scale;
+  coords.crval1 = 0.0; // SAS center
+  coords.crval2 = 0.0;
+  coords.crpix1 = 0.5*Nx; // middle of projection is middle of map
+  coords.crpix2 = 0.5*Ny; // middle of projection is middle of map
+    
+  PutCoords (&coords, header);
+
+  float *buffer = (float *)matrix->buffer;
+
+  int ix, iy;
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+      double R, D;
+      int status = XY_to_RD (&R, &D, ix, iy, &coords);
+      if (!status) continue;
+
+      R = ohana_normalize_angle (R);
+
+      int iD = (D + 89.0) / frame->scale;
+      if (iD < 0) continue;
+      if (iD >= frame->Ndec) continue;
+      int iR = R / frame->dR[iD];
+      if (iR < 0) continue;
+      if (iR >= frame->Nra[iD]) continue;
+
+
+      float value = NAN;
+      if (raDirection) {
+	value = frame->Roff[iD][iR];
+      } else {
+	value = frame->Doff[iD][iR];
+      }
+      buffer[ix + Nx*iy] = value;
+    }
+  }
+  return TRUE;
+}
+
+// write out the AstromOffsetMap linked by the Coords structure
+int FrameCorrectionMapToImage (Header *header, Matrix *matrix, Coords *coords, int raDirection) {
+  // write out the maps as images
+
+  AstromOffsetMap *map = coords->offsetMap;
+  if (!map) {
+    fprintf (stderr, "ERROR: missing AstromOffsetMap link in coords\n");
+    return FALSE;
+  }
+
+  gfits_init_header (header);
+  header->bitpix = -32;
+  header->Naxes = 2;
+  header->Naxis[0] = map->Nx;
+  header->Naxis[1] = map->Ny;
+
+  gfits_create_header (header);
+  gfits_create_matrix (header, matrix);
+  PutCoords (coords, header);
+
+  float *buffer = (float *)matrix->buffer;
+
+  float **value = (raDirection) ? map->dXv : map->dYv;
+
+  int ix, iy;
+  for (ix = 0; ix < map->Nx; ix++) {
+    for (iy = 0; iy < map->Ny; iy++) {
+      buffer[ix + map->Nx*iy] = value[ix][iy];
+    }
+  }
+  return TRUE;
+}
+
+Coords *FrameCorrectionImageToMap (Header *header, Matrix *matrix, Coords *coords, int raDirection) {
+  // write out the maps as images
+
+  if (!coords) {
+    ALLOCATE (coords, Coords, 1);
+    GetCoords (coords, header);
+    coords->offsetMap = AstromOffsetMapInit (header->Naxis[0], header->Naxis[1]);
+    coords->offsetMap->dX = 1.0; // scale from projection (in arcsec) to correction patches
+    coords->offsetMap->dY = 1.0;
+  }
+  AstromOffsetMap *map = coords->offsetMap;
+
+  float *buffer = (float *) matrix->buffer;
+
+  float **value = (raDirection) ? map->dXv : map->dYv;
+
+  int ix, iy;
+  for (ix = 0; ix < map->Nx; ix++) {
+    for (iy = 0; iy < map->Ny; iy++) {
+      value[ix][iy] = buffer[ix + map->Nx*iy];
+    }
+  }
+  return coords;
+}
+
Index: /trunk/Ohana/src/relastro/src/ICRF.c
===================================================================
--- /trunk/Ohana/src/relastro/src/ICRF.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/ICRF.c	(revision 37807)
@@ -0,0 +1,92 @@
+# include "relastro.h"
+
+int Nicrf = 0;
+int NICRF = 0;
+int *ICRFtoCatalog = NULL;
+int *ICRFtoAverage = NULL;
+int *ICRFtoMeasure = NULL;
+
+void ICRFinit () {
+
+  Nicrf = 0;
+  NICRF = 500;
+  ALLOCATE (ICRFtoCatalog, int, NICRF);
+  ALLOCATE (ICRFtoAverage, int, NICRF);
+  ALLOCATE (ICRFtoMeasure, int, NICRF);
+}
+
+int ICRFsave (int cat, int ave, int meas) {
+  
+  // fprintf (stderr, "ICRF: %d %d %d\n", cat, ave, meas);
+
+  ICRFtoCatalog[Nicrf] = cat;
+  ICRFtoAverage[Nicrf] = ave;
+  ICRFtoMeasure[Nicrf] = meas;
+
+  Nicrf ++;
+  if (Nicrf == NICRF) {
+    NICRF += 500;
+
+    REALLOCATE (ICRFtoCatalog, int, NICRF);
+    REALLOCATE (ICRFtoAverage, int, NICRF);
+    REALLOCATE (ICRFtoMeasure, int, NICRF);
+  }
+  return TRUE;
+}
+
+int ICRFmax () {
+  return Nicrf;
+}
+
+int ICRFdata (int n, int *cat, int *ave, int *meas) {
+
+  myAssert (n >= 0 && n < Nicrf, "out of range");
+
+  *cat  = ICRFtoCatalog[n];
+  *ave  = ICRFtoAverage[n];
+  *meas = ICRFtoMeasure[n];
+  return TRUE;
+}
+
+int select_catalog_ICRF (Catalog *catalog, int Ncatalog) {
+
+  if (!USE_ICRF_CORRECT) return TRUE;
+
+  int N = 0;
+
+  char filename[1024];
+  snprintf (filename, 1024, "%s/test.icrf.dat", CATDIR);
+  FILE *f = fopen (filename, "w");
+
+  int c, i, j;
+  for (c = 0; c < Ncatalog; c++) {
+
+    for (i = 0; i < catalog[c].Naverage; i++) {
+      if (!(catalog[c].average[i].flags & ID_ICRF_QSO)) continue;
+
+      // only save a single value
+      int savedICRF = FALSE;
+      for (j = 0; j < catalog[c].average[i].Nmeasure; j++) {
+	  
+	int offset = catalog[c].average[i].measureOffset + j;
+	  
+	if (!(catalog[c].measureT[offset].dbFlags & ID_MEAS_ICRF_QSO)) continue;
+
+	fprintf (f, "%d %d %d : %f %f : %f %f\n", c, i, offset,
+		 catalog[c].average[i].R, catalog[c].average[i].D,
+		 catalog[c].measureT[offset].R, catalog[c].measureT[offset].D);
+
+	if (savedICRF) continue;
+
+	ICRFsave (c, i, offset);
+	N ++;
+	savedICRF = TRUE;
+      }
+    }
+  }
+  fclose (f);
+
+  fprintf (stderr, "added %d ICRF QSO\n", N);
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/relastro/src/ICRFobjIO.c
===================================================================
--- /trunk/Ohana/src/relastro/src/ICRFobjIO.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/ICRFobjIO.c	(revision 37807)
@@ -0,0 +1,146 @@
+# include "relastro.h"
+
+// column OUT refers to a variable which is already defined
+# define GET_COLUMN(OUT,NAME,TYPE) \
+  OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+// this is nearly identical to the one in 'uniphot/src' used by setphot / setphot_client.
+// Here, we use a handful of different columns (if not, we could move to libdvo)
+ICRFobj *ICRFobjLoad(char *filename) {
+
+  int Ncol;
+  off_t Nrow;
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  ICRFobj *icrfobj = NULL;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset header\n");
+    fclose (f);
+    return NULL;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset matrix\n");
+    gfits_free_header (&header);
+    fclose (f);
+    return NULL;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) {
+    fclose (f);
+    return NULL;
+  }
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
+    fclose (f);
+    return (NULL);
+  }
+  fclose (f);
+
+  char type[16]; // used in GET_COLUMN
+
+  ALLOCATE (icrfobj, ICRFobj, 1);
+
+  GET_COLUMN (icrfobj->Rave,   	 "R_AVE",        double);
+  GET_COLUMN (icrfobj->Dave,   	 "D_AVE",        double);
+  GET_COLUMN (icrfobj->dRoff,  	 "R_OFF",        double);
+  GET_COLUMN (icrfobj->dDoff,  	 "D_OFF",        double);
+  fprintf (stderr, "loaded data for %lld objects (* filters)\n", (long long) Nrow);
+
+  // free FITS table pieces...
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  icrfobj->Nicrfobj = Nrow;
+  return icrfobj;
+}
+
+// STATUS is value expected for success
+# define CHECK_STATUS(STATUS,MSG,...)					\
+  if (!(STATUS)) {							\
+    fprintf (stderr, MSG, __VA_ARGS__);					\
+    return FALSE;							\
+  }
+
+int ICRFobjSave(char *filename, ICRFobj *icrfobj) {
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  gfits_create_table_header (&theader, "BINTABLE", "ICRFOBJ");
+
+  gfits_define_bintable_column (&theader, "D", "R_AVE",      "mean position, ra",  "degrees", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "D_AVE",      "mean position, dec", "degrees", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "R_OFF",      "position offset, ra",  "arcsec", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "D_OFF",      "position offset, dec", "arcsec", 1.0, 0.0);
+
+  // generate the output array that carries the data
+  gfits_create_table (&theader, &ftable);
+
+  // add the columns to the output array
+  gfits_set_bintable_column (&theader, &ftable, "R_AVE",       icrfobj->Rave ,         icrfobj->Nicrfobj);
+  gfits_set_bintable_column (&theader, &ftable, "D_AVE",       icrfobj->Dave ,         icrfobj->Nicrfobj);
+  gfits_set_bintable_column (&theader, &ftable, "R_OFF",       icrfobj->dRoff,         icrfobj->Nicrfobj);
+  gfits_set_bintable_column (&theader, &ftable, "D_OFF",       icrfobj->dDoff,         icrfobj->Nicrfobj);
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open meanmag file for output %s\n", filename);
+    return FALSE;
+  }
+
+  int status;
+  status = gfits_fwrite_header  (f, &header);
+  CHECK_STATUS (status, "ERROR: cannot write header for icrfobj %s\n", filename);
+
+  status = gfits_fwrite_matrix  (f, &matrix);
+  CHECK_STATUS (status, "ERROR: cannot write matrix for icrfobj %s\n", filename);
+
+  status = gfits_fwrite_Theader (f, &theader);
+  CHECK_STATUS (status, "ERROR: cannot write table header for icrfobj %s\n", filename);
+
+  status = gfits_fwrite_table  (f, &ftable);
+  CHECK_STATUS (status, "ERROR: cannot write table data for icrfobj %s\n", filename);
+
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  int fd = fileno (f);
+
+  status = fflush (f);
+  CHECK_STATUS (!status, "ERROR: cannot flush file icrfobj %s\n", filename);
+
+  status = fsync (fd);
+  CHECK_STATUS (!status, "ERROR: cannot fsync file icrfobj %s\n", filename);
+
+  status = fclose (f);
+  CHECK_STATUS (!status, "ERROR: problem closing icrfobj file %s\n", filename);
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/relastro/src/ImageOps.c
===================================================================
--- /trunk/Ohana/src/relastro/src/ImageOps.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/ImageOps.c	(revision 37807)
@@ -28,5 +28,14 @@
 static off_t        *imageIDs; // list of all image IDs
 static off_t        *imageIdx; // list of index for image IDs 
-# else
+
+// as an alternative, we generate imageSeq, which directly maps imageID -> seq
+static off_t        *imageSeq; // list of index for image IDs 
+
+// MAX_ID requires 512M to store the image index
+# define MAX_ID      0x8000000
+static off_t         minImageID = MAX_ID;
+static off_t         maxImageID = 0;
+
+# else /* not using IMAGE_ID */
 static unsigned int *start;
 static unsigned int *stop;
@@ -84,6 +93,21 @@
     imageIdx[i] = i;
     imageIDs[i] = image[i].imageID;
+    minImageID = MIN(minImageID, image[i].imageID);
+    maxImageID = MAX(maxImageID, image[i].imageID);
+    myAssert (image[i].imageID < MAX_ID, "image IDs too large for index memory");
   }
   llsortpair (imageIDs, imageIdx, Nimage);
+
+  ALLOCATE (imageSeq, off_t, maxImageID + 1);
+  for (i = 0; i < maxImageID + 1; i++)  {
+    imageSeq[i] = -1; // not yet assigned
+  }
+
+  for (i = 0; i < Nimage; i++) {
+    off_t N = image[i].imageID;
+    myAssert (imageSeq[N] == -1, "previously assigned");
+    imageSeq[N] = i;
+  }
+
 # else
   ALLOCATE (start, unsigned, Nimage);
@@ -98,9 +122,16 @@
 
 off_t getImageByID (off_t ID) {
+# if USE_IMAGE_ID
+
+  if (imageSeq) {
+    if (ID < minImageID) return (-1);
+    if (ID > maxImageID) return (-1);
+    off_t N = imageSeq[ID];
+    return N;
+  }
 
   // we have a pair of vectors (imageIDs, imageIdx) sorted by imageIDs
   // use bisection to find the specified image ID
 
-# if USE_IMAGE_ID
   off_t Nlo, Nhi, N;
 
@@ -207,18 +238,19 @@
     if (VERBOSE2 || (MATCHCAT && (N_onImage[i] < 20))) {
       name = GetPhotcodeNamebyCode (image[i].photcode);
-      fprintf (stderr, "image "OFF_T_FMT" (%d, %s) has "OFF_T_FMT" of %d measures (%s, %s)",  i,  image[i].imageID, image[i].name, N_onImage[i], image[i].nstar,
+      int showExample = (Nfew < 30);
+      if (showExample) fprintf (stderr, "image "OFF_T_FMT" (%d, %s) has "OFF_T_FMT" of %d measures (%s, %s) ",  i,  image[i].imageID, image[i].name, N_onImage[i], image[i].nstar,
 	       ohana_sec_to_date(image[i].tzero), name);
       if (N_onImage[i] < 20) {
-	fprintf (stderr, "*");
+	if (showExample) fprintf (stderr, "*");
 	Nfew ++;
       } 
       if (N_onImage[i] < 15) {
-	fprintf (stderr, "*** warning ***");
+	if (showExample) fprintf (stderr, "*** warning ***");
 	Nbad ++;
       } 
-      fprintf (stderr, "\n");
-    }
-  }
-  fprintf (stderr, OFF_T_FMT" total images, %d with < 20 measurements, %d with < 15\n", Nimage, Nfew, Nbad);
+      if (showExample) fprintf (stderr, "\n");
+    }
+  }
+  if (MATCHCAT) fprintf (stderr, OFF_T_FMT" total images, %d with < 20 measurements, %d with < 15\n", Nimage, Nfew, Nbad);
 }
 
@@ -245,4 +277,6 @@
   }
 
+  if (!ID) return; // detection not associated with an image
+
   idx = getImageByID (ID);
   if (idx == -1) {
@@ -250,4 +284,5 @@
     return;
   }
+  measure->myDet = TRUE; // I 'own' this detection (I am calibrating its image)
 
   // index for (catalog, measure) -> image
@@ -334,15 +369,4 @@
 # endif
 
-/*
-Coords *getCoords (off_t meas, int cat) {
-
-  off_t i;
-
-  i = MeasureToImage[cat][meas];
-  if (i == -1) return (NULL);
-  return (&image[i].coords);
-}
-*/
-
 float getColorBlue (off_t meas, int cat) {
 
@@ -433,7 +457,4 @@
   Coords *moscoords, *imcoords;
 
-  // check if this image is bad and should be skipped
-  if (badCoords(im)) return;
-
   // WRP images need to have an associated mosaic
   moscoords = NULL;
@@ -491,7 +512,6 @@
 
     int TESTPT = FALSE;
-
-    TESTPT |= CAT_ID_SRC && OBJ_ID_SRC && (measure->imageID == CAT_ID_SRC) && (measure->detID == OBJ_ID_SRC);
-    TESTPT |= CAT_ID_DST && OBJ_ID_DST && (measure->imageID == CAT_ID_DST) && (measure->detID == OBJ_ID_DST);
+    TESTPT |= CAT_ID_SRC && OBJ_ID_SRC && (measure->catID == CAT_ID_SRC) && (measure->objID == OBJ_ID_SRC);
+    TESTPT |= CAT_ID_DST && OBJ_ID_DST && (measure->catID == CAT_ID_DST) && (measure->objID == OBJ_ID_DST);
     if (TESTPT) {
       fprintf (stderr, "got test det\n");
@@ -597,5 +617,4 @@
   int Noff = NoffRAave + NoffDECave + NoffRAori + NoffDECori;
   if (VERBOSE2 && (Noff > 0)) fprintf (stderr, "Noff ave RA %d, Noff ave DEC %d, Noff ori RA %d, Noff ori DEC %d\n", NoffRAave, NoffDECave, NoffRAori, NoffDECori);
-  saveOffsets (dPos, nPos, im);
 
   return;
@@ -604,86 +623,4 @@
 void printNcatTotal () {
   fprintf (stderr, "NcatTotal: %d\n", NcatTotal);
-}
-
-// return StarData values for detections in the specified image, converting coordinates from the
-// chip positions: X,Y -> L,M -> P,Q -> R,D
-void resetImageRaw (Catalog *catalog, int Ncatalog, off_t im) {
-
-  off_t i, m, c, n;
-  double X, Y, L, M, P, Q, R, D;
-
-  Mosaic *mosaic;
-  Coords *moscoords, *imcoords, *oldcoords;
-
-  fprintf (stderr, "fix resetImageRaw wrt MeasureTiny\n");
-  abort();
-
-  // check if this image is bad and should be skipped
-  if (!badCoords(im)) {
-    fprintf (stderr, "ERROR: inconsistent result?");
-    abort();
-  }
-
-  // replace the current coords with the old coords:
-  oldcoords = getCoords (im);
-  memcpy (&image[im].coords, oldcoords, sizeof(Coords));
-
-  // WRP images need to have an associated mosaic
-  moscoords = NULL;
-  if (!strcmp(&image[im].coords.ctype[4], "-WRP")) {
-    mosaic = getMosaicForImage (im);
-    if (mosaic == NULL) return;  // if we cannot find the associated image, skip it
-    moscoords = &mosaic[0].coords;
-  }
-  imcoords = &image[im].coords;
-
-  for (i = 0; i < N_onImage[im]; i++) {
-    m = ImageToMeasure[im][i];
-    c = ImageToCatalog[im][i];
-
-    // XXX unclear if this should use Measure or MeasureTiny; it is only called by FixProblemImages, which has not been updated
-    Measure *measure = &catalog[c].measure[m];
-
-    X = measure[0].Xccd;
-    Y = measure[0].Yccd;
-    if (USE_FIXED_PIXCOORDS) {
-      if (isfinite(measure[0].Xfix) && isfinite(measure[0].Yfix)) {
-	float dX = measure[0].Xfix - measure[0].Xccd;
-	float dY = measure[0].Yfix - measure[0].Yccd;
-	if (hypot(dX,dY) < 2.0) {
-	  X = measure[0].Xfix;
-	  Y = measure[0].Yfix;
-	} 
-      }
-    }
-    n = measure[0].averef;
-
-    if (moscoords == NULL) {
-      // this is a Simple image (not a mosaic)
-      // note that for a Simple image, L,M = P,Q
-      XY_to_LM (&L, &M, X, Y, imcoords);
-      LM_to_RD (&R, &D, L, M, imcoords);
-    } else {
-      XY_to_LM (&L, &M, X, Y, imcoords);
-      XY_to_LM (&P, &Q, L, M, moscoords);
-      LM_to_RD (&R, &D, P, Q, moscoords);
-    }
-    measure[0].R = R;
-    measure[0].D = D;
-
-    float dRoff = dvoOffsetR(measure, &catalog[c].average[n]);
-
-    if (dRoff > +180.0*3600.0) {
-      // average on high end of boundary, move star up
-      measure[0].R += 360.0;
-      dRoff -= 360.0*3600.0;
-    }
-    if (dRoff < -180.0*3600.0) {
-      measure[0].R -= 360.0;
-      dRoff += 360.0*3600.0;
-    }
-  }
-
-  return;
 }
 
@@ -736,4 +673,11 @@
 
     n = measure[0].averef;
+
+    int TESTPT = FALSE;
+    TESTPT |= CAT_ID_SRC && OBJ_ID_SRC && (catalog[c].average[n].catID == CAT_ID_SRC) && (catalog[c].average[n].objID == OBJ_ID_SRC);
+    TESTPT |= CAT_ID_DST && OBJ_ID_DST && (catalog[c].average[n].catID == CAT_ID_DST) && (catalog[c].average[n].objID == OBJ_ID_DST);
+    if (TESTPT) {
+      fprintf (stderr, "got test det\n");
+    }
 
     // an object with only one detection provides no information about the image calibration
@@ -808,4 +752,12 @@
     ref[i].D = catalog[c].average[n].D;
 
+    // if we are correcting for the Galaxy Motion Model, we assume the mean R,D is at the J2000 epoch position
+    if (USE_GALAXY_MODEL) {
+      myAssert (!isnan(measure[0].RoffGAL), "oops");
+      myAssert (!isnan(measure[0].DoffGAL), "oops");
+      ref[i].R += measure[0].RoffGAL / 3600.0;
+      ref[i].D += measure[0].DoffGAL / 3600.0;
+    }
+
     ref[i].Mag  = measure[0].M;
     ref[i].dMag = measure[0].dM;
@@ -842,4 +794,95 @@
   *Nstars = N_onImage[im];
   return (ref);
+}
+
+// return StarData values for detections in the specified image, converting coordinates from the
+// chip positions: X,Y -> L,M -> P,Q -> R,D.  This function is used by the image fitting steps, for
+// which the detections have already been filtered when they were loaded (bcatalog)
+int setImageRaw (Catalog *catalog, int Ncatalog, off_t im, StarData *raw, off_t Nraw, CoordMode mode) {
+
+  off_t i, m, c;
+
+  Coords *moscoords;
+
+  moscoords = NULL;
+  if (mode == MODE_MOSAIC) {
+    moscoords = image[im].coords.mosaic;
+    myAssert (moscoords, "coords.mosaic not defined for image %s (%d)", image[im].name, (int) im);
+  }
+
+  myAssert (Nraw == N_onImage[im], "impossible!");
+
+  for (i = 0; i < N_onImage[im]; i++) {
+    m = ImageToMeasure[im][i];
+    c = ImageToCatalog[im][i];
+
+    // XXX should I use the raw coords or just measure.X,Y -> R,D?
+
+    switch (mode) {
+      case MODE_SIMPLE:
+        /* note that for a Simple image, L,M = P,Q */
+        XY_to_LM (&raw[i].L, &raw[i].M, raw[i].X, raw[i].Y, &image[im].coords);
+        raw[i].P = raw[i].L;
+        raw[i].Q = raw[i].M;
+        LM_to_RD (&raw[i].R, &raw[i].D, raw[i].P, raw[i].Q, &image[im].coords);
+        break;
+      case MODE_MOSAIC:
+        XY_to_LM (&raw[i].L, &raw[i].M, raw[i].X, raw[i].Y, &image[im].coords);
+        XY_to_LM (&raw[i].P, &raw[i].Q, raw[i].L, raw[i].M, moscoords);
+        LM_to_RD (&raw[i].R, &raw[i].D, raw[i].P, raw[i].Q, moscoords);
+        break;
+      default:
+	fprintf (stderr, "error: invalid mode in getImageRaw");
+	abort ();
+    }
+
+    MeasureTiny *measure = &catalog[c].measureT[m];
+    measure->R = raw[i].R;
+    measure->D = raw[i].D;
+    if (catalog[c].measure) {
+      catalog[c].measure[m].R = raw[i].R;
+      catalog[c].measure[m].D = raw[i].D;
+    }      
+  }
+  return TRUE;
+}
+
+// return StarData values for detections in the specified image, converting coordinates from the
+// chip positions: X,Y -> L,M -> P,Q -> R,D.  This function is used by the image fitting steps, for
+// which the detections have already been filtered when they were loaded (bcatalog)
+int updateImageRaw (Catalog *catalog, int Ncatalog, off_t im) {
+
+  off_t i, m, c;
+
+  for (i = 0; i < N_onImage[im]; i++) {
+    m = ImageToMeasure[im][i];
+    c = ImageToCatalog[im][i];
+
+    MeasureTiny *measure = &catalog[c].measureT[m];
+
+    /* apply the current image transformation or use the current value of R+dR, D+dD? */
+    double X = measure[0].Xccd;
+    double Y = measure[0].Yccd;
+    if (USE_FIXED_PIXCOORDS) {
+      if (isfinite(measure[0].Xfix) && isfinite(measure[0].Yfix)) {
+	float dX = measure[0].Xfix - measure[0].Xccd;
+	float dY = measure[0].Yfix - measure[0].Yccd;
+	if (hypot(dX,dY) < 2.0) {
+	  X = measure[0].Xfix;
+	  Y = measure[0].Yfix;
+	} 
+      }
+    }
+
+    double R, D;
+    XY_to_RD (&R, &D, X, Y, &image[im].coords);
+    measure->R = R;
+    measure->D = D;
+    if (catalog[c].measure) {
+      catalog[c].measure[m].R = R;
+      catalog[c].measure[m].D = D;
+    }      
+  }
+  return TRUE;
 }
 
Index: /trunk/Ohana/src/relastro/src/ImagePosIO.c
===================================================================
--- /trunk/Ohana/src/relastro/src/ImagePosIO.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/ImagePosIO.c	(revision 37807)
@@ -70,7 +70,9 @@
   GET_COLUMN (polyterms,        "POLYTERMS",         float); // verify that we got 14 columns?
   GET_COLUMN (ctype,            "CTYPE",             char);  // verify that we got 15 columns?
-  GET_COLUMN (Npolyterms,       "NPOLYTERMS",        char);
+  GET_COLUMN (Npolyterms,       "NPOLYTERMS",        byte);
   GET_COLUMN (dXpixSys,         "XPIX_SYS_ERR",      float);
   GET_COLUMN (dYpixSys,         "YPIX_SYS_ERR",      float);
+  GET_COLUMN (refColorBlue,     "REF_COLOR_BLUE",    float);
+  GET_COLUMN (refColorRed,      "REF_COLOR_RED",     float);
   GET_COLUMN (imageID,          "ID",                int);
   GET_COLUMN (nFitAstrom,       "NFIT",              int);
@@ -85,20 +87,23 @@
   ALLOCATE (image_pos, ImagePos, Nrow);
   for (i = 0; i < Nrow; i++) {
-    image_pos[i].coords.crval1             = crval1    [i];
-    image_pos[i].coords.crval2             = crval2    [i];
-    image_pos[i].coords.crpix1             = crpix1    [i];
-    image_pos[i].coords.crpix2             = crpix2    [i];
-    image_pos[i].coords.cdelt1             = cdelt1    [i];
-    image_pos[i].coords.cdelt2             = cdelt2    [i];
-    image_pos[i].coords.pc1_1              = pc1_1     [i];
-    image_pos[i].coords.pc1_2              = pc1_2     [i];
-    image_pos[i].coords.pc2_1              = pc2_1     [i];
-    image_pos[i].coords.pc2_2              = pc2_2     [i];
-    image_pos[i].coords.Npolyterms         = Npolyterms[i];
-    image_pos[i].dXpixSys                  = dXpixSys  [i];
-    image_pos[i].dYpixSys                  = dYpixSys  [i];
-    image_pos[i].imageID                   = imageID   [i];
-    image_pos[i].nFitAstrom                = nFitAstrom[i];
-    image_pos[i].flags                     = flags     [i];
+    InitCoords(&image_pos[i].coords, NULL);
+    image_pos[i].coords.crval1             = crval1      [i];
+    image_pos[i].coords.crval2             = crval2      [i];
+    image_pos[i].coords.crpix1             = crpix1      [i];
+    image_pos[i].coords.crpix2             = crpix2      [i];
+    image_pos[i].coords.cdelt1             = cdelt1      [i];
+    image_pos[i].coords.cdelt2             = cdelt2      [i];
+    image_pos[i].coords.pc1_1              = pc1_1       [i];
+    image_pos[i].coords.pc1_2              = pc1_2       [i];
+    image_pos[i].coords.pc2_1              = pc2_1       [i];
+    image_pos[i].coords.pc2_2              = pc2_2       [i];
+    image_pos[i].coords.Npolyterms         = Npolyterms  [i];
+    image_pos[i].dXpixSys                  = dXpixSys    [i];
+    image_pos[i].dYpixSys                  = dYpixSys    [i];
+    image_pos[i].refColorBlue              = refColorBlue[i];
+    image_pos[i].refColorRed               = refColorRed [i];
+    image_pos[i].imageID                   = imageID     [i];
+    image_pos[i].nFitAstrom                = nFitAstrom  [i];
+    image_pos[i].flags                     = flags       [i];
 
     // polyterms and ctype are a bit different
@@ -108,22 +113,24 @@
   fprintf (stderr, "loaded data for %lld images\n", (long long) Nrow);
 
-  free (crval1    );
-  free (crval2    );
-  free (crpix1    );
-  free (crpix2    );
-  free (cdelt1    );
-  free (cdelt2    );
-  free (pc1_1     );
-  free (pc1_2     );
-  free (pc2_1     );
-  free (pc2_2     );
-  free (polyterms );
-  free (ctype     );
-  free (Npolyterms);
-  free (dXpixSys  );
-  free (dYpixSys  );
-  free (imageID   );
-  free (nFitAstrom);
-  free (flags     );
+  free (crval1      );
+  free (crval2      );
+  free (crpix1      );
+  free (crpix2      );
+  free (cdelt1      );
+  free (cdelt2      );
+  free (pc1_1       );
+  free (pc1_2       );
+  free (pc2_1       );
+  free (pc2_2       );
+  free (polyterms   );
+  free (ctype       );
+  free (Npolyterms  );
+  free (dXpixSys    );
+  free (dYpixSys    );
+  free (refColorBlue);
+  free (refColorRed );
+  free (imageID     );
+  free (nFitAstrom  );
+  free (flags       );
 
   *nimage_pos = Nrow;
@@ -153,44 +160,48 @@
   gfits_create_table_header (&theader, "BINTABLE", "IMAGE_POS");
 
-  gfits_define_bintable_column (&theader, "D",   "CRVAL1",       "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "D",   "CRVAL2",       "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "CRPIX1",       "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "CRPIX2",       "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "CDELT1",       "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "CDELT2",       "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "PC1_1",        "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "PC1_2",        "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "PC2_1",        "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "PC2_2",        "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "14E", "POLYTERMS",    "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "15A", "CTYPE",        "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "L",   "NPOLYTERMS",   "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "XPIX_SYS_ERR", "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "E",   "YPIX_SYS_ERR", "word", "unit", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "J",   "ID",           "image ID",               "unitless", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "J",   "NFIT",         "number of fitted stars", "unitless", 1.0, 0.0);
-  gfits_define_bintable_column (&theader, "J",   "FLAGS",        "analysis flags",         "unitless", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D",   "CRVAL1",         "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D",   "CRVAL2",         "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "CRPIX1",         "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "CRPIX2",         "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "CDELT1",         "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "CDELT2",         "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "PC1_1",          "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "PC1_2",          "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "PC2_1",          "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "PC2_2",          "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "14E", "POLYTERMS",      "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "15A", "CTYPE",          "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "L",   "NPOLYTERMS",     "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "XPIX_SYS_ERR",   "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "YPIX_SYS_ERR",   "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "REF_COLOR_BLUE", "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "E",   "REF_COLOR_RED",  "word", "unit", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J",   "ID",             "image ID",               "unitless", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J",   "NFIT",           "number of fitted stars", "unitless", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J",   "FLAGS",          "analysis flags",         "unitless", 1.0, 0.0);
 
   // generate the output array that carries the data
   gfits_create_table (&theader, &ftable);
 
-  double *crval1    ;
-  double *crval2    ;
-  float  *crpix1    ;
-  float  *crpix2    ;
-  float  *cdelt1    ;
-  float  *cdelt2    ;
-  float  *pc1_1     ;
-  float  *pc1_2     ;
-  float  *pc2_1     ;
-  float  *pc2_2     ;
-  float  *polyterms ;
-  char   *ctype     ;
-  char   *Npolyterms;
-  float  *dXpixSys  ;
-  float  *dYpixSys  ;
-  int    *imageID   ;
-  int    *nFitAstrom;
-  int    *flags     ;
+  double *crval1      ;
+  double *crval2      ;
+  float  *crpix1      ;
+  float  *crpix2      ;
+  float  *cdelt1      ;
+  float  *cdelt2      ;
+  float  *pc1_1       ;
+  float  *pc1_2       ;
+  float  *pc2_1       ;
+  float  *pc2_2       ;
+  float  *polyterms   ;
+  char   *ctype       ;
+  char   *Npolyterms  ;
+  float  *dXpixSys    ;
+  float  *dYpixSys    ;
+  float  *refColorBlue;
+  float  *refColorRed ;
+  int    *imageID     ;
+  int    *nFitAstrom  ;
+  int    *flags       ;
 
   // create intermediate storage arrays
@@ -210,4 +221,6 @@
   ALLOCATE (dXpixSys  ,         float ,          Nimage_pos);
   ALLOCATE (dYpixSys  ,         float ,          Nimage_pos);
+  ALLOCATE (refColorBlue,       float ,          Nimage_pos);
+  ALLOCATE (refColorRed ,       float ,          Nimage_pos);
   ALLOCATE (imageID   ,         int   ,          Nimage_pos);
   ALLOCATE (nFitAstrom,         int   ,          Nimage_pos);
@@ -216,20 +229,22 @@
   // assign the storage arrays
   for (i = 0; i < Nimage_pos; i++) {
-    crval1    [i] = image_pos[i].coords.crval1    ;
-    crval2    [i] = image_pos[i].coords.crval2    ;
-    crpix1    [i] = image_pos[i].coords.crpix1    ;
-    crpix2    [i] = image_pos[i].coords.crpix2    ;
-    cdelt1    [i] = image_pos[i].coords.cdelt1    ;
-    cdelt2    [i] = image_pos[i].coords.cdelt2    ;
-    pc1_1     [i] = image_pos[i].coords.pc1_1     ;
-    pc1_2     [i] = image_pos[i].coords.pc1_2     ;
-    pc2_1     [i] = image_pos[i].coords.pc2_1     ;
-    pc2_2     [i] = image_pos[i].coords.pc2_2     ;
-    Npolyterms[i] = image_pos[i].coords.Npolyterms;
-    dXpixSys  [i] = image_pos[i].dXpixSys         ;
-    dYpixSys  [i] = image_pos[i].dYpixSys         ;
-    imageID   [i] = image_pos[i].imageID          ;
-    nFitAstrom[i] = image_pos[i].nFitAstrom       ;
-    flags     [i] = image_pos[i].flags            ;
+    crval1      [i] = image_pos[i].coords.crval1    ;
+    crval2      [i] = image_pos[i].coords.crval2    ;
+    crpix1      [i] = image_pos[i].coords.crpix1    ;
+    crpix2      [i] = image_pos[i].coords.crpix2    ;
+    cdelt1      [i] = image_pos[i].coords.cdelt1    ;
+    cdelt2      [i] = image_pos[i].coords.cdelt2    ;
+    pc1_1       [i] = image_pos[i].coords.pc1_1     ;
+    pc1_2       [i] = image_pos[i].coords.pc1_2     ;
+    pc2_1       [i] = image_pos[i].coords.pc2_1     ;
+    pc2_2       [i] = image_pos[i].coords.pc2_2     ;
+    Npolyterms  [i] = image_pos[i].coords.Npolyterms;
+    dXpixSys    [i] = image_pos[i].dXpixSys         ;
+    dYpixSys    [i] = image_pos[i].dYpixSys         ;
+    refColorBlue[i] = image_pos[i].refColorBlue     ;
+    refColorRed [i] = image_pos[i].refColorRed      ;
+    imageID     [i] = image_pos[i].imageID          ;
+    nFitAstrom  [i] = image_pos[i].nFitAstrom       ;
+    flags       [i] = image_pos[i].flags            ;
 
     // polyterms and ctype are a bit different
@@ -239,41 +254,45 @@
 
   // add the columns to the output array
-  gfits_set_bintable_column (&theader, &ftable, "CRVAL1",      crval1    ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "CRVAL2",      crval2    ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "CRPIX1",      crpix1    ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "CRPIX2",      crpix2    ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "CDELT1",      cdelt1    ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "CDELT2",      cdelt2    ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "PC1_1",       pc1_1     ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "PC1_2",       pc1_2     ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "PC2_1",       pc2_1     ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "PC2_2",       pc2_2     ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "POLYTERMS",   polyterms ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "CTYPE",       ctype     ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "NPOLYTERMS",  Npolyterms,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "XPIX_SYS_ERR",dXpixSys  ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "YPIX_SYS_ERR",dYpixSys  ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "ID",          imageID   ,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "NFIT",        nFitAstrom,         Nimage_pos);
-  gfits_set_bintable_column (&theader, &ftable, "FLAGS",       flags     ,         Nimage_pos);
-
-  free (crval1    );
-  free (crval2    );
-  free (crpix1    );
-  free (crpix2    );
-  free (cdelt1    );
-  free (cdelt2    );
-  free (pc1_1     );
-  free (pc1_2     );
-  free (pc2_1     );
-  free (pc2_2     );
-  free (polyterms );
-  free (ctype     );
-  free (Npolyterms);
-  free (dXpixSys  );
-  free (dYpixSys  );
-  free (imageID   );
-  free (nFitAstrom);
-  free (flags     );
+  gfits_set_bintable_column (&theader, &ftable, "CRVAL1",         crval1    ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "CRVAL2",         crval2    ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "CRPIX1",         crpix1    ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "CRPIX2",         crpix2    ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "CDELT1",         cdelt1    ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "CDELT2",         cdelt2    ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "PC1_1",          pc1_1     ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "PC1_2",          pc1_2     ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "PC2_1",          pc2_1     ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "PC2_2",          pc2_2     ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "POLYTERMS",      polyterms ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "CTYPE",          ctype     ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "NPOLYTERMS",     Npolyterms,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "XPIX_SYS_ERR",   dXpixSys  ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "YPIX_SYS_ERR",   dYpixSys  ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "REF_COLOR_BLUE", refColorBlue,       Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "REF_COLOR_RED",  refColorRed ,       Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "ID",             imageID   ,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "NFIT",           nFitAstrom,         Nimage_pos);
+  gfits_set_bintable_column (&theader, &ftable, "FLAGS",          flags     ,         Nimage_pos);
+
+  free (crval1      );
+  free (crval2      );
+  free (crpix1      );
+  free (crpix2      );
+  free (cdelt1      );
+  free (cdelt2      );
+  free (pc1_1       );
+  free (pc1_2       );
+  free (pc2_1       );
+  free (pc2_2       );
+  free (polyterms   );
+  free (ctype       );
+  free (Npolyterms  );
+  free (dXpixSys    );
+  free (dYpixSys    );
+  free (refColorBlue);
+  free (refColorRed );
+  free (imageID     );
+  free (nFitAstrom  );
+  free (flags       );
   
   FILE *f = fopen (filename, "w");
Index: /trunk/Ohana/src/relastro/src/MeasPosIO.c
===================================================================
--- /trunk/Ohana/src/relastro/src/MeasPosIO.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/MeasPosIO.c	(revision 37807)
@@ -0,0 +1,191 @@
+# include "relastro.h"
+
+# define GET_COLUMN(OUT,NAME,TYPE) \
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+// this is nearly identical to the one in 'uniphot/src' used by setphot / setphot_client.
+// Here, we use a handful of different columns (if not, we could move to libdvo)
+MeasPos *MeasPosLoad(char *filename, off_t *nmeaspos) {
+
+  int i, Ncol;
+  off_t Nrow;
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  *nmeaspos = 0;
+  MeasPos *measpos = NULL;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset header\n");
+    fclose (f);
+    return NULL;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (VERBOSE) fprintf (stderr, "can't read image subset matrix\n");
+    gfits_free_header (&header);
+    fclose (f);
+    return NULL;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) {
+    fclose (f);
+    return NULL;
+  }
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
+    fclose (f);
+    return (NULL);
+  }
+  fclose (f);
+
+  // a bit annoying : we read the entire block of data, then extract the columns, then set the image structure values.
+  // this means I need 3 copies in memory at some point.  ugh.
+
+  char type[16];
+
+  GET_COLUMN (R,    	 "RA",           double);
+  GET_COLUMN (D,   	 "DEC",          double);
+  GET_COLUMN (objID, 	 "OBJ_ID",       int);
+  GET_COLUMN (catID, 	 "CAT_ID",       int);
+  GET_COLUMN (imageID, 	 "IMAGE_ID",     int);
+
+  ALLOCATE (measpos, MeasPos, Nrow);
+  for (i = 0; i < Nrow; i++) {
+    measpos[i].R              = R    [i];
+    measpos[i].D              = D    [i];
+    measpos[i].objID          = objID[i];
+    measpos[i].catID          = catID[i];
+    measpos[i].imageID        = imageID[i];
+  }
+  fprintf (stderr, "loaded data for %lld objects (* filters)\n", (long long) Nrow);
+
+  free (R    );
+  free (D    );
+  free (objID);
+  free (catID);
+  free (imageID);
+
+  // free FITS table pieces...
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  *nmeaspos = Nrow;
+  return measpos;
+}
+
+// STATUS is value expected for success
+# define CHECK_STATUS(STATUS,MSG,...)					\
+  if (!(STATUS)) {							\
+    fprintf (stderr, MSG, __VA_ARGS__);					\
+    return FALSE;							\
+  }
+
+int MeasPosSave(char *filename, MeasPos *measpos, off_t Nmeaspos) {
+
+  int i;
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  gfits_create_table_header (&theader, "BINTABLE", "MEASPOS");
+
+  gfits_define_bintable_column (&theader, "D", "RA",        "meas position, ra",  "degrees", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "DEC",       "meas position, dec", "degrees", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "OBJ_ID",    "object ID",          NULL,      1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "CAT_ID",    "catalog ID",         NULL,      1.0, FT_BZERO_INT32);
+  gfits_define_bintable_column (&theader, "J", "IMAGE_ID",  "image ID",           NULL,      1.0, FT_BZERO_INT32);
+
+  // generate the output array that carries the data
+  gfits_create_table (&theader, &ftable);
+
+  double *R, *D;
+  unsigned int *objID, *catID, *imageID;
+
+  // create intermediate storage arrays
+  ALLOCATE (R,         double, 	       Nmeaspos);
+  ALLOCATE (D,         double, 	       Nmeaspos);
+  ALLOCATE (objID,     unsigned int,   Nmeaspos);
+  ALLOCATE (catID,     unsigned int,   Nmeaspos);
+  ALLOCATE (imageID,   unsigned int,   Nmeaspos);
+
+  // assign the storage arrays
+  for (i = 0; i < Nmeaspos; i++) {
+    R    [i]   = measpos[i].R    ;
+    D    [i]   = measpos[i].D    ;
+    objID[i]   = measpos[i].objID;
+    catID[i]   = measpos[i].catID;
+    imageID[i] = measpos[i].imageID;
+  }
+
+  // add the columns to the output array
+  gfits_set_bintable_column (&theader, &ftable, "RA",        R,         Nmeaspos);
+  gfits_set_bintable_column (&theader, &ftable, "DEC",       D,         Nmeaspos);
+  gfits_set_bintable_column (&theader, &ftable, "OBJ_ID",    objID,     Nmeaspos);
+  gfits_set_bintable_column (&theader, &ftable, "CAT_ID",    catID,     Nmeaspos);
+  gfits_set_bintable_column (&theader, &ftable, "IMAGE_ID",  imageID,   Nmeaspos);
+
+  free (R    );
+  free (D    );
+  free (objID);
+  free (catID);
+  free (imageID);
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open measmag file for output %s\n", filename);
+    return FALSE;
+  }
+
+  int status;
+  status = gfits_fwrite_header  (f, &header);
+  CHECK_STATUS (status, "ERROR: cannot write header for measpos %s\n", filename);
+
+  status = gfits_fwrite_matrix  (f, &matrix);
+  CHECK_STATUS (status, "ERROR: cannot write matrix for measpos %s\n", filename);
+
+  status = gfits_fwrite_Theader (f, &theader);
+  CHECK_STATUS (status, "ERROR: cannot write table header for measpos %s\n", filename);
+
+  status = gfits_fwrite_table  (f, &ftable);
+  CHECK_STATUS (status, "ERROR: cannot write table data for measpos %s\n", filename);
+
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  int fd = fileno (f);
+
+  status = fflush (f);
+  CHECK_STATUS (!status, "ERROR: cannot flush file measpos %s\n", filename);
+
+  status = fsync (fd);
+  CHECK_STATUS (!status, "ERROR: cannot flush file measpos %s\n", filename);
+
+  status = fclose (f);
+  CHECK_STATUS (!status, "ERROR: problem closing measpos file %s\n", filename);
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/relastro/src/StarMaps.c
===================================================================
--- /trunk/Ohana/src/relastro/src/StarMaps.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/StarMaps.c	(revision 37807)
@@ -176,2 +176,20 @@
 }
 
+int printStarMap(int N, char *filename) {
+
+  off_t i, Nimages;
+  Image *images = getimages(&Nimages, NULL);
+  double L, M;
+
+  FILE *f = fopen (filename, "w");
+
+  for (i = 0; i < starmap[N].Npoints; i++) {
+    // set the transformed coordinates
+    XY_to_LM (&L, &M, starmap[N].points[i].X, starmap[N].points[i].Y, &images[N].coords);
+
+    fprintf (f, "%d %7.2f %f : %7.2f %7.2f : %7.2f %7.2f : %7.2f %7.2f\n", (int) i, starmap[N].points[i].X, starmap[N].points[i].Y, starmap[N].points[i].L, starmap[N].points[i].M, L, M, starmap[N].points[i].L - L, starmap[N].points[i].M - M);
+  }
+  fclose (f);
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/relastro/src/UpdateChips.c
===================================================================
--- /trunk/Ohana/src/relastro/src/UpdateChips.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/UpdateChips.c	(revision 37807)
@@ -36,5 +36,4 @@
   off_t i, Nraw, Nref, nFitAstr;
   StarData *raw, *ref;
-  Coords *oldCoords;
   float dXpixSys, dYpixSys;
   double *Ro, *Do;
@@ -74,4 +73,10 @@
       Nskip ++;
       mode[i] = 0;
+      continue;
+    }
+    if (Nraw <= IMFIT_TOO_FEW) {
+      Nskip ++;
+      mode[i] = 0;
+      free (raw);
       continue;
     }
@@ -90,5 +95,7 @@
 
     // save these in case of failure
-    saveCoords (&image[i].coords, i);
+    Coords oldCoords;
+    SaveCoords (&oldCoords, &image[i].coords);
+
     dXpixSys = image[i].dXpixSys;
     dYpixSys = image[i].dYpixSys;
@@ -100,36 +107,47 @@
       if (VERBOSE) fprintf (stderr, "reject fit for image %s ("OFF_T_FMT") : Nstars: "OFF_T_FMT" of %d\n", image[i].name,  i,  Nraw, image[i].nstar);
 
-      // restore status quo ante
-      oldCoords = getCoords (i);
-      memcpy (&image[i].coords, oldCoords, sizeof(Coords));
-      image[i].dXpixSys = dXpixSys;
-      image[i].dYpixSys = dYpixSys; 
-      image[i].nFitAstrom = nFitAstr;
-
-      saveCenter (image, &Ro[i], &Do[i], i);
-      mode[i] = 1;
-      NoldFit ++;
-      free (raw);
-      free (ref);
-      continue;
+      if (1) {
+	// restore status quo ante (replace truMap with tmpMap)
+	RestoreCoords (&image[i].coords, &oldCoords, &image[i]);
+	image[i].dXpixSys = dXpixSys;
+	image[i].dYpixSys = dYpixSys; 
+	image[i].nFitAstrom = nFitAstr;
+
+	saveCenter (image, &Ro[i], &Do[i], i);
+	mode[i] = 1;
+	NoldFit ++;
+	free (raw);
+	free (ref);
+	continue;
+      }
     }
 
     if (!checkStarMap (i)) {
       if (VERBOSE) fprintf (stderr, "fit diverges too much for image %s ("OFF_T_FMT") : Nstars: "OFF_T_FMT"\n", image[i].name,  i,  Nraw);
-      // restore status quo ante
-      oldCoords = getCoords (i);
-      memcpy (&image[i].coords, oldCoords, sizeof(Coords));
-      image[i].dXpixSys = dXpixSys;
-      image[i].dYpixSys = dYpixSys; 
-      image[i].nFitAstrom = nFitAstr;
-
-      saveCenter (image, &Ro[i], &Do[i], i);
-      mode[i] = 2;
-      image[i].flags |= ID_IMAGE_ASTROM_POOR;
-      NoldFit ++;
-      free (raw);
-      free (ref);
-      continue;
+
+      if (1) {
+	// restore status quo ante (replace truMap with tmpMap)
+	RestoreCoords (&image[i].coords, &oldCoords, &image[i]);
+	image[i].dXpixSys = dXpixSys;
+	image[i].dYpixSys = dYpixSys; 
+	image[i].nFitAstrom = nFitAstr;
+
+	saveCenter (image, &Ro[i], &Do[i], i);
+	mode[i] = 2;
+	image[i].flags |= ID_IMAGE_ASTROM_POOR;
+	NoldFit ++;
+	free (raw);
+	free (ref);
+	continue;
+      }
     } 
+
+    // Apply the modified coords back to the measure.R,D.  Note that raw.R,D, ref.L,M, etc
+    // are all automatically updated in this block because they are re-generated from
+    // image.coords on each pass.
+    setImageRaw (catalog, Ncatalog, i, raw, Nraw, MODE_MOSAIC);
+    if (USE_GALAXY_MODEL) {
+      image[i].flags |= ID_IMAGE_ASTROM_GMM;
+    }
 
     saveCenter (image, &Ro[i], &Do[i], i);
@@ -235,5 +253,4 @@
   off_t Nraw, Nref, nFitAstr;
   StarData *raw, *ref;
-  Coords *oldCoords;
   float dXpixSys, dYpixSys;
 
@@ -260,4 +277,10 @@
       threadinfo->Nskip ++;
       threadinfo->mode[i] = 0;
+      continue;
+    }
+    if (Nraw <= IMFIT_TOO_FEW) {
+      threadinfo->Nskip ++;
+      threadinfo->mode[i] = 0;
+      free (raw);
       continue;
     }
@@ -276,5 +299,6 @@
 
     // save these in case of failure
-    saveCoords (&image[i].coords, i);
+    Coords oldCoords;
+    SaveCoords (&oldCoords, &image[i].coords);
     dXpixSys = image[i].dXpixSys;
     dYpixSys = image[i].dYpixSys;
@@ -286,7 +310,6 @@
       if (VERBOSE) fprintf (stderr, "reject fit for image %s ("OFF_T_FMT") : Nstars: "OFF_T_FMT" of %d\n", image[i].name,  i,  Nraw, image[i].nstar);
 
-      // restore status quo ante
-      oldCoords = getCoords (i);
-      memcpy (&image[i].coords, oldCoords, sizeof(Coords));
+      // restore status quo ante (replace truMap with tmpMap)
+      RestoreCoords (&image[i].coords, &oldCoords, &image[i]);
       image[i].dXpixSys = dXpixSys;
       image[i].dYpixSys = dYpixSys; 
@@ -303,7 +326,7 @@
     if (!checkStarMap (i)) {
       if (VERBOSE) fprintf (stderr, "fit diverges too much for image %s ("OFF_T_FMT") : Nstars: "OFF_T_FMT"\n", image[i].name,  i,  Nraw);
-      // restore status quo ante
-      oldCoords = getCoords (i);
-      memcpy (&image[i].coords, oldCoords, sizeof(Coords));
+
+      // restore status quo ante (replace truMap with tmpMap)
+      RestoreCoords (&image[i].coords, &oldCoords, &image[i]);
       image[i].dXpixSys = dXpixSys;
       image[i].dYpixSys = dYpixSys; 
@@ -319,4 +342,7 @@
     } 
 
+    // apply the modified R,D back to the measures
+    setImageRaw (threadinfo->catalog, threadinfo->Ncatalog, i, raw, Nraw, MODE_MOSAIC);
+
     saveCenter (image, &threadinfo->Ro[i], &threadinfo->Do[i], i);
     threadinfo->mode[i] = 3;
@@ -333,11 +359,18 @@
 static pthread_mutex_t UpdateChips_mutex = PTHREAD_MUTEX_INITIALIZER;
 
+void lockUpdateChips () {
+  pthread_mutex_lock (&UpdateChips_mutex);
+}
+void unlockUpdateChips () {
+  pthread_mutex_unlock (&UpdateChips_mutex);
+}
+
 // we have an array of chips (image, Nimage).  we need to hand out images one at a time to
 // the worker threads as they need
 off_t getNextImageForThread () {
 
-  pthread_mutex_lock (&UpdateChips_mutex);
+  lockUpdateChips ();
   if (nextImage >= Nimage) {
-    pthread_mutex_unlock (&UpdateChips_mutex);
+    unlockUpdateChips ();
     return (-1);
   }
@@ -345,5 +378,5 @@
   nextImage ++;
 
-  pthread_mutex_unlock (&UpdateChips_mutex);
+  unlockUpdateChips ();
   return (thisImage);
 }
@@ -494,3 +527,48 @@
 }
 
-// XXX if (!FindMosaicForImage (image, Nimage, i)) { }
+// save the coords and offset map in src at tgt
+void SaveCoords (Coords *tgt, Coords *src) {
+  myAssert (tgt, "oops");
+  myAssert (src, "oops");
+  CopyCoords (tgt, src); // src retains a pointer to the AstromOffsetMap table, to be saved
+  tgt->offsetMap = AstromOffsetMapCopy(src->offsetMap); // oldCoords now saves the old values in a new structure
+}
+
+// restore the coords and offset map at tgt from src
+void RestoreCoords (Coords *tgt, Coords *src, Image *image) {
+
+  AstromOffsetMap *truMap = tgt->offsetMap;
+  AstromOffsetMap *tmpMap = src->offsetMap;
+
+  // copy the coords structure data first, then fix the offsetMap pointers
+  // this modifies tgt.coords, but we need to keep the original pointer
+  CopyCoords (tgt, src);
+  tgt->offsetMap = truMap;
+
+  // we want to keep the old solution, which is (maybe) a pointer to a stand-alone
+  // map.  the image has a bad solution, but it is a pointer to the map in the I/O
+  // table.  those values will be saved on exit.
+
+  if (!tmpMap && !truMap) return;
+
+  if (tmpMap && truMap) {
+    AstromOffsetMapSetOrder (truMap, tmpMap->Nx, tmpMap->Ny, image);
+    AstromOffsetMapCopyData (truMap, tmpMap);
+    return;
+  }
+
+  if (tmpMap && !truMap) {
+    // is this even possible?
+    lockUpdateChips ();
+    AstromOffsetTable *table = get_astrom_table ();
+    AstromOffsetTableNewMap(table, tmpMap->Nx, tmpMap->Ny, image);  // registers the map with the image
+    unlockUpdateChips ();
+    AstromOffsetMapCopyData (image->coords.offsetMap, tmpMap);
+    return;
+  }
+  if (!tmpMap && truMap) {
+    truMap->keep = FALSE;
+    return;
+  }
+  myAbort ("oops");
+}
Index: /trunk/Ohana/src/relastro/src/UpdateMeasures.c
===================================================================
--- /trunk/Ohana/src/relastro/src/UpdateMeasures.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/UpdateMeasures.c	(revision 37807)
@@ -1,8 +1,142 @@
 # include "relastro.h"
+void dump_measures (Average *average, Measure *measure); // in ImageOps.c
 
-void printNcatTotal ();
+// this function is called by UpdateObjectOffsets or relastro_images.c for reset
+int UpdateMeasures (Catalog *catalog, int Ncatalog) {
+
+  off_t i, j, Nimage;
+  Image *image;
+  double DPOS_MAX_ASEC;
+
+  int badImage = 
+    ID_IMAGE_ASTROM_NOCAL | 
+    ID_IMAGE_ASTROM_POOR | 
+    ID_IMAGE_ASTROM_FAIL | 
+    ID_IMAGE_ASTROM_SKIP | 
+    ID_IMAGE_ASTROM_FEW;
+
+  if (RESET_BAD_IMAGES) badImage = 0;
+
+  image = getimages (&Nimage, NULL);
+
+  // track measurements which are far from their original position (poor at pole)
+  int NoffRAave = 0;  int NoffRAori = 0; 
+  int NoffDECave = 0; int NoffDECori = 0;
+
+  for (i = 0; i < Ncatalog; i++) {
+    myAssert (!catalog[i].Nmeasure || catalog[i].measureT, "programming error");
+    for (j = 0; j < catalog[i].Nmeasure; j++) {
+      MeasureTiny *measureT = &catalog[i].measureT[j];
+      Measure     *measureB = catalog[i].measure ? &catalog[i].measure[j] : NULL;
+
+      off_t im = getImageByID (measureT->imageID);
+      if (im < 0) continue; // detections without imageIDs are not associated with a known image (eg, refs)
+
+      // check that we have the right image:
+      myAssert (measureT->photcode == image[im].photcode, "image photcode mismatch");
+      myAssert (measureT->t >= image[im].tzero, "image time mismatch (1)");
+      myAssert (measureT->t <= image[im].tzero + image[im].NX*image[im].trate/5000.0, "image time mismatch (2)");
+      // I'm allowing the trate*NX to be a factor of 2x too small
+
+      // skip measurements on images that have failed solutions (divergent or otherwise)
+      // XXX apply this or not??
+      if (FALSE && (image[im].flags & badImage)) continue;
+
+      Coords *moscoords = image[im].coords.mosaic;
+      Coords *imcoords = &image[im].coords;
+
+      if (moscoords) {
+	DPOS_MAX_ASEC = 3600.0*DPOS_MAX*hypot(moscoords->cdelt1, moscoords->cdelt2);
+      } else {
+	DPOS_MAX_ASEC = 3600.0*DPOS_MAX*hypot(imcoords->cdelt1, imcoords->cdelt2);
+      }
+
+      double X = measureT->Xccd;
+      double Y = measureT->Yccd;
+      if (USE_FIXED_PIXCOORDS) {
+	if (isfinite(measureT->Xfix) && isfinite(measureT->Yfix)) {
+	  float dX = measureT->Xfix - measureT->Xccd;
+	  float dY = measureT->Yfix - measureT->Yccd;
+	  if (hypot(dX,dY) < 2.0) {
+	    X = measureT->Xfix;
+	    Y = measureT->Yfix;
+	  } 
+	}
+      }
+
+      double R, D;
+      XY_to_RD (&R, &D, X, Y, imcoords);
+
+      int n = measureT[0].averef;
+      Average *average = &catalog[i].average[n];
+
+      double dR = 3600.0*(average[0].R - R);
+      double dD = 3600.0*(average[0].D - D);
+
+      // make sure detection is on the same side of the 0,360 boundary as the average
+      // this will give some funny results withing ~1 arcsec of the pol
+      if (dR > +180.0*3600.0) {
+	// average on high end of boundary, move star up
+	R += 360.0;
+	dR = 3600.0*(average[0].R - R);
+      }
+      if (dR < -180.0*3600.0) {
+	// average on low end of boundary, move star down
+	R -= 360.0;
+	dR = 3600.0*(average[0].R - R);
+      }
+
+      float csdec = cos(average[0].D * RAD_DEG);
+
+      // complain if the new location is far from the average location
+      // NOTE: This should never happen, or our StarMap tests are not working
+      if (fabs(dR*csdec) > 3.0*ADDSTAR_RADIUS) {
+	NoffRAave ++;
+	if (VERBOSE2 && catalog[i].measure) {
+	  fprintf (stderr, "measurement is far from average location (R): %f %f (%f %f %f)\n", average[0].R, average[0].D, dR, dR*csdec, dD);
+	  dump_measures (&average[0], catalog[i].measure);
+	}
+      }
+      if (fabs(dD) > 3.0*ADDSTAR_RADIUS) {
+	NoffDECave ++;
+	if (VERBOSE2 && catalog[i].measure) {
+	  fprintf (stderr, "measurement is far from average location (D): %f %f (%f %f)\n", average[0].R, average[0].D, dR, dD);
+	  dump_measures (&average[0], catalog[i].measure);
+	}
+      }
+
+      // complain if the new location is far from the old location
+      if (fabs(csdec*(measureT[0].R - R)) > DPOS_MAX_ASEC) {
+	NoffRAori ++;
+	if (VERBOSE2 && catalog[i].measure) {
+	  fprintf (stderr, "measurement moves far from original location (R): %f %f (%f %f %f %f)\n", average[0].R, average[0].D, measureT[0].R, dR, csdec*(measureT[0].R - R), dD);
+	  dump_measures (&average[0], catalog[i].measure);
+	}
+      }
+      if (fabs(measureT[0].D - D) > DPOS_MAX_ASEC) {
+	NoffDECori ++;
+	if (VERBOSE2 && catalog[i].measure) {
+	  fprintf (stderr, "measurement moves far from original location (D): %f %f (%f %f %f)\n", average[0].R, average[0].D, dR, measureT[0].D, dD);
+	  dump_measures (&average[0], catalog[i].measure);
+	}
+      }
+      measureT->R = R;
+      measureT->D = D;
+      if (measureB) {
+	measureB->R = R;
+	measureB->D = D;
+      }
+    }
+  }
+  // printNcatTotal ();
+
+  int Noff = NoffRAave + NoffDECave + NoffRAori + NoffDECori;
+  if (VERBOSE && (Noff > 0)) fprintf (stderr, "Noff ave RA %d, Noff ave DEC %d, Noff ori RA %d, Noff ori DEC %d\n", NoffRAave, NoffDECave, NoffRAori, NoffDECori);
+
+  return (TRUE);
+}
 
 // this function operates on Measure, not MeasureTiny
-int UpdateMeasures (Catalog *catalog, int Ncatalog) {
+int UpdateMeasuresOld (Catalog *catalog, int Ncatalog) {
 
   off_t i, Nimage;
@@ -32,5 +166,5 @@
   }
 
-  printNcatTotal ();
+  // printNcatTotal ();
 
   return (TRUE);
Index: /trunk/Ohana/src/relastro/src/UpdateObjectOffsets.c
===================================================================
--- /trunk/Ohana/src/relastro/src/UpdateObjectOffsets.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/UpdateObjectOffsets.c	(revision 37807)
@@ -59,5 +59,6 @@
 
     // uses MeasureTiny values, but will also update Measure values when populated
-    UpdateObjects (&catalog, 1);
+    // if Galaxy Motion Model correction is active, do not apply at this last stage 
+    UpdateObjects (&catalog, 1, FALSE);
 
     free_tiny_values(&catalog);
@@ -74,7 +75,10 @@
     save_catalogs (&catalog, 1);
   }
-  
+  printNcatTotal();
   return (TRUE);
 }
+
+int UpdateObjectOffsets_parallel_group (HostTableGroup *group, SkyList *sky);
+int UpdateObjectOffsets_parallel_table (HostTable *table, SkyList *sky);
 
 // CATDIR is supplied globally
@@ -90,4 +94,148 @@
     exit (1);
   }    
+
+# if (0)
+  
+  UpdateObjectOffsets_parallel_table (table, sky);
+
+# else
+
+  int Ngroups;
+  HostTableGroup *groups = HostTableGroups (table, &Ngroups);
+  // split the table into Ngroups, each with a unique set of machines (this avoids overloading the remote host machines)
+
+  int i;
+  for (i = 0; i < Ngroups; i++) {
+    // update only a group of unique machines at a time
+    UpdateObjectOffsets_parallel_group (&groups[i], sky);
+  }
+# endif
+
+  return TRUE;
+}      
+
+// CATDIR is supplied globally
+# define DEBUG 1
+int UpdateObjectOffsets_parallel_group (HostTableGroup *group, SkyList *sky) {
+
+  // launch the relastro_client jobs to the parallel hosts
+
+  int i, j;
+  for (i = 0; i < group->Nhosts; i++) {
+
+    if (sky->Nregions < group->Nhosts) {
+      // do any of the regions want this host?
+      int wantThisHost = FALSE;
+      for (j = 0; j < sky->Nregions; j++) {
+	if (HostTableTestHost (sky->regions[j], group->hosts[i][0].hostID)) {
+	  wantThisHost = TRUE;
+	  break;
+	}
+      }
+      if (!wantThisHost) {
+	// fprintf (stderr, "skip host %s\n", group->hosts[i][0].hostname);
+	continue;
+      }
+    }
+
+    // ensure that the paths are absolute path names
+    char *tmppath = abspath (group->hosts[i][0].pathname, DVO_MAX_PATH);
+    free (group->hosts[i][0].pathname);
+    group->hosts[i][0].pathname = tmppath;
+
+    // options / arguments that can affect relastro_client -load:
+    // VERBOSE, VERBOSE2
+    // RESET (-reset)
+    // TimeSelect -time
+    // (note that psfQF is applied rigidly at 0.85, as is the galaxy test)
+    // ImagSelect, ImagMin, ImagMax
+    // MaxDensityUse, MaxDensityValue
+
+    char command[1024];
+    snprintf (command, 1024, "relastro_client -update-offsets -hostID %d -D CATDIR %s -hostdir %s -region %f %f %f %f -statmode %s -minerror %f", 
+	      group->hosts[i][0].hostID, CATDIR, group->hosts[i][0].pathname, UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax, STATMODE, MIN_ERROR);
+
+    if (FIT_MODE == FIT_PM_ONLY)  	 strextend (command, "-pm");
+    if (FIT_MODE == FIT_PAR_ONLY) 	 strextend (command, "-par");
+    if (FIT_MODE == FIT_PM_AND_PAR)      strextend (command, "-pmpar");
+
+    if (VERBOSE)       strextend (command, "-v");
+    if (VERBOSE2)      strextend (command, "-vv");
+    if (RESET)         strextend (command, "-reset");
+    if (UPDATE)        strextend (command, "-update");
+
+    if (RESET_BAD_IMAGES) strextend (command, "-reset-bad-images");
+
+    if (ImagSelect)    strextend (command, "-instmag %f %f", ImagMin, ImagMax);
+    if (MaxDensityUse) strextend (command, "-max-density %f", MaxDensityValue);
+    
+    if (USE_BASIC_CHECK) strextend (command, "-basic-image-search");
+    if (FlagOutlier)     strextend (command, "-clip %d", CLIP_THRESH);
+    if (ExcludeBogus)    strextend (command, "-exclude-bogus %f", ExcludeBogusRadius);
+    
+    if (USE_FIXED_PIXCOORDS) strextend (command, "-D USE_FIXED_PIXCOORDS 1"); 
+
+    if (PHOTCODE_KEEP_LIST) strextend (command, "+photcode %s", PHOTCODE_KEEP_LIST);
+    if (PHOTCODE_SKIP_LIST) strextend (command, "-photcode %s", PHOTCODE_SKIP_LIST);
+    if (PhotFlagSelect)     strextend (command, "+photflags"); 
+    if (PhotFlagBad)        strextend (command, "+photflagbad %d", PhotFlagBad);
+    if (PhotFlagPoor)       strextend (command, "+photflagpoor %d", PhotFlagPoor);
+    // XXX note that the above pass in the flag as decimal -- also note that args.c cannot handle 0xHEX values
+
+    if (DCR_BLUE_COLOR_POS && DCR_BLUE_COLOR_NEG) {
+      strextend (command, "-dcr-blue-color %s %s", DCR_BLUE_COLOR_POS, DCR_BLUE_COLOR_NEG); 
+    }
+    if (DCR_RED_COLOR_POS && DCR_RED_COLOR_NEG) {
+      strextend (command, "-dcr-red-color %s %s", DCR_RED_COLOR_POS, DCR_RED_COLOR_NEG); 
+    }
+
+    if (MinBadQF > 0.0)        strextend (command, "-min-bad-psfqf %f", MinBadQF); 
+    if (MaxMeanOffset != 10.0) strextend (command, "-max-mean-offset  %f", MaxMeanOffset);
+
+    if (TimeSelect) { 
+      char *tstart = ohana_sec_to_date (TSTART);
+      char *tstop  = ohana_sec_to_date (TSTOP);
+      strextend (command, "-time %s %s", tstart, tstop); 
+      free (tstart);
+      free (tstop);
+    }
+    fprintf (stderr, "command: %s\n", command);
+
+    if (PARALLEL_MANUAL) continue;
+
+    if (PARALLEL_SERIAL) {
+      int status = system (command);
+      if (status) {
+	fprintf (stderr, "ERROR running relastro_client\n");
+	exit (2);
+      }
+    } else {
+      // launch the job on the remote machine (no handshake)
+      int errorInfo = 0;
+      int pid = rconnect ("ssh", group->hosts[i][0].hostname, command, group->hosts[i][0].stdio, &errorInfo, FALSE);
+      if (!pid) {
+	if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", group->hosts[i][0].hostname, errorInfo);
+	exit (1);
+      }
+      group->hosts[i][0].pid = pid; // save for future reference
+    }
+  }
+
+  if (PARALLEL_MANUAL) {
+    fprintf (stderr, "run the relastro_client commands above.  when these are done, hit return\n");
+    getchar();
+  }
+  if (!PARALLEL_MANUAL && !PARALLEL_SERIAL) {
+    HostTableGroupWaitJobsGetIO (group, __FILE__, __LINE__, VERBOSE);
+  }
+
+  return TRUE;
+}      
+
+// CATDIR is supplied globally
+# define DEBUG 1
+int UpdateObjectOffsets_parallel_table (HostTable *table, SkyList *sky) {
+
+  // launch the relastro_client jobs to the parallel hosts
 
   int i, j;
Index: /trunk/Ohana/src/relastro/src/UpdateObjects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/UpdateObjects.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/UpdateObjects.c	(revision 37807)
@@ -2,5 +2,5 @@
 # define PAR_TOOFEW 5
 
-int UpdateObjects_Chips (Average *average, SecFilt *secfilt, MeasureTiny *measure, Measure *measureBig, int Nsecfilt, FitStats *fitStats, int i, off_t m);
+int UpdateObjects_Chips (Average *average, SecFilt *secfilt, MeasureTiny *measure, Measure *measureBig, int Nsecfilt, FitStats *fitStats, int i, off_t m, int applyGalaxyOffset);
 int UpdateObjects_Stack (Average *average, SecFilt *secfilt, MeasureTiny *measure, Measure *measureBig, int Nsecfilt, FitStats *fitStats);
 
@@ -69,13 +69,6 @@
 
   /* project coordinates to a plane centered on the object with units of arcsec */
-  coords.crval1 = 0;
-  coords.crval2 = 0;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
+  InitCoords (&coords, "DEC--SIN");
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   // use J2000 as a reference time
@@ -106,5 +99,5 @@
 // This function operates on both Measure and MeasureTiny.  In the big stages, this should
 // be called with just MeasureTiny set and Measure == NULL
-int UpdateObjects (Catalog *catalog, int Ncatalog) {
+int UpdateObjects (Catalog *catalog, int Ncatalog, int Nloop) {
 
   initObjectData (catalog, Ncatalog);
@@ -137,8 +130,8 @@
 
       UpdateObjects_Stack(average, secfilt, measure, measureBig, Nsecfilt, &fitStatsStack);
-      UpdateObjects_Chips(average, secfilt, measure, measureBig, Nsecfilt, &fitStatsChips, i, m);
-    }
-    if (VERBOSE) fprintf (stderr, "catalog %d : chips "OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par : Nskip "OFF_T_FMT", Noffset "OFF_T_FMT"\n",  i,  fitStatsChips.Nave,  fitStatsChips.Npm,  fitStatsChips.Npar,  fitStatsChips.Nskip, fitStatsChips.Noffset);
-    if (VERBOSE) fprintf (stderr, "catalog %d : stack "OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par : Nskip "OFF_T_FMT", Noffset "OFF_T_FMT"\n",  i,  fitStatsStack.Nave,  fitStatsStack.Npm,  fitStatsStack.Npar,  fitStatsStack.Nskip, fitStatsStack.Noffset);
+      UpdateObjects_Chips(average, secfilt, measure, measureBig, Nsecfilt, &fitStatsChips, i, m, Nloop);
+    }
+    if (VERBOSE2) fprintf (stderr, "catalog %d : chips "OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par : Nskip "OFF_T_FMT", Noffset "OFF_T_FMT"\n",  i,  fitStatsChips.Nave,  fitStatsChips.Npm,  fitStatsChips.Npar,  fitStatsChips.Nskip, fitStatsChips.Noffset);
+    if (VERBOSE2) fprintf (stderr, "catalog %d : stack "OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par : Nskip "OFF_T_FMT", Noffset "OFF_T_FMT"\n",  i,  fitStatsStack.Nave,  fitStatsStack.Npm,  fitStatsStack.Npar,  fitStatsStack.Nskip, fitStatsStack.Noffset);
     sumFitStats (&fitStatsChips, &sumStatsChips);
     sumFitStats (&fitStatsStack, &sumStatsStack);
@@ -146,6 +139,6 @@
   freeObjectData ();
 
-  if (VERBOSE) fprintf (stderr, "fitted "OFF_T_FMT" objects ("OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par), skipped "OFF_T_FMT", "OFF_T_FMT" have too large an offset\n",  (sumStatsChips.Nave + sumStatsChips.Npm + sumStatsChips.Npar),  sumStatsChips.Nave,  sumStatsChips.Npm,  sumStatsChips.Npar,  sumStatsChips.Nskip, sumStatsChips.Noffset);
-  if (VERBOSE) fprintf (stderr, "fitted "OFF_T_FMT" objects ("OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par), skipped "OFF_T_FMT", "OFF_T_FMT" have too large an offset\n",  (sumStatsStack.Nave + sumStatsStack.Npm + sumStatsStack.Npar),  sumStatsStack.Nave,  sumStatsStack.Npm,  sumStatsStack.Npar,  sumStatsStack.Nskip, sumStatsStack.Noffset);
+  if (VERBOSE && (Ncatalog > 1)) fprintf (stderr, "fitted "OFF_T_FMT" objects ("OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par), skipped "OFF_T_FMT", "OFF_T_FMT" have too large an offset\n",  (sumStatsChips.Nave + sumStatsChips.Npm + sumStatsChips.Npar),  sumStatsChips.Nave,  sumStatsChips.Npm,  sumStatsChips.Npar,  sumStatsChips.Nskip, sumStatsChips.Noffset);
+  if (VERBOSE && (Ncatalog > 1)) fprintf (stderr, "fitted "OFF_T_FMT" objects ("OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par), skipped "OFF_T_FMT", "OFF_T_FMT" have too large an offset\n",  (sumStatsStack.Nave + sumStatsStack.Npm + sumStatsStack.Npar),  sumStatsStack.Nave,  sumStatsStack.Npm,  sumStatsStack.Npar,  sumStatsStack.Nskip, sumStatsStack.Noffset);
   return (TRUE);
 }
@@ -153,5 +146,5 @@
 // This function operates on both Measure and MeasureTiny.  In the big stages, this should
 // be called with just MeasureTiny set and Measure == NULL
-int UpdateObjects_Chips (Average *average, SecFilt *secfilt, MeasureTiny *measure, Measure *measureBig, int Nsecfilt, FitStats *fitStats, int i, off_t m) {
+int UpdateObjects_Chips (Average *average, SecFilt *secfilt, MeasureTiny *measure, Measure *measureBig, int Nsecfilt, FitStats *fitStats, int i, off_t m, int applyGalaxyOffset) {
 
   int setRefColor = areImagesMatched();
@@ -215,4 +208,14 @@
     double Ri = getMeanR (&measure[k], average, secfilt);
     double Di = getMeanD (&measure[k], average, secfilt);
+
+    // if we are correcting for the Galaxy Motion Model, only should apply it here 
+    // (a) when we are working to correct the images (mean R,D assumed to be at J2000) and
+    // (b) if we think the measure R,D is already at the image epoch position
+    if (USE_GALAXY_MODEL && applyGalaxyOffset) {
+      Ri -= measure[k].RoffGAL / 3600.0;
+      Di -= measure[k].DoffGAL / 3600.0;
+    }
+
+    // XXX add in dR,dD GAL here
 
     // mark (as POOR) any measurements which are deviant from the mean by > ExcludeBogusRadius
@@ -329,4 +332,14 @@
   // to judge the quality of the PM and PAR fits, we need to fit all three models and compare Chisq
 
+  // project all of the R,D coordinates to a plane centered on this coordinate. set
+  // the times to be relative to Tmean (this is required for parallax as well)
+  for (k = 0; k < N; k++) {
+    RD_to_XY (&X[k], &Y[k], R[k], D[k], &coords);
+    T[k] -= Tmean;
+    if (XVERB) {
+      fprintf (stderr, OFF_T_FMT" %f %f %f  %f %f +/- %f %f\n",  k, T[k], R[k], D[k], X[k], Y[k], dX[k], dY[k]);
+    }
+  }	  
+
   // *** first fit for the proper motion (skip fit if Trange or Npts is too small) ***
   if ((mode == FIT_PM_ONLY) || (mode == FIT_PM_AND_PAR)) {
@@ -340,14 +353,4 @@
     }
 
-    // project all of the R,D coordinates to a plane centered on this coordinate. set
-    // the times to be relative to Tmean (this is required for parallax as well)
-    for (k = 0; k < N; k++) {
-      RD_to_XY (&X[k], &Y[k], R[k], D[k], &coords);
-      T[k] -= Tmean;
-      if (XVERB) {
-	fprintf (stderr, OFF_T_FMT" %f %f %f  %f %f +/- %f %f\n",  k, T[k], R[k], D[k], X[k], Y[k], dX[k], dY[k]);
-      }
-    }	  
-
     FitPM (&fitPM, X, dX, Y, dY, T, N, XVERB);
 
@@ -357,4 +360,6 @@
     XY_to_RD (&fitPM.Ro, &fitPM.Do, fitPM.Ro, fitPM.Do, &coords);
     if (XVERB) fprintf (stderr, "project: %f %f : %f %f : %f\n", fitPM.Ro, fitPM.Do, fitPM.uR, fitPM.uD, fitPM.p);
+    if (fabs(fitPM.Ro) < 0.01) fprintf (stderr, "watch out for 0,360 boundary\n");
+    // XXX : does this make sense at 0,360 boundary?
 
     fitPM.p  = fitPM.dp  = 0.0;
@@ -409,4 +414,6 @@
     fitStats->Npar ++;
 
+    if (fabs(fitPM.Ro) < 0.01) fprintf (stderr, "watch out for 0,360 boundary\n");
+
     // XXX a hard-wired hack...
     if ((fabs(fitPAR.uR) > 2.0) || (fabs(fitPAR.uD) > 2.0)) {
@@ -419,12 +426,13 @@
     // ALWAYS fit the average model
     StatType statsR, statsD;
-    liststats_pos (R, dR, N, &statsR, XVERB); // WARNING: this function modifies R (do not use after here)
-    liststats_pos (D, dD, N, &statsD, XVERB); // WARNING: this function modifies D (do not use after here)
-
-    fitAve.Ro = statsR.mean;
-    fitAve.dRo = 3600.0*statsR.sigma;
-
-    fitAve.Do = statsD.mean;
-    fitAve.dDo = 3600.0*statsD.sigma;
+    liststats_pos (X, dX, N, &statsR, XVERB); // WARNING: this function modifies R (do not use after here)
+    liststats_pos (Y, dY, N, &statsD, XVERB); // WARNING: this function modifies D (do not use after here)
+
+    // project Ro, Do back to RA,DEC
+    XY_to_RD (&fitAve.Ro, &fitAve.Do, statsR.mean, statsD.mean, &coords);
+    if (XVERB) fprintf (stderr, "average: %f %f\n", fitAve.Ro, fitAve.Do);
+
+    fitAve.dRo = statsR.sigma;
+    fitAve.dDo = statsD.sigma;
 
     fitAve.chisq = (N > 1) ? 0.5 * (statsR.chisq + statsD.chisq) : NAN;
@@ -466,11 +474,14 @@
       break;
   }
-  if (XVERB) fprintf (stderr, "%f %f -> %f %f (%f,%f) pm=(%f %f) plx=(%f +/- %f)\n",
-		      average[0].R, 
-		      average[0].D, 
-		      fit.Ro, fit.Do, 
-		      3600*(average[0].R - fit.Ro), 
-		      3600*(average[0].D - fit.Do),
-		      fit.uR, fit.uD, fit.p, fit.dp);
+  if (XVERB) {
+    fprintf (stderr, "%f %f -> %f %f (%f,%f) pm=(%f %f) plx=(%f +/- %f)\n",
+	     average[0].R, 
+	     average[0].D, 
+	     fit.Ro, fit.Do, 
+	     3600*(average[0].R - fit.Ro), 
+	     3600*(average[0].D - fit.Do),
+	     fit.uR, fit.uD, fit.p, fit.dp);
+    fprintf (stderr, "-----\n");
+  }
 
   // make sure that the fit succeeded
@@ -494,4 +505,10 @@
   coords.crval1 = average[0].R;
   coords.crval2 = average[0].D;
+  if (isnan(coords.crval1)) {
+    return (FALSE);
+  }
+  if (isnan(coords.crval2)) {
+    return (FALSE);
+  }
 
   double dXoff, dYoff;
@@ -505,28 +522,4 @@
     return FALSE; // XXX ??
   }
-
-  average[0].R  	= fit.Ro; // RA in degrees
-  average[0].D  	= fit.Do; // DEC in degrees
-  average[0].dR 	= fit.dRo; // RA scatter in arcsec
-  average[0].dD 	= fit.dDo; // DEC scatter in arcsec
-
-  average[0].uR         = fit.uR; // RA proper motion in arcsec/year
-  average[0].uD         = fit.uD; // DEC proper motion in arcsec/year
-  average[0].duR        = fit.duR; // RA proper motion error in arcsec/year
-  average[0].duD        = fit.duD; // DEC proper motion error in arcsec/year
-
-  average[0].P          = fit.p; // parallax in arcsec
-  average[0].dP         = fit.dp; // parallax error in arcsec
-
-  average[0].ChiSqAve   = fitAve.chisq;
-  average[0].ChiSqPM    = fitPM.chisq;
-  average[0].ChiSqPar   = fitPAR.chisq;
-
-  average[0].Tmean      = (Tmean * 86400 * 365.25) + T2000;
-  average[0].Trange     = (Trange * 86400 * 365.25);
-  average[0].Npos       = fit.Nfit;
-
-  // unset the NO_ASTROM bit (not(NO_ASTROM) == HAVE_ASTROM)
-  average[0].flags &= ~ID_STAR_NO_ASTROM;
 
   if (XVERB) fprintf (stderr, "%f %f -> %f %f (%f,%f) pm=(%f %f) chisq=(%f, %f, %f)\n",
@@ -540,4 +533,28 @@
 		      fitAve.chisq, fitPM.chisq, fitPAR.chisq);
 
+  average[0].R  	= fit.Ro; // RA in degrees
+  average[0].D  	= fit.Do; // DEC in degrees
+  average[0].dR 	= fit.dRo; // RA scatter in arcsec
+  average[0].dD 	= fit.dDo; // DEC scatter in arcsec
+
+  average[0].uR         = fit.uR; // RA proper motion in arcsec/year
+  average[0].uD         = fit.uD; // DEC proper motion in arcsec/year
+  average[0].duR        = fit.duR; // RA proper motion error in arcsec/year
+  average[0].duD        = fit.duD; // DEC proper motion error in arcsec/year
+
+  average[0].P          = fit.p; // parallax in arcsec
+  average[0].dP         = fit.dp; // parallax error in arcsec
+
+  average[0].ChiSqAve   = fitAve.chisq;
+  average[0].ChiSqPM    = fitPM.chisq;
+  average[0].ChiSqPar   = fitPAR.chisq;
+
+  average[0].Tmean      = (Tmean * 86400 * 365.25) + T2000;
+  average[0].Trange     = (Trange * 86400 * 365.25);
+  average[0].Npos       = fit.Nfit;
+
+  // unset the NO_ASTROM bit (not(NO_ASTROM) == HAVE_ASTROM)
+  average[0].flags &= ~ID_STAR_NO_ASTROM;
+
   return (TRUE);
 }
Index: /trunk/Ohana/src/relastro/src/args.c
===================================================================
--- /trunk/Ohana/src/relastro/src/args.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/args.c	(revision 37807)
@@ -265,4 +265,10 @@
     remove_argument (N, &argc, argv);
     MaxDensityUse = TRUE;
+  }
+
+  APPLY_OFFSETS = FALSE;
+  if ((N = get_argument (argc, argv, "-apply-offsets"))) {
+    remove_argument (N, &argc, argv);
+    APPLY_OFFSETS = TRUE;
   }
 
@@ -372,4 +378,12 @@
   }
 
+  CHIPMAP = 0;
+  if ((N = get_argument (argc, argv, "-chipmap"))) {
+    remove_argument (N, &argc, argv);
+    CHIPMAP = atoi(argv[N]);
+    remove_argument (N, &argc, argv);
+
+  }
+
   SAVEPLOT = FALSE;
   PLOTSTUFF = FALSE;
@@ -399,9 +413,13 @@
     RESET = TRUE;
   }
-
   RESET_BAD_IMAGES = FALSE;
   if ((N = get_argument (argc, argv, "-reset-bad-images"))) {
     remove_argument (N, &argc, argv);
     RESET_BAD_IMAGES = TRUE;
+  }
+  RESET_IMAGES = FALSE;
+  if ((N = get_argument (argc, argv, "-reset-images"))) {
+    remove_argument (N, &argc, argv);
+    RESET_IMAGES = TRUE;
   }
 
@@ -868,4 +886,5 @@
   fprintf (stderr, "  -high-speed (code[,code,code]) (code[,code,code]) (radius) (output catdir)\n");
   fprintf (stderr, "  -hpm (radius) (output catdir)\n");
+  fprintf (stderr, "  -chipmap (MaxOrder)\n");
   fprintf (stderr, "  -merge-source (objID) (catID) into (objID) (catID)\n\n");
 
@@ -880,4 +899,5 @@
   fprintf (stderr, "  -statmode (mode)\n");
   fprintf (stderr, "  -reset\n");
+  fprintf (stderr, "  -reset-images\n");
   fprintf (stderr, "  -nloop (N) : number of image-fit iterations\n");
   fprintf (stderr, "  -update : apply new fit to database\n");
Index: /trunk/Ohana/src/relastro/src/assign_images.c
===================================================================
--- /trunk/Ohana/src/relastro/src/assign_images.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/assign_images.c	(revision 37807)
@@ -23,4 +23,16 @@
   BuildChipMatch (image, Nimage);
   MARKTIME("build chip match for %d images: %f sec\n", (int) Nimage, dtime);
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", CATDIR);
+  AstromOffsetTable *table = AstromOffsetMapLoad (mapfile, VERBOSE);
+
+  // assign images.coords.offsetMap -> table->map[i]
+  if (table) {
+    AstromOffsetTableMatchChips (image, Nimage, table);
+  } else {
+    table = AstromOffsetTableInit ();
+  }
+  put_astrom_table (table);
 
   initMosaics (image, Nimage);
@@ -89,10 +101,4 @@
     if (!strcmp(&image[j].coords.ctype[4], "-DIS")) continue;
 
-    // match the image to its corresponding ASTROMETRIC mosaic (not the same as the Mosaic above)
-    if (!FindMosaicForImage (image, Nimage, j)) {
-      if (VERBOSE2) fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
-    
     // Exclude images with crazy astrometry
     // XXX NOTE : this is gpc1-specific
@@ -150,13 +156,21 @@
 
     // this is a bit memory expensive : I am making a complete copy of the image table here
-    off_t Nsubset = regionHosts->hosts[i].Nimage;
-    regionHosts->hosts[i].image[Nsubset] = image[j];
-    regionHosts->hosts[i].imseq[Nsubset] = j;
-
-    regionHosts->hosts[i].Nimage ++;
-    if (regionHosts->hosts[i].Nimage == regionHosts->hosts[i].NIMAGE) {
-      regionHosts->hosts[i].NIMAGE += D_NIMAGE;
-      REALLOCATE (regionHosts->hosts[i].image, Image, regionHosts->hosts[i].NIMAGE);
-      REALLOCATE (regionHosts->hosts[i].imseq, off_t, regionHosts->hosts[i].NIMAGE);
+    off_t Nsubset = host->Nimage;
+    host->image[Nsubset] = image[j];
+    host->imseq[Nsubset] = j;
+
+    host->Nimage ++;
+    if (host->Nimage == host->NIMAGE) {
+      host->NIMAGE += D_NIMAGE;
+      REALLOCATE (host->image, Image, host->NIMAGE);
+      REALLOCATE (host->imseq, off_t, host->NIMAGE);
+    }
+
+    // save the astrometry maps, where they exist
+    if (image[j].coords.offsetMap) {
+      if (!host->astromTable) {
+	host->astromTable = AstromOffsetTableInit();
+      }
+      AstromOffsetTableAddMapFromImage(host->astromTable, &image[j]);
     }
   }
@@ -199,64 +213,4 @@
 }
 
-int calculate_host_image_bounds (RegionHostTable *regionHosts) {
-
-  int i, n;
-  off_t j;
-
-  for (i = 0; i < regionHosts->Nhosts; i++) {
-
-    RegionHostInfo *host = &regionHosts->hosts[i];
-
-    // XXX clear the chip match?
-
-    BuildChipMatch (host->image, host->Nimage);
-
-    double Rmin = 360.0;
-    double Rmax =   0.0;
-    double Dmin = +90.0;
-    double Dmax = -90.0;
-
-    for (j = 0; j < host->Nimage; j++) {
-
-      Image *image = &host->image[j];
-
-      if (!FindMosaicForImage (host->image, host->Nimage, j)) {
-	fprintf (stderr, "missing astrometry? programming error?\n");
-	abort ();
-      }
-      
-      // define image corners
-      for (n = 0; n < 4; n++) {
-	double Xc, Yc, Rc, Dc;
-	Xc = Xf[n]*image->NX; 
-	Yc = Yf[n]*image->NY;
-	XY_to_RD (&Rc, &Dc, Xc, Yc, &image->coords);
-	Rc = ohana_normalize_angle_to_midpoint (Rc, 180.0);
-      
-	Rmin = MIN (Rmin, Rc);
-	Dmin = MIN (Dmin, Rc);
-	
-	Rmax = MAX (Rmax, Rc);
-	Dmax = MAX (Dmax, Rc);
-      }
-
-    }
-
-    host->RminCat = Rmin;
-    host->DminCat = Dmin;
-    host->RmaxCat = Rmax;
-    host->DmaxCat = Dmax;
-
-    // regionHosts needs to have the full outer boundary
-    // (so reload_catalogs covers the correct region)
-    regionHosts->Rmin = MIN(Rmin, regionHosts->Rmin);
-    regionHosts->Rmax = MAX(Rmax, regionHosts->Rmax);
-    regionHosts->Dmin = MIN(Dmin, regionHosts->Dmin);
-    regionHosts->Dmax = MAX(Dmax, regionHosts->Dmax);
-  }
-  return TRUE;
-}
-
-
 // XXX add a search tree to speed this up?
 int find_host_for_coords (RegionHostTable *regionHosts, double Rc, double Dc) {
Index: /trunk/Ohana/src/relastro/src/bcatalog.c
===================================================================
--- /trunk/Ohana/src/relastro/src/bcatalog.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/bcatalog.c	(revision 37807)
@@ -27,13 +27,6 @@
 
   /* for outlier rejection, project coordinates to a plane centered on the object with units of arcsec */
-  coords.crval1 = 0;
-  coords.crval2 = 0;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
+  InitCoords (&coords, "DEC--SIN");
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   // XXX in the future, use catalog[0].Nsecfilt only?  allow catalogs to have variable Nsecfilt?
@@ -72,5 +65,4 @@
     if (catalog[0].average[i].Nmeasure <= SRC_MEAS_TOOFEW) {
       continue;
-      // XXX can't (easily) count this for a given chip
     }
     
@@ -84,4 +76,43 @@
     if (RESET) {
       subcatalog[0].average[Naverage].flags &= ~averageBits;
+    }
+
+    // if we want to do static correction to ICRF, keep the ICRF QSOs (regardless of other data quality)
+    if (USE_ICRF_CORRECT && (subcatalog[0].average[Naverage].flags & ID_ICRF_QSO)) {
+      Nm = 0;
+      for (j = 0; j < catalog[0].average[i].Nmeasure; j++) {
+	  
+	offset = catalog[0].average[i].measureOffset + j;
+	  
+	CopyMeasureToTiny (&subcatalog[0].measureT[Nmeasure], &catalog[0].measure[offset]);
+	subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_SKIP_ASTROM;
+	subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_NOCAL;
+	subcatalog[0].measureT[Nmeasure].averef   = Naverage;
+	if (RESET) {
+	  subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_POOR_ASTROM;
+	  subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_AREA;
+	}
+	  
+	Nmeasure ++;
+	Nm ++;
+	if (Nmeasure == NMEASURE) {
+	  NMEASURE += 1000;
+	  REALLOCATE (subcatalog[0].measureT, MeasureTiny, NMEASURE);
+	}
+      }
+      subcatalog[0].average[Naverage].Nmeasure = Nm;
+      Naverage ++;
+      if (Naverage == NAVERAGE) {
+	NAVERAGE += 50;
+	REALLOCATE (subcatalog[0].average, Average, NAVERAGE);
+	REALLOCATE (subcatalog[0].secfilt, SecFilt, NAVERAGE*Nsecfilt);
+      }
+      continue;
+    }
+
+    // if we want to correct to the galay model, we need to use objects with distances determined by Green & Schlafly
+    if (USE_GALAXY_MODEL && !catalog[0].average[i].Nstarpar) {
+      // we may want to add some starpar quality filters...
+      continue;
     }
 
@@ -116,21 +147,21 @@
       // exclude bogus 
       if (ExcludeBogus) {
-	  double Ri = getMeanR_Big (&catalog[0].measure[offset], &catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt]);
-	  double Di = getMeanD_Big (&catalog[0].measure[offset], &catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt]);
-	  coords.crval1 = catalog[0].average[i].R;
-	  coords.crval2 = catalog[0].average[i].D;
-	  double Xi, Yi;
-	  RD_to_XY (&Xi, &Yi, Ri, Di, &coords);
-	  double radius = hypot(Xi, Yi);
-	  if (radius > ExcludeBogusRadius) {
-	      NskipBogus ++;
-	      if (VERBOSE2) {
-		FILE *foutput = fbogus ? fbogus : stderr;
-		char *date = ohana_sec_to_date(catalog[0].measure[offset].t);
-		fprintf (foutput, "exclude bogus: %10.6f %10.6f : %10.6f %10.6f : %6.2f %6.2f : %6.2f : %5d %s\n", catalog[0].average[i].R, catalog[0].average[i].D, Ri, Di, Xi, Yi, radius, catalog[0].measure[offset].photcode, date);
-		free (date);
-	      }
-	      continue;
+	double Ri = getMeanR_Big (&catalog[0].measure[offset], &catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt]);
+	double Di = getMeanD_Big (&catalog[0].measure[offset], &catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt]);
+	coords.crval1 = catalog[0].average[i].R;
+	coords.crval2 = catalog[0].average[i].D;
+	double Xi, Yi;
+	RD_to_XY (&Xi, &Yi, Ri, Di, &coords);
+	double radius = hypot(Xi, Yi);
+	if (radius > ExcludeBogusRadius) {
+	  NskipBogus ++;
+	  if (VERBOSE2) {
+	    FILE *foutput = fbogus ? fbogus : stderr;
+	    char *date = ohana_sec_to_date(catalog[0].measure[offset].t);
+	    fprintf (foutput, "exclude bogus: %10.6f %10.6f : %10.6f %10.6f : %6.2f %6.2f : %6.2f : %5d %s\n", catalog[0].average[i].R, catalog[0].average[i].D, Ri, Di, Xi, Yi, radius, catalog[0].measure[offset].photcode, date);
+	    free (date);
 	  }
+	  continue;
+	}
       }
 
@@ -157,6 +188,6 @@
       subcatalog[0].measureT[Nmeasure].averef   = Naverage;
       if (RESET) {
-        subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_POOR_ASTROM;
-        subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_AREA;
+	subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_POOR_ASTROM;
+	subcatalog[0].measureT[Nmeasure].dbFlags &= ~ID_MEAS_AREA;
       }
 
@@ -164,7 +195,12 @@
       Nm ++;
       if (Nmeasure == NMEASURE) {
-        NMEASURE += 1000;
-        REALLOCATE (subcatalog[0].measureT, MeasureTiny, NMEASURE);
-      }
+	NMEASURE += 1000;
+	REALLOCATE (subcatalog[0].measureT, MeasureTiny, NMEASURE);
+      }
+    }
+    // we have filtered this object.  do not keep it if we now have too few meas
+    if (Nm <= SRC_MEAS_TOOFEW) {
+      Nmeasure -= Nm;
+      continue;
     }
     subcatalog[0].average[Naverage].Nmeasure = Nm;
@@ -186,5 +222,5 @@
   assert (Nsecfilt == catalog[0].Nsecfilt);
 
-  // limit the total number of stars in the catalog
+// limit the total number of stars in the catalog
   if (MaxDensityUse) {
     LimitDensityCatalog_ByNmeasureGrid (subcatalog, catalog);
@@ -217,7 +253,7 @@
 
 # define SWAPFUNC(A,B){ int tmpI; off_t tmpT;	\
-  tmpI = X[A]; X[A] = X[B]; X[B] = tmpI; \
-  tmpT = Y[A]; Y[A] = Y[B]; Y[B] = tmpT; \
-}
+    tmpI = X[A]; X[A] = X[B]; X[B] = tmpI;	\
+    tmpT = Y[A]; Y[A] = Y[B]; Y[B] = tmpT;	\
+  }
 # define COMPARE(A,B)(X[A] > X[B])
 
@@ -231,5 +267,5 @@
 /* generate a grid in a locally-projected coordinate system, try to select average entries 
    from each grid cell in decending Nmeasure order.
- */ 
+*/ 
 int LimitDensityCatalog_ByNmeasureGrid (Catalog *subcatalog, Catalog *oldcatalog) {
 
@@ -269,13 +305,8 @@
   /* project coordinates to a plane centered on the object with units of arcsec */
   Coords coords;
+  InitCoords (&coords, "DEC--SIN");
   coords.crval1 = Rc;
   coords.crval2 = Dc;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   // convert all average R,D values to X,Y:
Index: /trunk/Ohana/src/relastro/src/client_logger.c
===================================================================
--- /trunk/Ohana/src/relastro/src/client_logger.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/client_logger.c	(revision 37807)
@@ -0,0 +1,37 @@
+# include "relastro.h"
+
+// I'm getting unlogged errors and failures.  I need a log ouput that the clients can
+// write independent of the master
+
+static FILE *logfile = NULL;
+int client_logger_init (char *dirname) {
+
+  char filename[DVO_MAX_PATH];
+
+  snprintf (filename, DVO_MAX_PATH, "%s/log.rlas.XXXXXX", dirname);
+    
+  int fd = mkstemp (filename);
+  if (fd == -1) {
+    fprintf (stderr, "failed to open client logger %s, exiting\n", filename);
+    exit (50);
+  }
+
+  logfile = fdopen (fd, "w");
+  if (!logfile)  {
+    fprintf (stderr, "failed to fdopen client logger, exiting\n");
+    exit (51);
+  }
+  return TRUE;
+}
+
+int client_logger_message (char *format,...) {
+
+  va_list argp;
+
+  va_start (argp, format);
+  vfprintf (logfile, format, argp);
+  va_end (argp);
+
+  fflush (logfile);
+  return TRUE;
+}
Index: /trunk/Ohana/src/relastro/src/fitpoly.c
===================================================================
--- /trunk/Ohana/src/relastro/src/fitpoly.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/fitpoly.c	(revision 37807)
@@ -78,4 +78,39 @@
 }
   
+int array_print (double **matrix, int Nx, int Ny) {
+
+  int i, j;
+  for (i = 0; i < Nx; i++) {
+    for (j = 0; j < Ny; j++) {
+      fprintf (stderr, "%10.3e ", matrix[i][j]);
+    }
+    fprintf (stderr, "\n");
+  }
+  return TRUE;
+}
+
+int fit_print (CoordFit *fit) {
+
+  fprintf (stderr, "Npts: %d, Norder: %d, Nterms: %d, Nsums: %d, Nelems: %d\n",
+	   fit[0].Npts, fit[0].Norder, fit[0].Nterms, fit[0].Nsums, fit[0].Nelems);
+
+  fprintf (stderr, "sums: \n");
+  array_print (fit[0].sum, fit[0].Nsums, fit[0].Nsums);
+
+  fprintf (stderr, "xsums: \n");
+  array_print (fit[0].xsum, fit[0].Nterms, fit[0].Nterms);
+
+  fprintf (stderr, "ysums: \n");
+  array_print (fit[0].ysum, fit[0].Nterms, fit[0].Nterms);
+
+  fprintf (stderr, "xfits: \n");
+  array_print (fit[0].xfit, fit[0].Nterms, fit[0].Nterms);
+
+  fprintf (stderr, "yfits: \n");
+  array_print (fit[0].yfit, fit[0].Nterms, fit[0].Nterms);
+  
+  return TRUE;
+}
+
 // XXX use implicit masks as below or explicit masks (with function to set?)
 // XXX eg, add a global mask to this file and 
@@ -134,4 +169,5 @@
       for (j = 0; j < fit[0].Nelems; j++) {
 	matrix[i][j] = 0.0;
+	matrix[j][i] = 0.0;
       }
       vector[i][0] = 0.0;
@@ -148,5 +184,5 @@
   }	
   
-  if (!dgaussjordan (matrix, vector, fit[0].Nelems, 2)) {
+  if (!dgaussjordan_pivot (matrix, vector, fit[0].Nelems, 2, 1e-16)) {
     array_free (matrix, fit[0].Nelems);
     array_free (vector, fit[0].Nelems);
@@ -277,4 +313,5 @@
 /* this should only apply to the polynomial, not the projection terms */
 /* compare with psastro supporting code */
+// this code will work for linear (Npolyterm == 0 or 1) and linear + map fits
 int fit_apply_coords (CoordFit *fit, Coords *coords, int keepRef) {
 
@@ -352,4 +389,17 @@
   /* keep the order and type from initial values */
   
+  if (isnan(coords[0].crval1)) {
+    return FALSE;
+  }
+  if (isnan(coords[0].crval2)) {
+    return FALSE;
+  }
+  if (isnan(coords[0].crpix1)) {
+    return FALSE;
+  }
+  if (isnan(coords[0].crpix2)) {
+    return FALSE;
+  }
+
   return (TRUE);
 }
Index: /trunk/Ohana/src/relastro/src/high_speed_objects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/high_speed_objects.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/high_speed_objects.c	(revision 37807)
@@ -149,4 +149,5 @@
 
   // define a local projection
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -155,11 +156,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
-  strcpy (tcoords.ctype, "DEC--ARC");
 
   /* build spatial index (RA sort) referencing input array sequence */
@@ -271,5 +266,5 @@
 
   populate_tiny_values (&catalogOut, DVO_TV_MEASURE);
-  UpdateObjects (&catalogOut, 1);
+  UpdateObjects (&catalogOut, 1, 0);
   free_tiny_values(&catalogOut);
 
Index: /trunk/Ohana/src/relastro/src/high_speed_utils.c
===================================================================
--- /trunk/Ohana/src/relastro/src/high_speed_utils.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/high_speed_utils.c	(revision 37807)
@@ -141,5 +141,5 @@
     off_t n;
     for (n = 0; n < NfieldsA; n++) {
-        valuesA[n] = dbExtractAverages (&catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt], &catalog[0].measure[m], &fieldsA[n]);
+      valuesA[n] = dbExtractAverages (&catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt], &catalog[0].measure[m], NULL, NULL, &fieldsA[n]);
     }
     return dbBooleanCond(stackA, NstackA, valuesA);
@@ -155,5 +155,5 @@
     off_t n;
     for (n = 0; n < NfieldsB; n++) {
-        valuesB[n] = dbExtractAverages (&catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt], &catalog[0].measure[m], &fieldsB[n]);
+        valuesB[n] = dbExtractAverages (&catalog[0].average[i], &catalog[0].secfilt[i*Nsecfilt], &catalog[0].measure[m], NULL, NULL, &fieldsB[n]);
     }
     return dbBooleanCond(stackB, NstackB, valuesB);
Index: /trunk/Ohana/src/relastro/src/hpm_objects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/hpm_objects.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/hpm_objects.c	(revision 37807)
@@ -126,4 +126,5 @@
 
   // define a local projection
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -132,10 +133,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
   strcpy (tcoords.ctype, "DEC--ARC");
 
@@ -256,5 +252,5 @@
     // distinguishing a bad fit from a fit with 1 or 2 bad points
     FIT_MODE = FIT_PM_ONLY;
-    UpdateObjects (&testcat, 1);
+    UpdateObjects (&testcat, 1, 0);
 
     free_tiny_values(&testcat);
@@ -286,5 +282,5 @@
 
   populate_tiny_values (&catalogOut, DVO_TV_MEASURE);
-  UpdateObjects (&catalogOut, 1);
+  UpdateObjects (&catalogOut, 1, 0);
   free_tiny_values(&catalogOut);
 
Index: /trunk/Ohana/src/relastro/src/initialize.c
===================================================================
--- /trunk/Ohana/src/relastro/src/initialize.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/initialize.c	(revision 37807)
@@ -9,5 +9,5 @@
 
   if (DCR_BLUE_COLOR_POS)  fprintf (stderr, "DCR_BLUE_COLOR_POS:  %s - %s\n", DCR_BLUE_COLOR_POS, DCR_BLUE_COLOR_NEG);
-  if (DCR_BLUE_COLOR_POS)  fprintf (stderr, "DCR_BLUE_COLOR_POS:  %s - %s\n", DCR_BLUE_COLOR_POS, DCR_BLUE_COLOR_NEG);
+  if (DCR_RED_COLOR_POS)   fprintf (stderr, "DCR_RED_COLOR_POS:   %s - %s\n", DCR_RED_COLOR_POS, DCR_RED_COLOR_NEG);
 
   if (PHOTCODE_KEEP_LIST)  fprintf (stderr, "PHOTCODE_KEEP_LIST:  %s\n", PHOTCODE_KEEP_LIST);
@@ -63,4 +63,12 @@
   initstats (STATMODE);
 
+  if (USE_ICRF_CORRECT) {
+    if (!USE_ICRF_LOCAL && !USE_ICRF_SHFIT) {
+      fprintf (stderr, "no ICRF correction method chosen\n");
+      exit (2);
+    }
+  }
+  if (USE_ICRF_CORRECT) ICRFinit();
+
   /* XXX drop irrelevant entries */
   if (SHOW_PARAMS) {
Index: /trunk/Ohana/src/relastro/src/launch_region_hosts.c
===================================================================
--- /trunk/Ohana/src/relastro/src/launch_region_hosts.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/launch_region_hosts.c	(revision 37807)
@@ -6,25 +6,52 @@
   int i;
 
-  // clear the I/O files
-  for (i = 0; i < regionHosts->Nhosts; i++) {
-    char *syncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "meanmags.sync");
-    if (truncate (syncfile, 0)) fprintf (stderr, "trouble clearing syncfile %s\n", syncfile);
-    free (syncfile);
+  // do not remote the sync and fits files if we do a manual run -- user must clear if needed
+  if (!PARALLEL_REGIONS_MANUAL) {
+    // clear the I/O files
+    for (i = 0; i < regionHosts->Nhosts; i++) {
+      char *meansync = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "meanpos.sync");
+      if (truncate (meansync, 0)) fprintf (stderr, "trouble clearing meansync %s\n", meansync);
+      free (meansync);
 
-    char *fitsfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "meanmags.fits");
-    if (truncate (fitsfile, 0)) fprintf (stderr, "trouble clearing fitsfile %s\n", fitsfile);
-    free (fitsfile);
+      char *meanfits = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "meanpos.fits");
+      if (truncate (meanfits, 0)) fprintf (stderr, "trouble clearing meanfits %s\n", meanfits);
+      free (meanfits);
 
-    char *imsyncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "imagemags.sync");
-    if (truncate (imsyncfile, 0)) fprintf (stderr, "trouble clearing imsyncfile %s\n", imsyncfile);
-    free (imsyncfile);
+      char *meassync = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "measpos.sync");
+      if (truncate (meassync, 0)) fprintf (stderr, "trouble clearing meassync %s\n", meassync);
+      free (meassync);
 
-    char *imfitsfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "imagemags.fits");
-    if (truncate (imfitsfile, 0)) fprintf (stderr, "trouble clearing imfitsfile %s\n", imfitsfile);
-    free (imfitsfile);
+      char *measfits = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "measpos.fits");
+      if (truncate (measfits, 0)) fprintf (stderr, "trouble clearing measfits %s\n", measfits);
+      free (measfits);
 
-    char *loopsyncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "loop.sync");
-    if (truncate (loopsyncfile, 0)) fprintf (stderr, "trouble clearing loopsyncfile %s\n", loopsyncfile);
-    free (loopsyncfile);
+      char *icrfsync = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "icrfobj.sync");
+      if (truncate (icrfsync, 0)) fprintf (stderr, "trouble clearing icrfsync %s\n", icrfsync);
+      free (icrfsync);
+
+      char *icrffits = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "icrfobj.fits");
+      if (truncate (icrffits, 0)) fprintf (stderr, "trouble clearing icrffits %s\n", icrffits);
+      free (icrffits);
+
+      char *imsyncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "imagepos.sync");
+      if (truncate (imsyncfile, 0)) fprintf (stderr, "trouble clearing imsyncfile %s\n", imsyncfile);
+      free (imsyncfile);
+
+      char *imfitsfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "imagepos.fits");
+      if (truncate (imfitsfile, 0)) fprintf (stderr, "trouble clearing imfitsfile %s\n", imfitsfile);
+      free (imfitsfile);
+
+      char *loopsyncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "loop.sync");
+      if (truncate (loopsyncfile, 0)) fprintf (stderr, "trouble clearing loopsyncfile %s\n", loopsyncfile);
+      free (loopsyncfile);
+    }
+
+    char *framesync = make_filename (CATDIR, "master", 0, "frame.corr.sync");
+    if (truncate (framesync, 0)) fprintf (stderr, "trouble clearing framesync %s\n", framesync);
+    free (framesync);
+  
+    char *framefits = make_filename (CATDIR, "master", 0, "frame.corr.fits");
+    if (truncate (framefits, 0)) fprintf (stderr, "trouble clearing framefits %s\n", framefits);
+    free (framefits);
   }
 
@@ -37,7 +64,16 @@
     char filename[1024];
     snprintf (filename, 1024, "%s/Image.%d.fits", CATDIR, host->hostID);
+    if (unlink (filename)) fprintf (stderr, "trouble clearing image %s\n", filename);
 
     // write the image subset for this host
     ImageTableSave (filename, host->image, host->Nimage);
+
+    if (host->astromTable) {
+      char mapname[1024];
+      snprintf (mapname, 1024, "%s/AstroMap.%d.fits", CATDIR, host->hostID);
+
+      // write the image subset for this host
+      AstromOffsetMapSave (host->astromTable, mapname);
+    }
 
     char command[1024];
@@ -92,4 +128,7 @@
     if (PhotFlagPoor)       strextend (command, "+photflagpoor %d", PhotFlagPoor); 
 
+    if (CHIPMAP)            strextend (command, "-chipmap %d", CHIPMAP); 
+    if (RESET_IMAGES)       strextend (command, "-reset-images"); 
+
     if (MinBadQF > 0.0)        strextend (command, "-min-bad-psfqf %f", MinBadQF);
     if (MaxMeanOffset != 10.0) strextend (command, "-max-mean-offset  %f", MaxMeanOffset);
@@ -99,4 +138,11 @@
     strextend (command, "-D RELASTRO_DPOS_MAX %f", DPOS_MAX);
     strextend (command, "-D ADDSTAR_RADIUS %f", ADDSTAR_RADIUS);
+
+    strextend (command, "-D USE_GALAXY_MODEL %d", USE_GALAXY_MODEL);
+
+    strextend (command, "-D USE_ICRF_CORRECT %d", USE_ICRF_CORRECT);
+    strextend (command, "-D USE_ICRF_LOCAL %d",   USE_ICRF_LOCAL);
+    strextend (command, "-D USE_ICRF_SHFIT %d",   USE_ICRF_SHFIT);
+    strextend (command, "-D USE_ICRF_POLE %d",    USE_ICRF_POLE);
 
     if (USE_FIXED_PIXCOORDS) strextend (command, "-D USE_FIXED_PIXCOORDS 1");
@@ -133,16 +179,20 @@
   }
 
+  if (USE_ICRF_CORRECT) return TRUE;
+
+  int status = HarvestRegionHosts (regionHosts);
+  return status;
+}
+
+int HarvestRegionHosts (RegionHostTable *regionHosts) {
+
+  int status = TRUE;
+
   if (PARALLEL_REGIONS_MANUAL) {
     fprintf (stderr, "run the relastro_client commands above.  when these are done, hit return\n");
     getchar();
   } else {
-    RegionHostTableWaitJobsGetIO (regionHosts, __FILE__, __LINE__, VERBOSE);
+    status = RegionHostTableWaitJobsGetIO (regionHosts, __FILE__, __LINE__, VERBOSE);
   }
- 
-  int status = TRUE;
-  for (i = 0; i < regionHosts->Nhosts; i++) {
-    status = status && (regionHosts->hosts[i].status == 0);
-  }
-
   return status;
 }
Index: /trunk/Ohana/src/relastro/src/liststats.c
===================================================================
--- /trunk/Ohana/src/relastro/src/liststats.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/liststats.c	(revision 37807)
@@ -252,5 +252,5 @@
     X2 += M / dM;
     dS += M;
-    if (XVERB) fprintf (stderr, "chisq contrib : %f : %f : %f : %f : %f\n", Mo, 3600.0*(value[i] - Mo), 3600.0*dvalue[i], (value[i] - Mo) / dvalue[i], X2);
+    if (XVERB) fprintf (stderr, "chisq contrib : %f : %f : %f : %f : %f\n", Mo, (value[i] - Mo), dvalue[i], (value[i] - Mo) / dvalue[i], X2);
   }
   X2 = X2 / (Nm - 1);
Index: /trunk/Ohana/src/relastro/src/load_catalogs.c
===================================================================
--- /trunk/Ohana/src/relastro/src/load_catalogs.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/load_catalogs.c	(revision 37807)
@@ -6,5 +6,5 @@
 Catalog *load_catalogs (SkyList *skylist, int *Ncatalog, int subselect, int hostID, char *hostpath, char *syncfile) {
 
-  int i, j, Nstar;
+  int i, j;
   // int k, m;
   Catalog *catalog, *pcatalog, tcatalog;
@@ -88,8 +88,11 @@
 
   bcatalog_show_skips();
-
-  Nstar = 0;
+  fprintf (stderr, "included %d ICRF QSOs\n", ICRFmax()); 
+
+  int Nstar = 0;
+  int Nmeas = 0;
   for (i = 0; i < Ncat; i++) {
     Nstar += catalog[i].Naverage;
+    Nmeas += catalog[i].Nmeasure;
     if ((sizeof(IDX_T) == 8) && (catalog[i].Nmeasure > 0xffffffff)) {
       fprintf (stderr, "ERROR: using small-sized IDX_T on data with more than 2G detections per table will cause errors\n");
@@ -101,4 +104,5 @@
     fprintf (stderr, "warning: insufficient stars %d\n", Nstar);
   }
+  fprintf (stderr, "using %d stars, %d measurements to calibrate\n", Nstar, Nmeas);
 
   // if we are running with parallel_images but not a parallel database, we need to
@@ -170,4 +174,7 @@
     if (FlagOutlier)   strextend (command, "-clip %d", CLIP_THRESH);
     if (ExcludeBogus)  strextend (command, "-exclude-bogus %f", ExcludeBogusRadius);
+
+    if (USE_ICRF_CORRECT) strextend (command, "-D USE_ICRF_CORRECT %d", USE_ICRF_CORRECT);
+    if (USE_GALAXY_MODEL) strextend (command, "-D USE_GALAXY_MODEL %d", USE_GALAXY_MODEL);
     
     if (DCR_BLUE_COLOR_POS && DCR_BLUE_COLOR_NEG) {
Index: /trunk/Ohana/src/relastro/src/load_images.c
===================================================================
--- /trunk/Ohana/src/relastro/src/load_images.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/load_images.c	(revision 37807)
@@ -1,3 +1,9 @@
 # include "relastro.h"
+
+static AstromOffsetTable *table = NULL;
+
+/* AstromOffsetMap is the map of Astrometry Offsets for non-polynomial astrometric
+ * corrections for each chip.  
+ */
 
 int load_images (FITS_DB *db, SkyList *skylist, int UseFullOverlap) {
@@ -17,7 +23,27 @@
   MARKTIME("  convert image table: %f sec\n", dtime);
 
+  // assign image->parent and image->coords.mosaic 
+  BuildChipMatch (image, Nimage);
+  MARKTIME("build chip match: %f sec\n", dtime);
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", CATDIR);
+  table = AstromOffsetMapLoad (mapfile, VERBOSE);
+
+  // assign images.coords.offsetMap -> table->map[i]
+  if (table) {
+    AstromOffsetTableMatchChips (image, Nimage, table);
+  } else {
+    table = AstromOffsetTableInit ();
+  }
+
   // select the images which overlap the selected sky regions
   subset = select_images (skylist, image, Nimage, &LineNumber, &Nsubset, UseFullOverlap);
   MARKTIME("  select images: %f sec\n", dtime);
+
+  if (Nsubset == Nimage) {
+    free (subset);
+    subset = image;
+  }
 
   initImages (subset, LineNumber, Nsubset);
@@ -51,2 +77,20 @@
   return TRUE;
 }
+
+int save_astrom_table () {
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", CATDIR);
+  AstromOffsetMapSave (table, mapfile);
+
+  return TRUE;
+}
+
+AstromOffsetTable *get_astrom_table () {
+  return table;
+}
+
+void put_astrom_table (AstromOffsetTable *myTable) {
+  table = myTable;
+}
+
Index: /trunk/Ohana/src/relastro/src/markObjects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/markObjects.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/markObjects.c	(revision 37807)
@@ -14,6 +14,16 @@
       int m = catalog[i].average[j].measureOffset;
       for (n = 0; n < catalog[i].average[j].Nmeasure; n++) {
-	if (!catalog[i].measureT[m+n].myDet) continue;
-	nOwn ++;
+	// this is a detection I own (it is on one of my images)
+	if (catalog[i].measureT[m+n].myDet) {
+	  nOwn ++;
+	  continue;
+	}
+	// 2MASS detections are counted as 'owned' (I completely control this object is nOwn_t == Nmeasure; no sharing needed)
+	if ((catalog[i].measureT[m+n].photcode == 2011) || 
+	    (catalog[i].measureT[m+n].photcode == 2012) || 
+	    (catalog[i].measureT[m+n].photcode == 2013)) {
+	  nOwn ++;
+	  continue;
+	}
       }
       catalog[i].nOwn_t[j] = nOwn;
Index: /trunk/Ohana/src/relastro/src/relastroVisual.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastroVisual.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/relastroVisual.c	(revision 37807)
@@ -24,4 +24,8 @@
 /** Spawn a kapa window */
 static int initWindow(int *kapid) {
+  if (NTHREADS) {
+    fprintf (stderr, "-visual and -threads are incompatible, quitting\n");
+    exit (2);
+  }
   if (*kapid == -1) {
     *kapid = KapaOpenNamedSocket("kapa", "relastro_plots");
@@ -348,5 +352,5 @@
 
   int i;
-  for(i = 0; i < numObj; i++) {
+  for (i = 0; i < numObj; i++) {
     if (raw[i].mask) continue; // XXX 
 
Index: /trunk/Ohana/src/relastro/src/relastro_client.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro_client.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/relastro_client.c	(revision 37807)
@@ -79,4 +79,6 @@
       load_images (&db, skylist, FALSE);
       
+      // I can free the database after I have loaded the images...
+
       // // load the image subset table from the specified location
       // off_t Nimage;
Index: /trunk/Ohana/src/relastro/src/relastro_images.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro_images.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/relastro_images.c	(revision 37807)
@@ -23,9 +23,5 @@
   MARKTIME("load images: %f sec\n", dtime);
 
-  initCoords();
-
   /* load catalog data from region files : subselect high-quality measurements */
-  // XXX pass in the image table
-  // XXX who carries the image grid?
 
   // photcodesKeep is used here to allow measurements from the images being calibrated
@@ -33,4 +29,7 @@
   catalog = load_catalogs (skylist, &Ncatalog, TRUE, 0, NULL, NULL);
   MARKTIME("load catalog data: %f sec\n", dtime);
+
+  // find ICRF QSOs for reference downstream (only if USE_ICRF_CORRECT)
+  select_catalog_ICRF (catalog, Ncatalog);
 
   if (photcodesReset) {
@@ -65,5 +64,5 @@
     case TARGET_SIMPLE:
       for (i = 0; i < NLOOP; i++) {
-	UpdateObjects (catalog, Ncatalog);
+	UpdateObjects (catalog, Ncatalog, i);
 	UpdateSimple (catalog, Ncatalog);
       }
@@ -71,7 +70,13 @@
 
     case TARGET_CHIPS:
+      if (RESET_IMAGES) UpdateMeasures (catalog, Ncatalog);
       for (i = 0; i < NLOOP; i++) {
-	UpdateObjects (catalog, Ncatalog);
-	UpdateChips (catalog, Ncatalog);
+	UpdateObjects (catalog, Ncatalog, (i > 0)); // calculate <R>,<D>; if (i > 0), apply Galaxy Motion Model (if desired)
+	if ((i > 1) || !USE_GALAXY_MODEL) {
+	  // if GALAXY_MODEL is selected, we want to delay the frame correction until we have 
+	  // applied the galaxy model a couple of times.
+	  FrameCorrectionSerial (catalog, Ncatalog);
+	}
+	UpdateChips (catalog, Ncatalog);   // measure.X,Y -> R,D, fit image.coords
 	MARKTIME("update chips: %f sec\n", dtime);
       }
@@ -82,5 +87,5 @@
     case TARGET_MOSAICS:
       for (i = 0; i < NLOOP; i++) {
-	UpdateObjects (catalog, Ncatalog);
+	UpdateObjects (catalog, Ncatalog, i);
 	UpdateMosaic (catalog, Ncatalog);
       }
@@ -102,4 +107,6 @@
   if (PARALLEL) {
     // save the updated image parameters
+    // need to also save the image map table...
+    save_astrom_table ();
     dvo_image_update (&db, VERBOSE);
     dvo_image_unlock (&db); 
@@ -109,9 +116,7 @@
   UpdateObjectOffsets (skylist, 0, NULL);
 
-  // iterate over catalogs to make detection coordinates consistant
-  // FixProblemImages (skylist);
-
   if (!PARALLEL) {
     // save the updated image parameters
+    save_astrom_table ();
     dvo_image_update (&db, VERBOSE);
     dvo_image_unlock (&db); 
Index: /trunk/Ohana/src/relastro/src/relastro_objects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro_objects.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/relastro_objects.c	(revision 37807)
@@ -77,5 +77,5 @@
     populate_tiny_values(&catalog, DVO_TV_MEASURE);
 
-    UpdateObjects (&catalog, 1);
+    UpdateObjects (&catalog, 1, 0);
 
     free_tiny_values(&catalog);
Index: /trunk/Ohana/src/relastro/src/relastro_parallel_images.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro_parallel_images.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/relastro_parallel_images.c	(revision 37807)
@@ -17,8 +17,11 @@
   INITTIME;
 
+  client_logger_init (CATDIR);
+
   // load the RegionTable (UserRegion should not be used at this level)
   RegionHostTable *regionHosts = RegionHostTableLoad (CATDIR, REGION_FILE);
   int myHost = regionHosts->index[REGION_HOST_ID];
   RegionHostFindNeighbors (regionHosts, myHost);
+  client_logger_message ("started parallel images on %s\n", regionHosts->hosts[myHost].hostname);
 
   // load the subset images belonging to this host
@@ -29,4 +32,20 @@
     exit (2);
   }
+
+  // assign image->parent and image->coords.mosaic 
+  BuildChipMatch (image, Nimage);
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.%d.fits", CATDIR, REGION_HOST_ID);
+  AstromOffsetTable *table = AstromOffsetMapLoad (mapfile, VERBOSE);
+
+  // assign images.coords.offsetMap -> table->map[i]
+  if (table) {
+    AstromOffsetTableMatchChips (image, Nimage, table);
+  } else {
+    table = AstromOffsetTableInit ();
+  }
+  put_astrom_table (table);
+  client_logger_message ("loaded images on %s\n", regionHosts->hosts[myHost].hostname);
 
   // once we have read this table, we should remove it for repeat runs
@@ -42,12 +61,15 @@
   SkyList *skylist = SkyListByBounds (sky, -1, UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax);
 
-  initCoords();
-
   /* load catalog data from region files (hostID is 0 since we are not a client */
   char *syncfile = make_filename (CATDIR, regionHosts->hosts[myHost].hostname, REGION_HOST_ID, "loadcat.sync");
   catalog = load_catalogs (skylist, &Ncatalog, TRUE, 0, NULL, syncfile);
-  MARKTIME("-- load catalog data: %f sec\n", dtime);
+  MARKTIME("-- load catalog data, host %d: %f sec\n", REGION_HOST_ID, dtime);
   free (syncfile);
   
+  client_logger_message ("loaded catalog data %s\n", regionHosts->hosts[myHost].hostname);
+
+  // find ICRF QSOs for reference downstream (only if USE_ICRF_CORRECT)
+  select_catalog_ICRF (catalog, Ncatalog);
+
   if (photcodesReset) {
     photcodesKeep  = photcodesReset;
@@ -60,11 +82,13 @@
   /* match measurements with images, mosaics */
   initImageBins  (catalog, Ncatalog, TRUE);
-  MARKTIME("-- make image bins: %f sec\n", dtime);
+  MARKTIME("-- make image bins, host %d: %f sec\n", REGION_HOST_ID, dtime);
 
   findImages (catalog, Ncatalog, TRUE);
-  MARKTIME("-- set up image indexes: %f sec\n", dtime);
+  MARKTIME("-- set up image indexes, host %d: %f sec\n", REGION_HOST_ID, dtime);
 
   // set test points based on the starmap
   createStarMap (catalog, Ncatalog);
+
+  client_logger_message ("set up image indexes on %s\n", regionHosts->hosts[myHost].hostname);
 
   markObjects (catalog, Ncatalog);
@@ -72,26 +96,52 @@
   SAVEPLOT = FALSE;
 
+  client_logger_message ("starting the loops: %s\n", regionHosts->hosts[myHost].hostname);
+
   /* major modes */
   switch (FIT_TARGET) {
     case TARGET_SIMPLE:
       for (i = 0; i < NLOOP; i++) {
-	UpdateObjects (catalog, Ncatalog);
+	UpdateObjects (catalog, Ncatalog, i);
 	share_mean_pos (catalog, Ncatalog, regionHosts, i);
 	slurp_mean_pos (catalog, Ncatalog, regionHosts, i);
 	UpdateSimple (catalog, Ncatalog);
 	share_image_pos (regionHosts, i);
-	slurp_image_pos (regionHosts, i);
+	slurp_image_pos (catalog, Ncatalog, regionHosts, i);
       }
       break;
 
     case TARGET_CHIPS:
+      if (RESET_IMAGES) {
+	UpdateMeasures (catalog, Ncatalog);
+	MARKTIME("reset measures, host %d: %f sec\n", REGION_HOST_ID, dtime);
+	client_logger_message ("reset measures: %s\n", regionHosts->hosts[myHost].hostname);
+      }
       for (i = 0; i < NLOOP; i++) {
-	UpdateObjects (catalog, Ncatalog);
+	UpdateObjects (catalog, Ncatalog, (i > 0));
+	MARKTIME("update objects loop %d, host %d: %f sec\n", i, REGION_HOST_ID, dtime);
+	client_logger_message ("update objects loop %d: %s\n", i, regionHosts->hosts[myHost].hostname);
+	if ((i > 1) || !USE_GALAXY_MODEL) {
+	  // if GALAXY_MODEL is selected, we want to delay the frame correction until we have 
+	  // applied the galaxy model a couple of times.
+	  FrameCorrectionParallelSlave (catalog, Ncatalog, regionHosts, i); 
+	  MARKTIME("frame correction loop %d, host %d: %f sec\n", i, REGION_HOST_ID, dtime);
+	  client_logger_message ("frame correction loop %d: %s\n", i, regionHosts->hosts[myHost].hostname);
+	}
 	share_mean_pos (catalog, Ncatalog, regionHosts, i);
 	slurp_mean_pos (catalog, Ncatalog, regionHosts, i);
+	client_logger_message ("slurp mags loop %d: %s\n", i, regionHosts->hosts[myHost].hostname);
+
 	UpdateChips (catalog, Ncatalog);
+	MARKTIME("update chips loop %d, host %d: %f sec\n", i, REGION_HOST_ID, dtime);
+	client_logger_message ("update chips loop %d: %s\n", i, regionHosts->hosts[myHost].hostname);
+
 	share_image_pos (regionHosts, i);
-	slurp_image_pos (regionHosts, i);
-	MARKTIME("update chips: %f sec\n", dtime);
+	slurp_image_pos (catalog, Ncatalog, regionHosts, i);
+	client_logger_message ("slurp image pos loop %d: %s\n", i, regionHosts->hosts[myHost].hostname);
+
+	share_meas_pos (catalog, Ncatalog, regionHosts, i);
+	slurp_meas_pos (catalog, Ncatalog, regionHosts, i);
+	MARKTIME("done exchange loop %d, host %d: %f sec\n", i, REGION_HOST_ID, dtime);
+	client_logger_message ("slurp meas loop %d: %s\n", i, regionHosts->hosts[myHost].hostname);
       }
       // create summary plots of the process
@@ -101,10 +151,10 @@
     case TARGET_MOSAICS:
       for (i = 0; i < NLOOP; i++) {
-	UpdateObjects (catalog, Ncatalog);
+	UpdateObjects (catalog, Ncatalog, i);
 	share_mean_pos (catalog, Ncatalog, regionHosts, i);
 	slurp_mean_pos (catalog, Ncatalog, regionHosts, i);
 	UpdateMosaic (catalog, Ncatalog);
 	share_image_pos (regionHosts, i);
-	slurp_image_pos (regionHosts, i);
+	slurp_image_pos (catalog, Ncatalog, regionHosts, i);
       }
       break;
@@ -128,4 +178,5 @@
 
   share_image_pos (regionHosts, -1);
+  client_logger_message ("share image pos loop %d: %s\n", -1, regionHosts->hosts[myHost].hostname);
 
   exit (0);
Index: /trunk/Ohana/src/relastro/src/relastro_parallel_regions.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro_parallel_regions.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/relastro_parallel_regions.c	(revision 37807)
@@ -44,6 +44,10 @@
   if (!launch_region_hosts (regionHosts)) Shutdown ("error launching region hosts");
 
+  // If we are doing FrameCorrection, we have to loop here waiting for ICRF objects from the region 
+  // hosts.  we then measure the frame correction and send back the result
+  if (!FrameCorrectionParallelMaster (regionHosts)) Shutdown ("error running parallel frame correction");
+
   // retrieve updated image parameters from the remote hosts (also set Image.mcal)
-  if (!slurp_image_pos (regionHosts, -1)) Shutdown ("error loading image updates");
+  if (!slurp_image_pos (NULL, 0, regionHosts, -1)) Shutdown ("error loading image updates");
 
   if (!UPDATE) { 
@@ -59,5 +63,7 @@
 
   if (PARALLEL) {
-    // save the updated image parameters
+    // save the updated image parameters.  if we are NOT PARALLEL, we have to wait
+    // until after UpdateObjectOffsets, or the image parameters are swapped and/or freed
+    save_astrom_table ();
     dvo_image_save (&db, VERBOSE);
     dvo_image_unlock (&db); 
@@ -65,8 +71,11 @@
 
   // iterate over catalogs to make detection coordinates consistant
-  UpdateObjectOffsets (skylist, 0, NULL);
+  if (APPLY_OFFSETS) {
+    UpdateObjectOffsets (skylist, 0, NULL);
+  }
 
   if (!PARALLEL) {
     // save the changes to the image parameters
+    save_astrom_table ();
     dvo_image_save (&db, VERBOSE);
     dvo_image_unlock (&db); 
Index: /trunk/Ohana/src/relastro/src/select_images.c
===================================================================
--- /trunk/Ohana/src/relastro/src/select_images.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/select_images.c	(revision 37807)
@@ -23,5 +23,4 @@
   int InRange, found;
   double Ri[5], Di[5], Xi[5], Yi[5], dx, dy;
-  Coords tcoords;
   SkyRegionCoords *skycoords;
   
@@ -46,9 +45,7 @@
 
   // the comparison is made in the catalog local projection. below we set crval1,2
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
+  Coords tcoords;
+  InitCoords (&tcoords, "DEC--TAN");
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1  = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2  = tcoords.pc2_1 = 0.0;
-  strcpy (tcoords.ctype, "DEC--TAN");
 
   ALLOCATE (skycoords, SkyRegionCoords, skylist[0].Nregions);
@@ -104,6 +101,4 @@
 
   if (VERBOSE) fprintf (stderr, "finding images\n");
-  BuildChipMatch (timage, Ntimage);
-  MARKTIME("build chip match: %f sec\n", dtime);
 
   nimage = 0;
@@ -148,10 +143,4 @@
     }
     
-    // this adds 1.3 sec for 3M images
-    if (!FindMosaicForImage (timage, Ntimage, i)) {
-      fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
-
     /* define image corners - note the DIS images (mosaic phu) are special */
     if (!strcmp(&timage[i].coords.ctype[4], "-DIS")) {
Index: /trunk/Ohana/src/relastro/src/share_icrf_obj.c
===================================================================
--- /trunk/Ohana/src/relastro/src/share_icrf_obj.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/share_icrf_obj.c	(revision 37807)
@@ -0,0 +1,136 @@
+# include "relastro.h"
+
+// we are sharing mean and specific positions for all known ICRF objects which I own
+// this function occurs on the remote hosts in parallel-region processing
+int share_icrf_obj (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop) {
+
+  if (!USE_ICRF_CORRECT) return TRUE;
+
+  ICRFobj *icrfobj = get_ICRF_data (catalog, Ncatalog);
+
+  // write out the meanmag fits table AND write state in some file
+  int myHost = regionHosts->index[REGION_HOST_ID];
+  char *hostname = regionHosts->hosts[myHost].hostname;
+
+  char *filename = make_filename (CATDIR, hostname, REGION_HOST_ID, "icrfobj.fits");
+  ICRFobjSave (filename, icrfobj);
+  free (filename);
+
+  free (icrfobj);
+
+  char *syncfile = make_filename (CATDIR, hostname, REGION_HOST_ID, "icrfobj.sync");
+  update_sync_file (syncfile, nloop);
+  free (syncfile);
+
+  return TRUE;
+}
+
+// load mean and specific positions for all known ICRF objects from remote hosts.
+// this function occurs on the master host in parallel-region processing
+ICRFobj *slurp_icrf_obj (RegionHostTable *regionHosts, int nloop) {
+
+  off_t i;
+
+  ICRFobj *icrfobj = NULL;
+  ALLOCATE (icrfobj, ICRFobj, 1);
+  ALLOCATE (icrfobj->Rave,  double, 1);
+  ALLOCATE (icrfobj->Dave,  double, 1);
+  ALLOCATE (icrfobj->dRoff, double, 1);
+  ALLOCATE (icrfobj->dDoff, double, 1);
+  icrfobj->Nicrfobj = 0;
+
+  fprintf (stderr, "grabbing icrf objects from remote hosts...\n");
+
+  for (i = 0; i < regionHosts->Nhosts; i++) {
+    char *syncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "icrfobj.sync");
+    check_sync_file (syncfile, nloop);
+    free (syncfile);
+    
+    char *filename = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "icrfobj.fits");
+    ICRFobj *icrfobjSubset = ICRFobjLoad (filename);
+    free (filename);
+
+    // merge_mean_pos reallocs icrfobj and frees the input icrfobjSubset
+    icrfobj = merge_icrf_obj (icrfobj, icrfobjSubset);
+  }
+  return icrfobj;
+}
+
+ICRFobj *merge_icrf_obj (ICRFobj *target, ICRFobj *source) {
+
+  off_t i;
+
+  REALLOCATE (target->Rave,  double, target->Nicrfobj + source->Nicrfobj);
+  REALLOCATE (target->Dave,  double, target->Nicrfobj + source->Nicrfobj);
+  REALLOCATE (target->dRoff, double, target->Nicrfobj + source->Nicrfobj);
+  REALLOCATE (target->dDoff, double, target->Nicrfobj + source->Nicrfobj);
+
+  for (i = 0; i < source->Nicrfobj; i++) {
+    off_t n = i + target->Nicrfobj;
+    target->Rave[n]  = source->Rave[i];
+    target->Dave[n]  = source->Dave[i];
+    target->dRoff[n] = source->dRoff[i];
+    target->dDoff[n] = source->dDoff[i];
+  }
+  target->Nicrfobj += source->Nicrfobj;
+
+  ICRFobjFree (source);
+  return (target);
+}
+
+ICRFobj *get_ICRF_data (Catalog *catalog, int Ncatalog) {
+
+  int i;
+  int Nicrf = ICRFmax();
+
+  // we need to write an empty table none are available
+  ICRFobj *icrfobj = NULL;
+  ALLOCATE (icrfobj, ICRFobj, 1);
+
+  ALLOCATE (icrfobj->Rave,  double, Nicrf);
+  ALLOCATE (icrfobj->Dave,  double, Nicrf);
+  ALLOCATE (icrfobj->dRoff, double, Nicrf);
+  ALLOCATE (icrfobj->dDoff, double, Nicrf);
+
+  // select the ICRF QSOS and save the necessary data
+  int Npts = 0;
+  for (i = 0; i < Nicrf; i++) {
+
+    int cat, ave, meas;
+    ICRFdata (i, &cat, &ave, &meas);
+
+    Average *average = &catalog[cat].average[ave];
+    MeasureTiny *measure = &catalog[cat].measureT[meas]; // MeasureTiny?
+
+    // record these in arcsec or degree?
+    // correct for cos(D) or not?
+    double dR = 3600.0*(average->R - measure->R)*cos(average->R*RAD_DEG);
+    double dD = 3600.0*(average->D - measure->D);
+
+    // dR = 3600*(Rave - Rmeas) -> Rmeas = Rave - dR / 36000
+
+    if (isnan(dR)) continue;
+    if (isnan(dD)) continue;
+
+    icrfobj->Rave[Npts]  = average->R;
+    icrfobj->Dave[Npts]  = average->D;
+    icrfobj->dRoff[Npts] = dR;
+    icrfobj->dDoff[Npts] = dD;
+    Npts ++;
+  }
+  icrfobj->Nicrfobj = Npts;
+
+  return icrfobj;
+}
+
+void ICRFobjFree (ICRFobj *icrfobj) {
+
+  if (!icrfobj) return;
+
+  FREE (icrfobj->Rave);
+  FREE (icrfobj->Dave);
+  FREE (icrfobj->dRoff);
+  FREE (icrfobj->dDoff);
+  return;
+}
+
Index: /trunk/Ohana/src/relastro/src/share_images_pos.c
===================================================================
--- /trunk/Ohana/src/relastro/src/share_images_pos.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/share_images_pos.c	(revision 37807)
@@ -34,4 +34,14 @@
   free (iposfile);
 
+  // save the per-chip residual table
+  AstromOffsetTable *table = get_astrom_table ();
+  if (table) {
+    char mapname[1024];
+    snprintf (mapname, 1024, "%s/AstroMapUpdate.%d.fits", CATDIR, REGION_HOST_ID);
+    
+    // write the image subset for this host
+    AstromOffsetMapSave (table, mapname);
+  }
+
   char *syncfile = make_filename (CATDIR, hostname, REGION_HOST_ID, "imagepos.sync");
   update_sync_file (syncfile, nloop);
@@ -41,7 +51,7 @@
 }
 
-int slurp_image_pos (RegionHostTable *regionHosts, int nloop) {
+int slurp_image_pos (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop) {
 
-  off_t Nimage, i;
+  off_t Nimage, i, j;
   Image *images = getimages (&Nimage, NULL);
 
@@ -74,10 +84,55 @@
       continue;
     }
+    Coords *moscoords = images[seq].coords.mosaic;
     images[seq].coords     = image_pos[i].coords    ;
-    images[seq].dXpixSys   = image_pos[i].dXpixSys  ;
-    images[seq].dYpixSys   = image_pos[i].dYpixSys  ;
-    images[seq].imageID    = image_pos[i].imageID   ;
-    images[seq].nFitAstrom = image_pos[i].nFitAstrom;
-    images[seq].flags      = image_pos[i].flags     ;
+    images[seq].coords.mosaic = moscoords;
+
+    images[seq].dXpixSys     = image_pos[i].dXpixSys  ;
+    images[seq].dYpixSys     = image_pos[i].dYpixSys  ;
+    images[seq].refColorBlue = image_pos[i].refColorBlue;
+    images[seq].refColorRed  = image_pos[i].refColorRed;
+    images[seq].imageID      = image_pos[i].imageID   ;
+    images[seq].nFitAstrom   = image_pos[i].nFitAstrom;
+    images[seq].flags        = image_pos[i].flags     ;
+  }
+
+  // load the astrometry offset maps, if they exist, and apply 
+  for (i = 0; i < regionHosts->Nhosts; i++) {
+    if (regionHosts->hosts[i].hostID == REGION_HOST_ID) continue;
+    if (REGION_HOST_ID && !regionHosts->hosts[i].isNeighbor) continue;
+
+    char mapname[1024];
+    snprintf (mapname, 1024, "%s/AstroMapUpdate.%d.fits", CATDIR, regionHosts->hosts[i].hostID);
+    
+    AstromOffsetTable *table = AstromOffsetMapLoad (mapname, VERBOSE);
+  
+    // apply table entries here to existing images
+    for (j = 0; j < table->Nmap; j++) {
+      off_t seq = getImageByID (table->map[j][0].imageID);
+      // I do not necessarily own all images listed in the table.  skip if not found
+      if (seq < 0) continue;
+
+      AstromOffsetMap *oldMap = images[seq].coords.offsetMap;
+      AstromOffsetMap *newMap = table->map[j];
+
+      if (oldMap) {
+	AstromOffsetMapSetOrder (oldMap, newMap->Nx, newMap->Ny, &images[seq]);
+	AstromOffsetMapCopyData (oldMap, newMap);
+      } else {
+	lockUpdateChips ();
+	AstromOffsetTable *FullTable = get_astrom_table ();
+	AstromOffsetTableNewMap(FullTable, newMap->Nx, newMap->Ny, &images[seq]);  // registers the map with the image
+	unlockUpdateChips ();
+	AstromOffsetMapCopyData (images[seq].coords.offsetMap, newMap); // oldMap is now not valid
+      }
+    }
+    AstromOffsetTableFree(table);
+  }
+
+  // apply the modified image parameters to my detections
+  for (i = 0; FALSE && (Ncatalog > 0) && (i < Nimage_pos); i++) {
+    off_t seq = getImageByID (image_pos[i].imageID);
+    if (seq < 0) continue;
+    updateImageRaw (catalog, Ncatalog, seq);
   }
   free (image_pos);
@@ -93,4 +148,6 @@
   image_pos->dXpixSys     = image->dXpixSys;
   image_pos->dYpixSys     = image->dYpixSys;
+  image_pos->refColorBlue = image->refColorBlue;
+  image_pos->refColorRed  = image->refColorRed;
   image_pos->imageID      = image->imageID;
   image_pos->nFitAstrom   = image->nFitAstrom;
Index: /trunk/Ohana/src/relastro/src/share_meas_pos.c
===================================================================
--- /trunk/Ohana/src/relastro/src/share_meas_pos.c	(revision 37807)
+++ /trunk/Ohana/src/relastro/src/share_meas_pos.c	(revision 37807)
@@ -0,0 +1,133 @@
+# include "relastro.h"
+// we are sharing meas positions for all objects which (a) I own and (b) which have unowned detections
+
+# define D_NMEASPOS 10000
+int share_meas_pos (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop) {
+
+  int i;
+  off_t j, k;
+
+  off_t Nmeaspos = 0;
+  off_t NMEASPOS = D_NMEASPOS;
+
+  MeasPos *measpos = NULL;
+  ALLOCATE (measpos, MeasPos, NMEASPOS);
+
+  int myHost = regionHosts->index[REGION_HOST_ID];
+
+  // XXX skip some catalogs based on UserPatch?
+  for (i = 0; i < Ncatalog; i++) {
+    for (j = 0; j < catalog[i].Naverage; j++) {
+
+      // does this object have missing detections (does someone else need it?)
+      if (catalog[i].nOwn_t[j] == catalog[i].average[j].Nmeasure) continue;
+
+      off_t m = catalog[i].average[j].measureOffset;
+      for (k = 0; k < catalog[i].average[j].Nmeasure; k++, m++) {
+	if (!catalog[i].measureT[m].myDet) continue;
+
+	set_meas_pos (&measpos[Nmeaspos], &catalog[i].average[j], &catalog[i].measureT[m]);
+	Nmeaspos ++;
+	CHECK_REALLOCATE (measpos, MeasPos, NMEASPOS, Nmeaspos, D_NMEASPOS);
+      }
+    }
+  }
+
+  // write out the measmag fits table AND write state in some file
+  char *hostname = regionHosts->hosts[myHost].hostname;
+
+  char *posfile = make_filename (CATDIR, hostname, REGION_HOST_ID, "measpos.fits");
+  MeasPosSave (posfile, measpos, Nmeaspos);
+  free (measpos);
+  free (posfile);
+
+  char *syncfile = make_filename (CATDIR, hostname, REGION_HOST_ID, "measpos.sync");
+  update_sync_file (syncfile, nloop);
+  free (syncfile);
+
+  return TRUE;
+}
+
+int slurp_meas_pos (Catalog *catalog, int Ncatalog, RegionHostTable *regionHosts, int nloop) {
+
+  off_t i, k;
+
+  int Nmeaspos = 0;
+  MeasPos *measpos = NULL;
+  ALLOCATE (measpos, MeasPos, 1);
+
+  fprintf (stderr, "grabbing meas object pos from other hosts...\n");
+
+  for (i = 0; i < regionHosts->Nhosts; i++) {
+    if (regionHosts->hosts[i].hostID == REGION_HOST_ID) continue;
+    if (REGION_HOST_ID && !regionHosts->hosts[i].isNeighbor) continue;
+
+    char *syncfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "measpos.sync");
+    check_sync_file (syncfile, nloop);
+    free (syncfile);
+    
+    off_t Nsubset = 0;
+    char *posfile = make_filename (CATDIR, regionHosts->hosts[i].hostname, regionHosts->hosts[i].hostID, "measpos.fits");
+    MeasPos *measposSubset = MeasPosLoad (posfile, &Nsubset);
+    free (posfile);
+
+    // merge_meas_pos reallocs measpos and frees the input measposSubset
+    measpos = merge_meas_pos (measpos, &Nmeaspos, measposSubset, Nsubset);
+  }
+
+  for (i = 0; i < Nmeaspos; i++) {
+    int objID = measpos[i].objID;
+    int catID = measpos[i].catID;
+
+    // set the meas mag
+    int catSeq;
+    off_t objSeq;
+    if (!catID_and_objID_to_seq (catID, objID, &catSeq, &objSeq)) {
+	// XXX what should I do if this does not match?
+	continue;
+    }
+    off_t m = catalog[catSeq].average[objSeq].measureOffset;
+    for (k = 0; k < catalog[catSeq].average[objSeq].Nmeasure; k++, m++) {
+      if (catalog[catSeq].measureT[m].imageID != measpos[i].imageID) continue;
+      catalog[catSeq].measureT[m].R = measpos[i].R;
+      catalog[catSeq].measureT[m].D = measpos[i].D;
+      if (catalog[catSeq].measure) {
+	catalog[catSeq].measure[m].R = measpos[i].R;
+	catalog[catSeq].measure[m].D = measpos[i].D;
+      }
+    }
+  }
+  free (measpos);
+
+  fprintf (stderr, "DONE grabbing meas object pos from other hosts...\n");
+
+  return TRUE;
+}
+
+int set_meas_pos (MeasPos *measpos, Average *average, MeasureTiny *measure) {
+
+  measpos->R       = measure->R;
+  measpos->D       = measure->D;
+  measpos->objID   = average->objID;
+  measpos->catID   = average->catID;
+  measpos->imageID = measure->imageID;
+
+  return TRUE;
+}
+
+MeasPos *merge_meas_pos (MeasPos *target, int *ntarget, MeasPos *source, int Nsource) {
+
+  off_t i;
+
+  REALLOCATE (target, MeasPos, *ntarget + Nsource);
+  for (i = 0; i < Nsource; i++) {
+    off_t n = i + *ntarget;
+    target[n] = source[i];
+  }
+  
+  free (source);
+
+  *ntarget += Nsource;
+  return (target);
+}
+
Index: /trunk/Ohana/src/relastro/src/testparallax.c
===================================================================
--- /trunk/Ohana/src/relastro/src/testparallax.c	(revision 37806)
+++ /trunk/Ohana/src/relastro/src/testparallax.c	(revision 37807)
@@ -49,13 +49,6 @@
 
   /* project coordinates to a plane centered on the object with units of arcsec */
-  coords.crval1 = 0;
-  coords.crval2 = 0;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
+  InitCoords (&coords, "DEC--SIN");
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   // use one point as a local reference
Index: /trunk/Ohana/src/relphot/doc/timeline.txt
===================================================================
--- /trunk/Ohana/src/relphot/doc/timeline.txt	(revision 37807)
+++ /trunk/Ohana/src/relphot/doc/timeline.txt	(revision 37807)
@@ -0,0 +1,36 @@
+
+timeline for processing on PV2 (31 remote hosts):
+
+load images, ready to launch hosts : 160s
+remote hosts begin:
+ * set up mosaic indexes: 388s - 1330s (from client launch time)
+ * loop 0 done: 1086s - 15870s (~ synchronous, thus clients took ~15ksec to load)
+ * loop 7 done: 4443s - 19251s (~ 3400sec total after load is done)
+ * finalized Mcal : 4444s - 19315s
+ * remote hosts done (5.4hr)
+
+remote client update Mcal average times:
+ * load catalog : 5963s (longest one is 11569s)
+ * make tiny : 113s
+ * find images: 576s
+ * set Mrel : 4793s
+ * save catalog: 2531s
+
+elapsed time for update Mcal: 51318s (14.3h)
+
+--> consistent with 3x longest single client times
+
+end of relphot: 70633s (19.6h)
+
+
+relastro timeline:
+on remote hosts:
+  * load catalog data: 247s - 4444s (loading more detections than relphot), 
+    * wall time of the load is NOT sum(load time), because clients are launched before slurp is started
+  * update chips: 23981s for last one 
+  ** end of load + analysis ~ 7hr
+  * analysis takes ~1-2hr
+
+update time ~ 9hr (compare with ~3-4h for relphot) 
+
+*** the I/O is NOT dominant. 
Index: /trunk/Ohana/src/relphot/include/relphot.h
===================================================================
--- /trunk/Ohana/src/relphot/include/relphot.h	(revision 37806)
+++ /trunk/Ohana/src/relphot/include/relphot.h	(revision 37807)
@@ -452,5 +452,7 @@
 int setMrel_catalog_alt (Catalog *catalog, int Nc, int pass, FlatCorrectionTable *flatcorr, SetMrelInfo *results, int Nsecfilt);
 int setMrelAverageExposure (off_t meas, int cat, int pass, FlatCorrectionTable *flatcorr, SetMrelInfo *results, Average *average, AverageTiny *averageT, SecFilt *secfilt, Measure *measure, MeasureTiny *measureT, off_t *found);
-int setMrelAverageStack (off_t meas, int cat, FlatCorrectionTable *flatcorr, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure);
+int setMrelAverageStack (off_t measureOffset, int cat, FlatCorrectionTable *flatcorr, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure, off_t Nmeasure);
+int setMrelAverageForcedWarp (off_t measureOffset, int cat, int pass, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure, off_t *found, off_t Nmeasure);
+
 int setGlobalObjStats (Average *average, Measure *measure);
 
Index: /trunk/Ohana/src/relphot/src/BoundaryTreeOps.c
===================================================================
--- /trunk/Ohana/src/relphot/src/BoundaryTreeOps.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/BoundaryTreeOps.c	(revision 37807)
@@ -51,5 +51,6 @@
   if (!tess) return FALSE;
 
-  status = TessellationPrimaryCellIDs(tess, Ntess, tessID, projID, skycellID, ra, dec);
+  double R = ohana_normalize_angle(ra);
+  status = TessellationPrimaryCellIDs(tess, Ntess, tessID, projID, skycellID, R, dec);
   return status;
 }
Index: /trunk/Ohana/src/relphot/src/ImageOps.c
===================================================================
--- /trunk/Ohana/src/relphot/src/ImageOps.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/ImageOps.c	(revision 37807)
@@ -35,4 +35,12 @@
 static off_t        *imageIdx; // list of index for image IDs 
 
+// QQ // as an alternative, we generate imageSeq, which directly maps imageID -> seq
+// QQ static off_t        *imageSeq; // list of index for image IDs 
+// QQ 
+// QQ // MAX_ID requires 512M to store the image index
+// QQ # define MAX_ID 0x8000000
+// QQ static off_t         minImageID = MAX_ID;
+// QQ static off_t         maxImageID = 0;
+
 // elsewhere, we have loaded a set of catalogs with measures (catalog[cat].measure[meas])
 // each image has N_onImage[ImageIndex] measurements
@@ -95,4 +103,8 @@
 
     TessellationIDsByImageName (&tessID[i], &projectID[i], &skycellID[i], image[i].name);
+
+    // QQ minImageID = MIN(minImageID, image[i].imageID);
+    // QQ maxImageID = MAX(maxImageID, image[i].imageID);
+    // QQ myAssert (image[i].imageID < MAX_ID, "image IDs too large for index memory");
   }
 
@@ -100,4 +112,15 @@
   // XXX does this break the imageID <-> projectID, etc match?
   llsortpair (imageIDs, imageIdx, Nimage);
+
+  // QQ ALLOCATE (imageSeq, off_t, maxImageID + 1);
+  // QQ for (i = 0; i < maxImageID + 1; i++)  {
+  // QQ   imageSeq[i] = -1; // not yet assigned
+  // QQ }
+  // QQ 
+  // QQ for (i = 0; i < Nimage; i++) {
+  // QQ   off_t N = image[i].imageID;
+  // QQ   myAssert (imageSeq[N] == -1, "previously assigned");
+  // QQ   imageSeq[N] = i;
+  // QQ }
 }
 
@@ -167,4 +190,11 @@
 
 off_t getImageByID (off_t ID) {
+
+  // QQ  if (imageSeq) {
+  // QQ    myAssert (ID >= minImageID, "oops");
+  // QQ    myAssert (ID <= maxImageID, "oops");
+  // QQ    off_t N = imageSeq[ID];
+  // QQ    return N;
+  // QQ  }
 
   // we have a pair of vectors (imageIDs, imageIdx) sorted by imageIDs
Index: /trunk/Ohana/src/relphot/src/ImageTable.c
===================================================================
--- /trunk/Ohana/src/relphot/src/ImageTable.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/ImageTable.c	(revision 37807)
@@ -32,4 +32,7 @@
   }
 
+  // XXX do not make the chip match -- we have not loaded the PHU entries (and do not need them here)
+  // BuildChipMatch (image, Nimage);
+
   *nimage = Nimage;
   return image;
Index: /trunk/Ohana/src/relphot/src/MosaicOps.c
===================================================================
--- /trunk/Ohana/src/relphot/src/MosaicOps.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/MosaicOps.c	(revision 37807)
@@ -520,12 +520,7 @@
       m = MosaicToImage[i][j];
 
-      if (!FindMosaicForImage (image, Nimage, m)) {
-	if (VERBOSE2) fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-	continue;
-      }
-
       NX = image[m].NX;
       NY = image[m].NY;
-      XY_to_RD (&R, &D, 0.5*NX, 0.5*NY, &image[m].coords);
+      if (!XY_to_RD (&R, &D, 0.5*NX, 0.5*NY, &image[m].coords)) continue;
       R = ohana_normalize_angle_to_midpoint (R, 180.0);
 
@@ -587,15 +582,9 @@
     }
 
-    strcpy (mosaic[i].coords.ctype, "DEC--TAN");
+    InitCoords (&mosaic[i].coords, "DEC--TAN");
     mosaic[i].coords.crval1 = Rmid;
     mosaic[i].coords.crval2 = Dmid;
-    mosaic[i].coords.crpix1 = 0.0;
-    mosaic[i].coords.crpix2 = 0.0;
     mosaic[i].coords.cdelt1 = 1.0 / 3600.0;
     mosaic[i].coords.cdelt2 = 1.0 / 3600.0;
-    mosaic[i].coords.pc1_1  = 1.0;
-    mosaic[i].coords.pc2_2  = 1.0;
-    mosaic[i].coords.pc1_2  = 0.0;
-    mosaic[i].coords.pc2_1  = 0.0;
 
     mosaic[i].Mcal   = 0.0;
@@ -631,20 +620,41 @@
       NY = image[m].NY;
       dS += hypot (image[m].coords.cdelt1*image[m].coords.pc1_1, image[m].coords.cdelt1*image[m].coords.pc2_1);
-      XY_to_RD (&R, &D, 0.0, 0.0, &image[m].coords);
+
+      OhanaProjection proj = GetProjection (image[m].coords.ctype);
+      if ((proj == PROJ_WRP) && !image[m].coords.mosaic) {
+	XY_to_LM (&R, &D, 0.0, 0.0, &image[m].coords);
+      } else {
+	XY_to_RD (&R, &D, 0.0, 0.0, &image[m].coords);
+      }
       Rmin = MIN (Rmin, R);
       Rmax = MAX (Rmax, R);
       Dmin = MIN (Dmin, D);
       Dmax = MAX (Dmax, D);
-      XY_to_RD (&R, &D, (double) NX, 0.0, &image[m].coords);
+
+      if ((proj == PROJ_WRP) && !image[m].coords.mosaic) {
+	XY_to_LM (&R, &D, (double) NX, 0.0, &image[m].coords);
+      } else {
+	XY_to_RD (&R, &D, (double) NX, 0.0, &image[m].coords);
+      }
       Rmin = MIN (Rmin, R);
       Rmax = MAX (Rmax, R);
       Dmin = MIN (Dmin, D);
       Dmax = MAX (Dmax, D);
-      XY_to_RD (&R, &D, (double) NX, (double) NY, &image[m].coords);
+
+      if ((proj == PROJ_WRP) && !image[m].coords.mosaic) {
+	XY_to_LM (&R, &D, (double) NX, (double) NY, &image[m].coords);
+      } else {
+	XY_to_RD (&R, &D, (double) NX, (double) NY, &image[m].coords);
+      }
       Rmin = MIN (Rmin, R);
       Rmax = MAX (Rmax, R);
       Dmin = MIN (Dmin, D);
       Dmax = MAX (Dmax, D);
-      XY_to_RD (&R, &D, 0.0, (double) NY, &image[m].coords);
+
+      if ((proj == PROJ_WRP) && !image[m].coords.mosaic) {
+	XY_to_LM (&R, &D, 0.0, (double) NY, &image[m].coords);
+      } else {
+	XY_to_RD (&R, &D, 0.0, (double) NY, &image[m].coords);
+      }
       Rmin = MIN (Rmin, R);
       Rmax = MAX (Rmax, R);
@@ -652,4 +662,6 @@
       Dmax = MAX (Dmax, D);
 
+      // XXX : this probably does not handle mosaics at RA = 0,360 well
+
       /* we are using mosaic.Mcal, not image.Mcal. reset image.Mcal */
 
@@ -668,15 +680,11 @@
     }
     dS /= MosaicN_Image[i];
-    strcpy (mosaic[i].coords.ctype, "DEC--TAN");
+
+    InitCoords (&mosaic[i].coords, "DEC--TAN");
     mosaic[i].coords.crval1 = Rmin;
     mosaic[i].coords.crval2 = Dmin;
-    mosaic[i].coords.crpix1 = 0.0;
-    mosaic[i].coords.crpix2 = 0.0;
     mosaic[i].coords.cdelt1 = dS;
     mosaic[i].coords.cdelt2 = dS;
-    mosaic[i].coords.pc1_1  = 1.0;
-    mosaic[i].coords.pc2_2  = 1.0;
-    mosaic[i].coords.pc1_2  = 0.0;
-    mosaic[i].coords.pc2_1  = 0.0;
+
     RD_to_XY (&dX, &dY, Rmax, Dmax, &mosaic[i].coords);
 
Index: /trunk/Ohana/src/relphot/src/assign_images.c
===================================================================
--- /trunk/Ohana/src/relphot/src/assign_images.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/assign_images.c	(revision 37807)
@@ -80,10 +80,4 @@
     if (!strcmp(&image[j].coords.ctype[4], "-DIS")) continue;
 
-    // match the image to its corresponding ASTROMETRIC mosaic (not the same as the Mosaic above)
-    if (!FindMosaicForImage (image, Nimage, j)) {
-      if (VERBOSE2) fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
-    
     // Exclude images with crazy astrometry
     // XXX NOTE : this is gpc1-specific
@@ -123,11 +117,13 @@
 
     if (MOSAIC_ZEROPT) {
-      // use the coords of the associated mosaic to select
+      // use the coords of the associated mosaic to select (only for chips; stacks use their own center)
       Mosaic *mosaic = getMosaicForImage (j); 
-      Rc = mosaic->coords.crval1;
-      Dc = mosaic->coords.crval2;
-      // NOTE : have defined mosaic Rc,Dc to choose the side of 0,360 on which most of the
-      // chips are located.  but, for host assignment, we rationalize to 0.0 - 360.0
-      Rc = ohana_normalize_angle_to_midpoint (Rc, 180.0);
+      if (mosaic) {
+	Rc = mosaic->coords.crval1;
+	Dc = mosaic->coords.crval2;
+	// NOTE : have defined mosaic Rc,Dc to choose the side of 0,360 on which most of the
+	// chips are located.  but, for host assignment, we rationalize to 0.0 - 360.0
+	Rc = ohana_normalize_angle_to_midpoint (Rc, 180.0);
+      }
     }
 
@@ -206,65 +202,4 @@
 }
 
-int calculate_host_image_bounds (RegionHostTable *regionHosts, double Rmid) {
-
-  int i, n;
-  off_t j;
-
-  for (i = 0; i < regionHosts->Nhosts; i++) {
-
-    RegionHostInfo *host = &regionHosts->hosts[i];
-
-    // XXX clear the chip match?
-
-    BuildChipMatch (host->image, host->Nimage);
-
-    double Rmin =  720.0;
-    double Rmax = -360.0;
-    double Dmin =  +90.0;
-    double Dmax = -90.0;
-
-    for (j = 0; j < host->Nimage; j++) {
-
-      Image *image = &host->image[j];
-
-      if (!FindMosaicForImage (host->image, host->Nimage, j)) {
-	fprintf (stderr, "missing astrometry? programming error?\n");
-	abort ();
-      }
-      
-      // define image corners
-      for (n = 0; n < 4; n++) {
-	double Xc, Yc, Rc, Dc;
-	Xc = Xf[n]*image->NX; 
-	Yc = Yf[n]*image->NY;
-	XY_to_RD (&Rc, &Dc, Xc, Yc, &image->coords);
-	Rc = ohana_normalize_angle_to_midpoint (Rc, Rmid);
-      
-	Rmin = MIN (Rmin, Rc);
-	Dmin = MIN (Dmin, Rc);
-	
-	Rmax = MAX (Rmax, Rc);
-	Dmax = MAX (Dmax, Rc);
-      }
-
-    }
-
-    // RminCat, RmaxCat may extended beyond 0.0 - 360.0
-    host->RminCat = Rmin;
-    host->DminCat = Dmin;
-    host->RmaxCat = Rmax;
-    host->DmaxCat = Dmax;
-
-    // regionHosts needs to have the full outer boundary
-    // (so reload_catalogs covers the correct region)
-    regionHosts->Rmin = MIN(Rmin, regionHosts->Rmin);
-    regionHosts->Rmax = MAX(Rmax, regionHosts->Rmax);
-    regionHosts->Dmin = MIN(Dmin, regionHosts->Dmin);
-    regionHosts->Dmax = MAX(Dmax, regionHosts->Dmax);
-  }
-  return TRUE;
-}
-
-
 // Rc is in range 0.0 - 360.0, hosts Rmin,Rmax in range 0.0 - 360.0
 int find_host_for_coords (RegionHostTable *regionHosts, double Rc, double Dc) {
Index: /trunk/Ohana/src/relphot/src/load_images.c
===================================================================
--- /trunk/Ohana/src/relphot/src/load_images.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/load_images.c	(revision 37807)
@@ -27,4 +27,16 @@
   }
   MARKTIME("read image table: %f sec\n", dtime);
+
+  BuildChipMatch (image, Nimage);
+  MARKTIME("build chip match for %d images: %f sec\n", (int) Nimage, dtime);
+
+  char mapfile[DVO_MAX_PATH];
+  snprintf (mapfile, DVO_MAX_PATH, "%s/AstroMap.fits", CATDIR);
+  AstromOffsetTable *table = AstromOffsetMapLoad (mapfile, VERBOSE);
+
+  // assign images.coords.offsetMap -> table->map[i]
+  if (table) {
+    AstromOffsetTableMatchChips (image, Nimage, table);
+  }
 
   // allocate and init an array to identify the images included in the subset
Index: /trunk/Ohana/src/relphot/src/reload_catalogs.c
===================================================================
--- /trunk/Ohana/src/relphot/src/reload_catalogs.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/reload_catalogs.c	(revision 37807)
@@ -128,4 +128,6 @@
 }
 
+int reload_catalog_parallel_group (HostTableGroup *group, SkyList *sky, char *imageFile);
+
 // XXX Image to Image Subset
 int reload_catalogs_parallel (SkyList *sky) {
@@ -165,15 +167,28 @@
     free (BOUNDARY_TREE);
     BOUNDARY_TREE = tmppath;
-    
-  }
+  }
+
+  int Ngroups;
+  HostTableGroup *groups = HostTableGroups (table, &Ngroups);
+  // split the table into Ngroups, each with a unique set of machines (this avoids overloading the remote host machines)
+
+  int i;
+  for (i = 0; i < Ngroups; i++) {
+    // update only a group of unique machines at a time
+    reload_catalog_parallel_group (&groups[i], sky, imageFile);
+  }
+  return TRUE;
+}
+
+int reload_catalog_parallel_group (HostTableGroup *group, SkyList *sky, char *imageFile) {
 
   int i, j;
-  for (i = 0; i < table->Nhosts; i++) {
-
-    if (sky->Nregions < table->Nhosts) {
+  for (i = 0; i < group->Nhosts; i++) {
+
+    if (sky->Nregions < group->Nhosts) {
       // do any of the regions want this host?
       int wantThisHost = FALSE;
       for (j = 0; j < sky->Nregions; j++) {
-	if (HostTableTestHost (sky->regions[j], table->hosts[i].hostID)) {
+	if (HostTableTestHost (sky->regions[j], group->hosts[i][0].hostID)) {
 	  wantThisHost = TRUE;
 	  break;
@@ -181,5 +196,5 @@
       }
       if (!wantThisHost) {
-	// fprintf (stderr, "skip host %s\n", table->hosts[i].hostname);
+	// fprintf (stderr, "skip host %s\n", group->hosts[i][0].hostname);
 	continue;
       }
@@ -187,11 +202,11 @@
 
     // ensure that the paths are absolute path names
-    char *tmppath = abspath (table->hosts[i].pathname, DVO_MAX_PATH);
-    free (table->hosts[i].pathname);
-    table->hosts[i].pathname = tmppath;
+    char *tmppath = abspath (group->hosts[i][0].pathname, DVO_MAX_PATH);
+    free (group->hosts[i][0].pathname);
+    group->hosts[i][0].pathname = tmppath;
 
     char command[1024];
     snprintf (command, 1024, "relphot_client %s -update-catalogs %s -hostID %d -D CATDIR %s -hostdir %s -region %f %f %f %f -statmode %s -D CAMERA %s -D STAR_TOOFEW %d -minerror %f", 
-	      PhotcodeList, imageFile, table->hosts[i].hostID, CATDIR, table->hosts[i].pathname, UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax, STATMODE, CAMERA, STAR_TOOFEW, MIN_ERROR);
+	      PhotcodeList, imageFile, group->hosts[i][0].hostID, CATDIR, group->hosts[i][0].pathname, UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax, STATMODE, CAMERA, STAR_TOOFEW, MIN_ERROR);
 
     // options & configs which affect relphot_client -update-catalogs
@@ -238,10 +253,10 @@
       // launch the job on the remote machine (no handshake)
       int errorInfo = 0;
-      int pid = rconnect ("ssh", table->hosts[i].hostname, command, table->hosts[i].stdio, &errorInfo, FALSE);
+      int pid = rconnect ("ssh", group->hosts[i][0].hostname, command, group->hosts[i][0].stdio, &errorInfo, FALSE);
       if (!pid) {
-	if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", table->hosts[i].hostname, errorInfo);
+	if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", group->hosts[i][0].hostname, errorInfo);
 	exit (1);
       }
-      table->hosts[i].pid = pid; // save for future reference
+      group->hosts[i][0].pid = pid; // save for future reference
     }
   }
@@ -252,5 +267,5 @@
   } 
   if (!PARALLEL_MANUAL && !PARALLEL_SERIAL) {
-    int status = HostTableWaitJobsGetIO (table, __FILE__, __LINE__, VERBOSE);
+    int status = HostTableGroupWaitJobsGetIO (group, __FILE__, __LINE__, VERBOSE);
     if (!status) {
       fprintf (stderr, "at least one remote client job failed to load data, exiting\n");
Index: /trunk/Ohana/src/relphot/src/relphot.c
===================================================================
--- /trunk/Ohana/src/relphot/src/relphot.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/relphot.c	(revision 37807)
@@ -9,4 +9,5 @@
   switch (mode) {
     case UPDATE_IMAGES:
+      // IF CALLED WITH NLOOP == 0, DOES NOT LOAD bcatalog, just loads image table and generates ImageSubset.dat
       relphot_images ();
       exit (0);
@@ -14,4 +15,5 @@
     case UPDATE_AVERAGES:
       // take the current set of detections and set the mean magnitudes
+      // DOES NOT LOAD THE IMAGE TABLE
       relphot_objects (0, NULL);
       exit (0);
@@ -43,2 +45,8 @@
   exit (1);
 }
+
+/***
+
+    I would like to merge the functionality of relphot_images (Nloop == 0), relphot_objects, and apply_offsets
+    
+***/
Index: /trunk/Ohana/src/relphot/src/relphot_objects.c
===================================================================
--- /trunk/Ohana/src/relphot/src/relphot_objects.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/relphot_objects.c	(revision 37807)
@@ -36,5 +36,4 @@
     if (!HostTableTestHost(skylist[0].regions[i], hostID)) continue;
 
-    // set up the basic catalog info
     // set up the basic catalog info
     char hostfile[1024];
Index: /trunk/Ohana/src/relphot/src/relphot_parallel_regions.c
===================================================================
--- /trunk/Ohana/src/relphot/src/relphot_parallel_regions.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/relphot_parallel_regions.c	(revision 37807)
@@ -43,4 +43,10 @@
   MARKTIME("-- load image data: %f sec\n", dtime);
 
+  // save Images.dat using the copied structure
+  if (UPDATE_CATFORMAT) {
+    // ensure the db format is updated
+    db.format = dvo_catalog_catformat (UPDATE_CATFORMAT);
+  }
+
   /* assign the images to the different region hosts */
   if (!assign_images (&db, regionHosts)) Shutdown ("error assigning images to region hosts");
@@ -49,4 +55,5 @@
   /* launch processing on the parallel region hosts */
   if (!launch_region_hosts (regionHosts)) Shutdown ("error launching region hosts");
+  MARKTIME ("finished relphot -parallel-regions: %f sec total\n", dtime);
 
   // retrieve updated image parameters from the remote hosts (also set Image.mcal)
Index: /trunk/Ohana/src/relphot/src/select_images.c
===================================================================
--- /trunk/Ohana/src/relphot/src/select_images.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/select_images.c	(revision 37807)
@@ -36,9 +36,6 @@
 
   // the comparison is made in the catalog local projection. below we set crval1,2
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
+  InitCoords (&tcoords, "DEC--TAN");
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1  = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2  = tcoords.pc2_1 = 0.0;
-  strcpy (tcoords.ctype, "DEC--TAN");
 
   double RminSkyRegion = region[0].Rmin;
@@ -93,6 +90,4 @@
 
   if (VERBOSE) fprintf (stderr, "finding images\n");
-  BuildChipMatch (timage, Ntimage);
-  MARKTIME("build chip match for %d images: %f sec\n", (int) Ntimage, dtime);
 
   D_NIMAGE = 6000;
@@ -107,4 +102,7 @@
       
     if (!(i % 300000)) fprintf (stderr, ".");
+
+    // XXX I am just going for force this for the moment:
+    goto found_it;
 
     /* exclude images by photcode */
@@ -123,10 +121,4 @@
     }
     
-    // this adds 1.3 sec for 3M images
-    if (!FindMosaicForImage (timage, Ntimage, i)) {
-      if (VERBOSE2) fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
-
     /* define image corners - note the DIS images (mosaic phu) are special */
     if (!strcmp(&timage[i].coords.ctype[4], "-DIS")) {
@@ -405,12 +397,6 @@
 
   // the comparison is made in the catalog local projection. below we set crval1,2
-  tcoords.crpix1 = tcoords.crpix2 = 0.0;
+  InitCoords (&tcoords, "DEC--TAN");
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1  = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2  = tcoords.pc2_1 = 0.0;
-
-  tcoords.Npolyterms = 0;
-  memset (tcoords.polyterms, 0, 14*sizeof(float));
-  strcpy (tcoords.ctype, "DEC--TAN");
 
   /* compare with each region file */
Index: /trunk/Ohana/src/relphot/src/setMrelCatalog.c
===================================================================
--- /trunk/Ohana/src/relphot/src/setMrelCatalog.c	(revision 37806)
+++ /trunk/Ohana/src/relphot/src/setMrelCatalog.c	(revision 37807)
@@ -36,6 +36,4 @@
 }
 
-int setMrelAverageForcedWarp (off_t measureOffset, int cat, int pass, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure, off_t *found);
-
 int setMrel_catalog_alt (Catalog *catalog, int Nc, int pass, FlatCorrectionTable *flatcorr, SetMrelInfo *results, int Nsecfilt) {
 
@@ -60,9 +58,10 @@
     Average *average = catalog[Nc].average ? &catalog[Nc].average[j] : NULL;
     Measure *measure = catalog[Nc].measure ? &catalog[Nc].measure[m] : NULL;
-    setMrelAverageExposure (m, Nc, pass, flatcorr, results, average, &catalog[Nc].averageT[j], &catalog[Nc].secfilt[j*Nsecfilt], measure, &catalog[Nc].measureT[m], &catalog[Nc].found_t[Nsecfilt*j]);
+    SecFilt *secfilt = catalog[Nc].secfilt ? &catalog[Nc].secfilt[j*Nsecfilt] : NULL;
+    setMrelAverageExposure (m, Nc, pass, flatcorr, results, average, &catalog[Nc].averageT[j], secfilt, measure, &catalog[Nc].measureT[m], &catalog[Nc].found_t[Nsecfilt*j]);
 
     // only apply Stack operation on setMrelFinal in first pass 
     if (isSetMrelFinal && (pass == 0)) {
-      setMrelAverageStack (m, Nc, flatcorr, results, &catalog[Nc].average[j], &catalog[Nc].secfilt[j*Nsecfilt], &catalog[Nc].measure[m]);
+      setMrelAverageStack (m, Nc, flatcorr, results, average, secfilt, &catalog[Nc].measure[m], catalog[Nc].Nmeasure);
       setGlobalObjStats (&catalog[Nc].average[j], &catalog[Nc].measure[m]);
     }
@@ -70,6 +69,6 @@
     // only measure force-warp mean values if issetMrelFinal (make it optional?)
     if (isSetMrelFinal) {
-      setMrelAverageForcedWarp (m, Nc, pass, results, &catalog[Nc].average[j], &catalog[Nc].secfilt[j*Nsecfilt], &catalog[Nc].measure[m], &catalog[Nc].foundWarp_t[Nsecfilt*j]);
-      setGlobalObjStats (&catalog[Nc].average[j], &catalog[Nc].measure[m]);
+      setMrelAverageForcedWarp (m, Nc, pass, results, average, secfilt, measure, &catalog[Nc].foundWarp_t[Nsecfilt*j], catalog[Nc].Nmeasure);
+      setGlobalObjStats (average, measure);
     }
   }
@@ -456,5 +455,5 @@
 // 2) select the BEST detections per filter (regardless of PRIMARY)
 // 3) apply the zero point and AB->Jy transformations
-int setMrelAverageStack (off_t measureOffset, int cat, FlatCorrectionTable *flatcorr, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure) {
+int setMrelAverageStack (off_t measureOffset, int cat, FlatCorrectionTable *flatcorr, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure, off_t Nmeasure) {
 
   off_t k;
@@ -496,4 +495,7 @@
     int Nstack = 0; // number for this photcode
     int NstackDet = 0; // number for this photcode
+
+    secfilt[Nsec].stackBestOff = -1;
+    secfilt[Nsec].stackPrmryOff = -1;
 
     off_t meas = measureOffset;
@@ -526,4 +528,5 @@
 	measure[k].dbFlags |= ID_MEAS_STACK_PRIMARY;
 	secfilt[Nsec].stackPrmryOff = meas;
+	myAssert (secfilt[Nsec].stackPrmryOff <= Nmeasure, "stackPrmryOff out of range");
       }
 
@@ -637,4 +640,5 @@
 
     secfilt[Nsec].stackBestOff = k + measureOffset;
+    myAssert (secfilt[Nsec].stackBestOff <= Nmeasure, "stackBestOff out of range");
 
     secfilt[Nsec].Nstack    = Nstack;
@@ -669,5 +673,5 @@
 // somewhat simplified relative to chip-photometry:
 // * no grid, no mosaic, no 2MASS, no SYNTH, no Ubercal, no flatcorr
-int setMrelAverageForcedWarp (off_t measureOffset, int cat, int pass, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure, off_t *found) {
+int setMrelAverageForcedWarp (off_t measureOffset, int cat, int pass, SetMrelInfo *results, Average *average, SecFilt *secfilt, Measure *measure, off_t *found, off_t Nmeasure) {
 
   off_t k;
@@ -713,4 +717,6 @@
     // if we cannot make that association, skip this set of warps
     int stkTessID, stkProjID, stkSkycellID;
+
+    myAssert (secfilt[Nsec].stackBestOff <= Nmeasure, "stackBestOff out of range");
     if (!FindImageSkycellID (secfilt[Nsec].stackBestOff, cat, &stkTessID, &stkProjID, &stkSkycellID)) continue;
 
Index: /trunk/Ohana/src/uniphot/Makefile
===================================================================
--- /trunk/Ohana/src/uniphot/Makefile	(revision 37806)
+++ /trunk/Ohana/src/uniphot/Makefile	(revision 37807)
@@ -1,3 +1,3 @@
-default: uniphot setphot setphot_client setfwhm setposangle setposangle_client setastrom setastrom_client fiximids fiximids_client fixstkids fixstkids_client ckids ckids_client
+default: uniphot setphot setphot_client setfwhm setposangle setposangle_client setastrom setastrom_client setgalmodel setgalmodel_client fiximids fiximids_client fixstkids fixstkids_client ckids ckids_client
 help:
 @echo "make options: uniphot setphot setfwhm default help install default (uniphot setphot setfwhm)"
@@ -25,4 +25,6 @@
 setastrom: $(BIN)/setastrom.$(ARCH)
 setastrom_client: $(BIN)/setastrom_client.$(ARCH)
+setgalmodel: $(BIN)/setgalmodel.$(ARCH)
+setgalmodel_client: $(BIN)/setgalmodel_client.$(ARCH)
 fiximids: $(BIN)/fiximids.$(ARCH)
 fiximids_client: $(BIN)/fiximids_client.$(ARCH)
@@ -32,5 +34,5 @@
 ckids_client: $(BIN)/ckids_client.$(ARCH)
 
-install: $(DESTBIN)/uniphot $(DESTBIN)/setfwhm $(DESTBIN)/setphot $(DESTBIN)/setphot_client $(DESTBIN)/setposangle $(DESTBIN)/setposangle_client $(DESTBIN)/setastrom $(DESTBIN)/setastrom_client $(DESTBIN)/fiximids $(DESTBIN)/fiximids_client $(DESTBIN)/fixstkids $(DESTBIN)/fixstkids_client $(DESTBIN)/ckids $(DESTBIN)/ckids_client
+install: $(DESTBIN)/uniphot $(DESTBIN)/setfwhm $(DESTBIN)/setphot $(DESTBIN)/setphot_client $(DESTBIN)/setposangle $(DESTBIN)/setposangle_client $(DESTBIN)/setastrom $(DESTBIN)/setastrom_client $(DESTBIN)/setgalmodel $(DESTBIN)/setgalmodel_client $(DESTBIN)/fiximids $(DESTBIN)/fiximids_client $(DESTBIN)/fixstkids $(DESTBIN)/fixstkids_client $(DESTBIN)/ckids $(DESTBIN)/ckids_client
 
 UNIPHOT = \
@@ -110,4 +112,6 @@
 $(SRC)/setastrom.$(ARCH).o	    \
 $(SRC)/initialize_setastrom.$(ARCH).o \
+$(SRC)/dcr_correction.$(ARCH).o \
+$(SRC)/kh_correction.$(ARCH).o \
 $(SRC)/astrom_correction.$(ARCH).o \
 $(SRC)/update_dvo_setastrom.$(ARCH).o \
@@ -123,4 +127,6 @@
 $(SRC)/update_dvo_setastrom.$(ARCH).o \
 $(SRC)/update_catalog_setastrom.$(ARCH).o \
+$(SRC)/dcr_correction.$(ARCH).o \
+$(SRC)/kh_correction.$(ARCH).o \
 $(SRC)/astrom_correction.$(ARCH).o \
 $(SRC)/initialize_setastrom.$(ARCH).o
@@ -128,4 +134,26 @@
 $(SETASTROM_CLIENT): $(INC)/setastrom.h
 $(BIN)/setastrom_client.$(ARCH): $(SETASTROM_CLIENT)
+
+SETGALMODEL = \
+$(SRC)/setgalmodel.$(ARCH).o	    \
+$(SRC)/initialize_setgalmodel.$(ARCH).o \
+$(SRC)/galmodel_correction.$(ARCH).o \
+$(SRC)/update_dvo_setgalmodel.$(ARCH).o \
+$(SRC)/update_catalog_setgalmodel.$(ARCH).o \
+$(SRC)/SetSignals.$(ARCH).o	    \
+$(SRC)/Shutdown.$(ARCH).o	    
+
+$(SETGALMODEL): $(INC)/setgalmodel.h
+$(BIN)/setgalmodel.$(ARCH): $(SETGALMODEL)
+
+SETGALMODEL_CLIENT = \
+$(SRC)/setgalmodel_client.$(ARCH).o	    \
+$(SRC)/update_dvo_setgalmodel.$(ARCH).o \
+$(SRC)/update_catalog_setgalmodel.$(ARCH).o \
+$(SRC)/galmodel_correction.$(ARCH).o \
+$(SRC)/initialize_setgalmodel.$(ARCH).o
+
+$(SETGALMODEL_CLIENT): $(INC)/setgalmodel.h
+$(BIN)/setgalmodel_client.$(ARCH): $(SETGALMODEL_CLIENT)
 
 SETFWHM =                           \
Index: /trunk/Ohana/src/uniphot/include/setastrom.h
===================================================================
--- /trunk/Ohana/src/uniphot/include/setastrom.h	(revision 37806)
+++ /trunk/Ohana/src/uniphot/include/setastrom.h	(revision 37807)
@@ -25,4 +25,10 @@
 char        *SINGLE_CPT;
 
+char        *KH_FILE;
+char        *DCR_FILE;
+
+int          KH_RESET;
+int          DCR_RESET;
+
 SkyRegion    UserPatch;
 
@@ -46,11 +52,15 @@
 // int           SetSignals                      PROTO((void));
 
-int           update_dvo_setastrom            PROTO((char *corrfile));
-int           update_dvo_setastrom_parallel   PROTO((SkyTable *sky, char *corrfile));
+int           update_dvo_setastrom            PROTO(());
+int           update_dvo_setastrom_parallel   PROTO((SkyTable *sky));
 
 int           update_catalog_setastrom        PROTO((Catalog *catalog));
 
 // spline correction functions
-int load_astrom_correction (char *filename);
-int get_astrom_correction (int sub, int chip, double *dX, double *dY, float Minst);
 double spline_apply_dbl (double *x, double *y, double *y2, int N, double X);
+
+int load_kh_correction (char *filename);
+int get_kh_correction (int sub, int chip, double *dX, double *dY, float Minst);
+
+int load_dcr_correction (char *filename);
+int get_dcr_correction (int filter, double *dX, double *dY, float Color);
Index: /trunk/Ohana/src/uniphot/include/setgalmodel.h
===================================================================
--- /trunk/Ohana/src/uniphot/include/setgalmodel.h	(revision 37807)
+++ /trunk/Ohana/src/uniphot/include/setgalmodel.h	(revision 37807)
@@ -0,0 +1,44 @@
+# include <ohana.h>
+# include <dvo.h>
+# include <signal.h>
+
+/* global variables set in parameter file */
+# define DVO_MAX_PATH 1024
+char         ImageCat[DVO_MAX_PATH];
+char        *CATDIR;
+int          HOST_ID;
+char        *HOSTDIR;
+int          VERBOSE;
+int          TESTING;
+int          UPDATE;
+int          PARALLEL;
+int          PARALLEL_MANUAL;
+int          PARALLEL_SERIAL;
+char        *UPDATE_CATFORMAT;
+char        *SINGLE_CPT;
+
+SkyRegion    UserPatch;
+
+/***** prototypes ****/
+int           main                            PROTO((int argc, char **argv));
+
+void          GetConfig                       PROTO((char *config, char *field, char *format, int N, void *ptr));
+void          ConfigInit                      PROTO((int *argc, char **argv));
+
+void          usage_setgalmodel                 PROTO(());
+void          initialize_setgalmodel            PROTO((int argc, char **argv));
+int           args_setgalmodel                  PROTO((int argc, char **argv));
+
+void          usage_setgalmodel_client          PROTO(());
+void          initialize_setgalmodel_client     PROTO((int argc, char **argv));
+int           args_setgalmodel_client           PROTO((int argc, char **argv));
+
+// int           Shutdown                        PROTO((char *format, ...) OHANA_FORMAT(printf, 1, 2) );
+// void          TrapSignal                      PROTO((int sig));
+// void          SetProtect                      PROTO((int mode));
+// int           SetSignals                      PROTO((void));
+
+int           update_dvo_setgalmodel            PROTO((void));
+int           update_dvo_setgalmodel_parallel   PROTO((SkyTable *sky));
+
+int           update_catalog_setgalmodel        PROTO((Catalog *catalog));
Index: /trunk/Ohana/src/uniphot/src/astrom_correction.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/astrom_correction.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/astrom_correction.c	(revision 37807)
@@ -2,146 +2,6 @@
 
 // astrometry correction functions:
-// load the correction set from a file
-
-// the correction is saved as a set of splines, one per file extension
-// extensions have names of the form XYnn.Dx.Td
-
-// where nn = 00 - 77, the chip number 
-// x is X or Y, the correction direction (dir)
-// d is 0 or 1, pre or post camera correction (sub)
-
-// these are stored in an N-D array of (Spline *) splineset[sub][dir][chip]
-
-# define N_SUB 2
-# define N_DIR 2
-# define N_CHIP 78
-// note that we are allocating pointers for XY00 - XY77, but only the octal elements are set (and not the 00,07,70,77 ones)
-
-Spline ****splineset;
-
-int load_astrom_correction (char *filename) {
-
-  int Ncol;
-  off_t Nrow;
-
-  char type[16];
-  char extname[80];
-
-  Header header;
-  Header theader;
-  FTable ftable;
-
-  /* open file for input */
-  FILE *f = fopen (filename, "r");
-  if (f == (FILE *) NULL) {
-    gprint (GP_ERR, "can't open file for read : %s\n", filename);
-    return FALSE;
-  }
-
-  // read the PHU header
-  if (!gfits_load_header (f, &header)) return (FALSE);
-  
-  int Nbytes = gfits_data_size (&header);
-  fseeko (f, Nbytes, SEEK_CUR);
-  // XXX gfits_free_header (&header);
-
-  ftable.header = &theader;
-
-  int sub, dir, chip;
-
-  // we have a N-D array of splines:
-  // splineset[sub][dir][chip]
-
-  ALLOCATE (splineset, Spline ***, N_SUB);
-  for (sub = 0; sub < N_SUB; sub++) {
-    ALLOCATE (splineset[sub], Spline **, N_DIR);
-    for (dir = 0; dir < N_DIR; dir++) {
-      ALLOCATE (splineset[sub][dir], Spline *, N_CHIP);
-      for (chip = 0; chip < N_CHIP; chip ++) {
-	splineset[sub][dir][chip] = NULL;
-      }
-    }
-  }
-
-  while (TRUE) {
-    // load data for this header : if not found, assume we hit the end of the file
-    if (!gfits_load_header (f, &theader)) break;
-    
-    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) return (FALSE);
-    
-    if (!gfits_scan (&theader, "EXTNAME", "%s", 1, extname)) return (FALSE);
-
-    // figure out the chip ID, diretion, and sequence from the EXTNAME
-
-    // EXTNAME is of the form XY%d%d.D%s.T%s where 
-    char chipname[3];
-    chipname[0] = extname[2];
-    chipname[1] = extname[3];
-    chipname[2] = 0;
-    int chip = atoi(chipname);
-    myAssert (chip >   0, "error in extname %s not(chip >   0)\n", extname);
-    myAssert (chip <  77, "error in extname %s not(chip <  77)\n", extname);
-    myAssert (chip !=  7, "error in extname %s not(chip !=  7)\n", extname);
-    myAssert (chip != 70, "error in extname %s not(chip != 70)\n", extname);
-
-    int dir = -1;
-    if (extname[6] == 'X') {
-      dir = 0;
-    }
-    if (extname[6] == 'Y') {
-      dir = 1;
-    }
-    myAssert (dir != -1, "error in extname %s (dir)\n", extname);
-
-    int sub = -1;
-    if (extname[9] == '0') {
-      sub = 0;
-    }
-    if (extname[9] == '1') {
-      sub = 1;
-    }
-    myAssert (sub != -1, "error in extname %s (sub)\n", extname);
-
-    // need to create and assign to flat-field correction
-    double *xk = gfits_get_bintable_column_data (&theader, &ftable, "X_KNOT", type, &Nrow, &Ncol);
-    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
-    
-    // need to create and assign to flat-field correction
-    double *yk = gfits_get_bintable_column_data (&theader, &ftable, "Y_KNOT", type, &Nrow, &Ncol);
-    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
-    
-    // need to create and assign to flat-field correction
-    double *y2 = gfits_get_bintable_column_data (&theader, &ftable, "DY2_DX", type, &Nrow, &Ncol);
-    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
-    
-    ALLOCATE (splineset[sub][dir][chip], Spline, 1);
-    splineset[sub][dir][chip]->xk = xk;
-    splineset[sub][dir][chip]->yk = yk;
-    splineset[sub][dir][chip]->y2 = y2;
-    splineset[sub][dir][chip]->Nknots = Nrow;
-  }
-  return (TRUE);
-}
-
-int get_astrom_correction (int sub, int chip, double *dX, double *dY, float Minst) {
-
-  Spline *spline = NULL;
-
-  *dX = 0.0;
-  *dY = 0.0;
-
-  // XXX need to include some limits on Minst?
-
-  spline = splineset[sub][0][chip];
-  if (spline) {
-    *dX = spline_apply_dbl (spline->xk, spline->yk, spline->y2, spline->Nknots, Minst);
-  }
-
-  spline = splineset[sub][1][chip];
-  if (spline) {
-    *dY = spline_apply_dbl (spline->xk, spline->yk, spline->y2, spline->Nknots, Minst);
-  }
-  return (TRUE);
-}
+// KH correction
+// DCR correction
 
 /* evaluate spline for x, y, y2 at X */
Index: /trunk/Ohana/src/uniphot/src/dcr_correction.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/dcr_correction.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/dcr_correction.c	(revision 37807)
@@ -0,0 +1,138 @@
+# include "setastrom.h"
+
+// DCR correction functions:
+// load the correction set from a file
+
+// the correction is saved as a set of splines, one per filter and direction
+// extensions have names of the form [grizy].[xy].v0
+
+// [grizy] is the filter
+// [xy] is the correction direction (dir)
+
+// these are stored in an N-D array of (Spline *) splineset[dir][filter]
+
+# define N_DIR 2
+# define N_FILTER 5
+
+static Spline ***splineset;
+
+static float MaxDCR[] = {+0.030, +0.005, +0.005, +0.010, +0.015};
+static float MinDCR[] = {-0.030, -0.005, -0.010, -0.015, -0.010};
+
+int load_dcr_correction (char *filename) {
+
+  int Ncol;
+  off_t Nrow;
+
+  char type[16];
+  char extname[80];
+
+  Header header;
+  Header theader;
+  FTable ftable;
+
+  /* open file for input */
+  FILE *f = fopen (filename, "r");
+  if (f == (FILE *) NULL) {
+    gprint (GP_ERR, "can't open file for read : %s\n", filename);
+    return FALSE;
+  }
+
+  // read the PHU header
+  if (!gfits_load_header (f, &header)) return (FALSE);
+  
+  // skip the PHU matrix
+  int Nbytes = gfits_data_size (&header);
+  fseeko (f, Nbytes, SEEK_CUR);
+
+  ftable.header = &theader;
+
+  int dir, filter;
+
+  // we have a N-D array of splines:
+  // splineset[dir][filter]
+
+  ALLOCATE (splineset, Spline **, N_DIR);
+  for (dir = 0; dir < N_DIR; dir++) {
+    ALLOCATE (splineset[dir], Spline *, N_FILTER);
+    for (filter = 0; filter < N_FILTER; filter ++) {
+      splineset[dir][filter] = NULL;
+    }
+  }
+
+  while (TRUE) {
+    // load data for this header : if not found, assume we hit the end of the file
+    if (!gfits_load_header (f, &theader)) break;
+    
+    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) return (FALSE);
+    
+    if (!gfits_scan (&theader, "EXTNAME", "%s", 1, extname)) return (FALSE);
+
+    // figure out the filter and direction from the EXTNAME
+
+    // EXTNAME is of the form [grizy].[xy].v0 where 
+    char filtname = extname[0];
+    int filtnum = -1;
+    switch (filtname) {
+      case 'g':	filtnum = 0; break;
+      case 'r':	filtnum = 1; break;
+      case 'i':	filtnum = 2; break;
+      case 'z':	filtnum = 3; break;
+      case 'y':	filtnum = 4; break;
+      default:
+	fprintf (stderr, "invalid filter in ext: %s\n", extname);
+	abort();
+    }
+
+    int dir = -1;
+    if (extname[2] == 'x') {
+      dir = 0;
+    }
+    if (extname[2] == 'y') {
+      dir = 1;
+    }
+    myAssert (dir != -1, "error in extname %s (dir)\n", extname);
+
+    // load the spline elements:
+    double *xk = gfits_get_bintable_column_data (&theader, &ftable, "X_KNOT", type, &Nrow, &Ncol);
+    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
+    
+    double *yk = gfits_get_bintable_column_data (&theader, &ftable, "Y_KNOT", type, &Nrow, &Ncol);
+    myAssert (!strcmp(type, "double"), "error reading Y_KNOT");
+    
+    double *y2 = gfits_get_bintable_column_data (&theader, &ftable, "DY2_DX", type, &Nrow, &Ncol);
+    myAssert (!strcmp(type, "double"), "error reading DY2_DX");
+    
+    ALLOCATE (splineset[dir][filtnum], Spline, 1);
+    splineset[dir][filtnum]->xk = xk;
+    splineset[dir][filtnum]->yk = yk;
+    splineset[dir][filtnum]->y2 = y2;
+    splineset[dir][filtnum]->Nknots = Nrow;
+  }
+  return (TRUE);
+}
+
+int get_dcr_correction (int filter, double *dX, double *dY, float Color) {
+
+  Spline *spline = NULL;
+
+  *dX = 0.0;
+  *dY = 0.0;
+
+  // XXX need to include some limits on Minst?
+
+  if (0) {
+    spline = splineset[0][filter];
+    if (spline) {
+      *dX = spline_apply_dbl (spline->xk, spline->yk, spline->y2, spline->Nknots, Color);
+    }
+  }
+
+  spline = splineset[1][filter];
+  if (spline) {
+    *dY = spline_apply_dbl (spline->xk, spline->yk, spline->y2, spline->Nknots, Color);
+    *dY = MIN(MaxDCR[filter], *dY);
+    *dY = MAX(MinDCR[filter], *dY);
+  }
+  return (TRUE);
+}
Index: /trunk/Ohana/src/uniphot/src/find_image_sgroups.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/find_image_sgroups.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/find_image_sgroups.c	(revision 37807)
@@ -18,9 +18,5 @@
   }
 
-  coords.crpix1 = coords.crpix2 = 0.0;
-  coords.cdelt1 = coords.cdelt2 = 1.0; /* scale is degrees, radius in degrees */
-  coords.pc1_1 = coords.pc2_2 = 1.0;
-  coords.pc1_2 = coords.pc2_1 = 0.0;
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
   
   Ngroup = 0;
@@ -41,10 +37,4 @@
     // NOCAL above is used to mark images which do not match the photcode (including the DIS)
     // if (!strcmp(&image[i].coords.ctype[4], "-DIS")) continue;
-
-    // this adds 1.3 sec for 3M images
-    if (!FindMosaicForImage (image, Nimage, i)) {
-      fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", i);
-      continue;
-    }
 
     /* define image center - note the DIS images (mosaic phu) are special */
@@ -83,10 +73,4 @@
       // if (!strcmp(&image[j].coords.ctype[4], "-DIS")) continue;
 
-      // this adds 1.3 sec for 3M images
-      if (!FindMosaicForImage (image, Nimage, j)) {
-	  fprintf (stderr, "cannot find mosaic for "OFF_T_FMT"\n", j);
-	  continue;
-      }
-
       /* project image center to local coords, check radius */
       if (!strcmp(&image[j].coords.ctype[4], "-DIS")) {
Index: /trunk/Ohana/src/uniphot/src/galmodel_correction.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/galmodel_correction.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/galmodel_correction.c	(revision 37807)
@@ -0,0 +1,6 @@
+# include "setgalmodel.h"
+
+int galmodel_correction () {
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/uniphot/src/initialize_setastrom.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/initialize_setastrom.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/initialize_setastrom.c	(revision 37807)
@@ -2,6 +2,8 @@
 
 void usage_setastrom () {
-    fprintf (stderr, "USAGE: setastrom (corrections) [options]\n");
+    fprintf (stderr, "USAGE: setastrom [options]\n");
     fprintf (stderr, "  options:\n");
+    fprintf (stderr, "    -DCR (file) : supply DCR correction splines\n");
+    fprintf (stderr, "    -KH (file) : supply Koppenhoefer correction splines\n");
     fprintf (stderr, "    -v : verbose mode\n");
     fprintf (stderr, "    -region Rmin Rmax Dmin Dmax\n");
@@ -49,4 +51,30 @@
   int N;
 
+  KH_FILE = NULL;
+  if ((N = get_argument (argc, argv, "-KH"))) {
+    remove_argument (N, &argc, argv);
+    char *tmpfile = strcreate (argv[N]);
+    KH_FILE = abspath (tmpfile, DVO_MAX_PATH);
+    remove_argument (N, &argc, argv);
+  }
+  KH_RESET = FALSE;
+  if ((N = get_argument (argc, argv, "-KH-reset"))) {
+    remove_argument (N, &argc, argv);
+    KH_RESET = TRUE;
+  }
+
+  DCR_FILE = NULL;
+  if ((N = get_argument (argc, argv, "-DCR"))) {
+    remove_argument (N, &argc, argv);
+    char *tmpfile = strcreate (argv[N]);
+    DCR_FILE = abspath (tmpfile, DVO_MAX_PATH);
+    remove_argument (N, &argc, argv);
+  }
+  DCR_RESET = FALSE;
+  if ((N = get_argument (argc, argv, "-DCR-reset"))) {
+    remove_argument (N, &argc, argv);
+    DCR_RESET = TRUE;
+  }
+
   SINGLE_CPT = NULL;
   if ((N = get_argument (argc, argv, "-cpt"))) {
@@ -118,5 +146,10 @@
   }
 
-  if (argc != 2) usage_setastrom();
+  if (!KH_FILE && !DCR_FILE) {
+    fprintf (stderr, "at least one of -KH and -DCR must be supplied\n");
+    exit (1);
+  }
+
+  if (argc != 1) usage_setastrom();
 
   return (TRUE);
@@ -124,6 +157,8 @@
 
 void usage_setastrom_client () {
-  fprintf (stderr, "USAGE: setastrom_client (correction) -hostID (hostID) -catdir (catdir) -hostdir (hostdir) [options]\n");
+  fprintf (stderr, "USAGE: setastrom_client -hostID (hostID) -catdir (catdir) -hostdir (hostdir) [options]\n");
   fprintf (stderr, "  options:\n");
+  fprintf (stderr, "    -DCR (file) : supply DCR correction splines\n");
+  fprintf (stderr, "    -KH (file) : supply Koppenhoefer correction splines\n");
   fprintf (stderr, "    -region Rmin Rmax Dmin Dmax\n");
   fprintf (stderr, "    -update-catformat (format) : change database schema on output\n");
@@ -163,4 +198,28 @@
 
   int N;
+
+  KH_FILE = NULL;
+  if ((N = get_argument (argc, argv, "-KH"))) {
+    remove_argument (N, &argc, argv);
+    KH_FILE = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  KH_RESET = FALSE;
+  if ((N = get_argument (argc, argv, "-KH-reset"))) {
+    remove_argument (N, &argc, argv);
+    KH_RESET = TRUE;
+  }
+
+  DCR_FILE = NULL;
+  if ((N = get_argument (argc, argv, "-DCR"))) {
+    remove_argument (N, &argc, argv);
+    DCR_FILE = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  DCR_RESET = FALSE;
+  if ((N = get_argument (argc, argv, "-DCR-reset"))) {
+    remove_argument (N, &argc, argv);
+    DCR_RESET = TRUE;
+  }
 
   /* specify portion of the sky : allow default of all sky? */
@@ -223,5 +282,10 @@
   if (!CATDIR) usage_setastrom_client();
 
-  if (argc != 2) usage_setastrom_client();
+  if (!KH_FILE && !DCR_FILE) {
+    fprintf (stderr, "at least one of -KH and -DCR must be supplied\n");
+    exit (1);
+  }
+
+  if (argc != 1) usage_setastrom_client();
   return (TRUE);
 }
Index: /trunk/Ohana/src/uniphot/src/initialize_setgalmodel.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/initialize_setgalmodel.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/initialize_setgalmodel.c	(revision 37807)
@@ -0,0 +1,294 @@
+# include "setgalmodel.h"
+
+void usage_setgalmodel () {
+    fprintf (stderr, "USAGE: setgalmodel [options]\n");
+    fprintf (stderr, "  options:\n");
+    fprintf (stderr, "    -v : verbose mode\n");
+    fprintf (stderr, "    -region Rmin Rmax Dmin Dmax\n");
+    fprintf (stderr, "    -update-catformat (format) : change database schema on output\n");
+    fprintf (stderr, "    -update : actually write results to detections tables\n");
+    fprintf (stderr, "    -parallel : run in parallel mode\n");
+    fprintf (stderr, "    -h     : this help list\n");
+    fprintf (stderr, "    -help  : this help list\n");
+    fprintf (stderr, "    --h    : this help list\n");
+    fprintf (stderr, "    --help : this help list\n");
+    fprintf (stderr, "    Note that the dvo db can be specified by -D CATDIR (directory)\n");
+    exit (2);
+}
+
+void initialize_setgalmodel (int argc, char **argv) {
+
+  int N;
+  struct stat statbuffer;
+  char CatdirPhotcodeFile[256];
+
+  if ((N = get_argument (argc, argv, "-h"))) usage_setgalmodel();
+  if ((N = get_argument (argc, argv, "-help"))) usage_setgalmodel();
+  if ((N = get_argument (argc, argv, "--h"))) usage_setgalmodel();
+  if ((N = get_argument (argc, argv, "--help"))) usage_setgalmodel();
+
+  /* are these set correctly? */
+  ConfigInit (&argc, argv);
+  args_setgalmodel (argc, argv);
+
+  if (stat (CATDIR, &statbuffer)) {
+    fprintf (stderr, "error accessing dvo database directory '%s'\n", CATDIR);
+    exit (1);
+  }
+
+  // load the photcode table
+  sprintf (CatdirPhotcodeFile, "%s/Photcodes.dat", CATDIR);
+  if (!LoadPhotcodes (CatdirPhotcodeFile, NULL, FALSE)) {
+    fprintf (stderr, "error loading photcode table %s\n", CatdirPhotcodeFile);
+    exit (1);
+  }
+
+  // XXX add to config?
+  if (!InitGalaxyModel ("ROESER")) {
+    fprintf (stderr, "failed to init galaxy model\n");
+    exit (2);
+  }
+}
+
+int args_setgalmodel (int argc, char **argv) {
+
+  int N;
+
+  SINGLE_CPT = NULL;
+  if ((N = get_argument (argc, argv, "-cpt"))) {
+    remove_argument (N, &argc, argv);
+    SINGLE_CPT = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  /* specify portion of the sky : allow default of all sky? */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax = 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+  if ((N = get_argument (argc, argv, "-region"))) {
+    remove_argument (N, &argc, argv);
+    UserPatch.Rmin = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+    UserPatch.Rmax = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+    UserPatch.Dmin = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+    UserPatch.Dmax = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+  } 
+
+  UPDATE_CATFORMAT = NULL;
+  if ((N = get_argument (argc, argv, "-update-catformat"))) {
+    remove_argument (N, &argc, argv);
+    UPDATE_CATFORMAT = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  VERBOSE = FALSE;
+  if ((N = get_argument (argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  TESTING = FALSE;
+  if ((N = get_argument (argc, argv, "-testing"))) {
+    TESTING = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  PARALLEL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel"))) {
+    PARALLEL = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  // this is a test mode : rather than launching the remote jobs and waiting for completion,
+  // setgalmodel will simply list the remote command and wait for the user to signal completion
+  PARALLEL_MANUAL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel-manual"))) {
+    PARALLEL = TRUE; // -parallel-manual implies -parallel
+    PARALLEL_MANUAL = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+  // this is a test mode : rather than launching the setgalmodel_client jobs remotely, they are 
+  // run in serial via 'system'
+  PARALLEL_SERIAL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel-serial"))) {
+    if (PARALLEL_MANUAL) {
+      fprintf (stderr, "ERROR: cannot mix -parallel-manual and -parallel-serial\n");
+      exit (1);
+    }
+    PARALLEL = TRUE; // -parallel-serial implies -parallel
+    PARALLEL_SERIAL = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  UPDATE = FALSE;
+  if ((N = get_argument (argc, argv, "-update"))) {
+    remove_argument (N, &argc, argv);
+    UPDATE = TRUE;
+  }
+
+  if (argc != 1) usage_setgalmodel();
+
+  return (TRUE);
+}
+
+void usage_setgalmodel_client () {
+  fprintf (stderr, "USAGE: setgalmodel_client -hostID (hostID) -catdir (catdir) -hostdir (hostdir) [options]\n");
+  fprintf (stderr, "  options:\n");
+  fprintf (stderr, "    -region Rmin Rmax Dmin Dmax\n");
+  fprintf (stderr, "    -update-catformat (format) : change database schema on output\n");
+  fprintf (stderr, "    -v : verbose mode\n");
+  fprintf (stderr, "    -update : actually write results to detections tables\n");
+  exit (2);
+}
+
+void initialize_setgalmodel_client (int argc, char **argv) {
+
+  int N;
+  struct stat statbuffer;
+  char CatdirPhotcodeFile[256];
+
+  if ((N = get_argument (argc, argv, "-h"))) usage_setgalmodel_client();
+  if ((N = get_argument (argc, argv, "-help"))) usage_setgalmodel_client();
+  if ((N = get_argument (argc, argv, "--h"))) usage_setgalmodel_client();
+  if ((N = get_argument (argc, argv, "--help"))) usage_setgalmodel_client();
+
+  SetZeroPoint (25.0); // XXX is this needed?
+  args_setgalmodel_client (argc, argv);
+
+  if (stat (CATDIR, &statbuffer)) {
+    fprintf (stderr, "error accessing dvo database directory '%s'\n", CATDIR);
+    exit (1);
+  }
+
+  // load the photcode table : XXX needed?
+  sprintf (CatdirPhotcodeFile, "%s/Photcodes.dat", CATDIR);
+  if (!LoadPhotcodes (CatdirPhotcodeFile, NULL, FALSE)) {
+    fprintf (stderr, "error loading photcode table %s\n", CatdirPhotcodeFile);
+    exit (1);
+  }
+
+  // XXX add to config?
+  if (!InitGalaxyModel ("ROESER")) {
+    fprintf (stderr, "failed to init galaxy model\n");
+    exit (2);
+  }
+}
+
+int args_setgalmodel_client (int argc, char **argv) {
+
+  int N;
+
+  /* specify portion of the sky : allow default of all sky? */
+  UserPatch.Rmin = 0;
+  UserPatch.Rmax = 360;
+  UserPatch.Dmin = -90;
+  UserPatch.Dmax = +90;
+  if ((N = get_argument (argc, argv, "-region"))) {
+    remove_argument (N, &argc, argv);
+    UserPatch.Rmin = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+    UserPatch.Rmax = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+    UserPatch.Dmin = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+    UserPatch.Dmax = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+  } 
+
+  UPDATE_CATFORMAT = NULL;
+  if ((N = get_argument (argc, argv, "-update-catformat"))) {
+    remove_argument (N, &argc, argv);
+    UPDATE_CATFORMAT = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  VERBOSE = FALSE;
+  if ((N = get_argument (argc, argv, "-v"))) {
+    VERBOSE = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  TESTING = FALSE;
+  if ((N = get_argument (argc, argv, "-testing"))) {
+    TESTING = TRUE;
+    remove_argument (N, &argc, argv);
+  }
+
+  UPDATE = FALSE;
+  if ((N = get_argument (argc, argv, "-update"))) {
+    remove_argument (N, &argc, argv);
+    UPDATE = TRUE;
+  }
+
+  HOST_ID = 0;
+  if ((N = get_argument (argc, argv, "-hostID"))) {
+    remove_argument (N, &argc, argv);
+    HOST_ID = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!HOST_ID) usage_setgalmodel_client();
+
+  HOSTDIR = NULL;
+  if ((N = get_argument (argc, argv, "-hostdir"))) {
+    remove_argument (N, &argc, argv);
+    HOSTDIR = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!HOSTDIR) usage_setgalmodel_client();
+
+  if ((N = get_argument (argc, argv, "-catdir"))) {
+    remove_argument (N, &argc, argv);
+    CATDIR = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  if (!CATDIR) usage_setgalmodel_client();
+
+  if (argc != 1) usage_setgalmodel_client();
+  return (TRUE);
+}
+
+void ConfigInit (int *argc, char **argv) {
+
+  double ZERO_POINT;
+  char  *config, *file;
+
+  /*** load configuration info ***/
+  file = SelectConfigFile (argc, argv, "ptolemy");
+  config = LoadConfigFile (file);
+  if (config == (char *) NULL) {
+    fprintf (stderr, "ERROR: can't find configuration file %s\n", file);
+    if (file != (char *) NULL) free (file);
+    exit (0);
+  }
+  if (VERBOSE) fprintf (stderr, "loaded config file: %s\n", file);
+
+  // force CATDIR to be absolute (so parallel mode will work)
+  char *tmpcatdir = NULL;
+  ALLOCATE (tmpcatdir, char, DVO_MAX_PATH);
+  GetConfig (config, "CATDIR",                 "%s",  0, tmpcatdir);
+  CATDIR = abspath (tmpcatdir, DVO_MAX_PATH);
+  free (tmpcatdir);
+
+  sprintf (ImageCat, "%s/Images.dat", CATDIR);
+
+  ScanConfig (config, "ZERO_PT",                "%lf", 0, &ZERO_POINT);
+  SetZeroPoint (ZERO_POINT);
+
+  free (config);
+  free (file);
+}
+
+void GetConfig (char *config, char *field, char *format, int N, void *ptr) {
+
+  char *status;
+
+  status = ScanConfig (config, field, format, N, ptr);
+  if (status == NULL) {
+    fprintf (stderr, "error in config, cannot find %s\n", field);
+    exit (1);
+  }
+  return;
+}
Index: /trunk/Ohana/src/uniphot/src/kh_correction.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/kh_correction.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/kh_correction.c	(revision 37807)
@@ -0,0 +1,146 @@
+# include "setastrom.h"
+
+// Koppenhoefer astrometry correction functions:
+// load the correction set from a file
+
+// the correction is saved as a set of splines, one per file extension
+// extensions have names of the form XYnn.Dx.Td
+
+// where nn = 00 - 77, the chip number 
+// x is X or Y, the correction direction (dir)
+// d is 0 or 1, pre or post camera correction (sub)
+
+// these are stored in an N-D array of (Spline *) splineset[sub][dir][chip]
+
+# define N_SUB 2
+# define N_DIR 2
+# define N_CHIP 78
+// note that we are allocating pointers for XY00 - XY77, but only the octal elements are set (and not the 00,07,70,77 ones)
+
+static Spline ****splineset;
+
+int load_kh_correction (char *filename) {
+
+  int Ncol;
+  off_t Nrow;
+
+  char type[16];
+  char extname[80];
+
+  Header header;
+  Header theader;
+  FTable ftable;
+
+  /* open file for input */
+  FILE *f = fopen (filename, "r");
+  if (f == (FILE *) NULL) {
+    gprint (GP_ERR, "can't open file for read : %s\n", filename);
+    return FALSE;
+  }
+
+  // read the PHU header
+  if (!gfits_load_header (f, &header)) return (FALSE);
+  
+  int Nbytes = gfits_data_size (&header);
+  fseeko (f, Nbytes, SEEK_CUR);
+  // XXX gfits_free_header (&header);
+
+  ftable.header = &theader;
+
+  int sub, dir, chip;
+
+  // we have a N-D array of splines:
+  // splineset[sub][dir][chip]
+
+  ALLOCATE (splineset, Spline ***, N_SUB);
+  for (sub = 0; sub < N_SUB; sub++) {
+    ALLOCATE (splineset[sub], Spline **, N_DIR);
+    for (dir = 0; dir < N_DIR; dir++) {
+      ALLOCATE (splineset[sub][dir], Spline *, N_CHIP);
+      for (chip = 0; chip < N_CHIP; chip ++) {
+	splineset[sub][dir][chip] = NULL;
+      }
+    }
+  }
+
+  while (TRUE) {
+    // load data for this header : if not found, assume we hit the end of the file
+    if (!gfits_load_header (f, &theader)) break;
+    
+    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) return (FALSE);
+    
+    if (!gfits_scan (&theader, "EXTNAME", "%s", 1, extname)) return (FALSE);
+
+    // figure out the chip ID, diretion, and sequence from the EXTNAME
+
+    // EXTNAME is of the form XY%d%d.D%s.T%s where 
+    char chipname[3];
+    chipname[0] = extname[2];
+    chipname[1] = extname[3];
+    chipname[2] = 0;
+    int chip = atoi(chipname);
+    myAssert (chip >   0, "error in extname %s not(chip >   0)\n", extname);
+    myAssert (chip <  77, "error in extname %s not(chip <  77)\n", extname);
+    myAssert (chip !=  7, "error in extname %s not(chip !=  7)\n", extname);
+    myAssert (chip != 70, "error in extname %s not(chip != 70)\n", extname);
+
+    int dir = -1;
+    if (extname[6] == 'X') {
+      dir = 0;
+    }
+    if (extname[6] == 'Y') {
+      dir = 1;
+    }
+    myAssert (dir != -1, "error in extname %s (dir)\n", extname);
+
+    int sub = -1;
+    if (extname[9] == '0') {
+      sub = 0;
+    }
+    if (extname[9] == '1') {
+      sub = 1;
+    }
+    myAssert (sub != -1, "error in extname %s (sub)\n", extname);
+
+    // need to create and assign to flat-field correction
+    double *xk = gfits_get_bintable_column_data (&theader, &ftable, "X_KNOT", type, &Nrow, &Ncol);
+    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
+    
+    // need to create and assign to flat-field correction
+    double *yk = gfits_get_bintable_column_data (&theader, &ftable, "Y_KNOT", type, &Nrow, &Ncol);
+    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
+    
+    // need to create and assign to flat-field correction
+    double *y2 = gfits_get_bintable_column_data (&theader, &ftable, "DY2_DX", type, &Nrow, &Ncol);
+    myAssert (!strcmp(type, "double"), "error reading X_KNOT");
+    
+    ALLOCATE (splineset[sub][dir][chip], Spline, 1);
+    splineset[sub][dir][chip]->xk = xk;
+    splineset[sub][dir][chip]->yk = yk;
+    splineset[sub][dir][chip]->y2 = y2;
+    splineset[sub][dir][chip]->Nknots = Nrow;
+  }
+  return (TRUE);
+}
+
+int get_kh_correction (int sub, int chip, double *dX, double *dY, float Minst) {
+
+  Spline *spline = NULL;
+
+  *dX = 0.0;
+  *dY = 0.0;
+
+  // XXX need to include some limits on Minst?
+
+  spline = splineset[sub][0][chip];
+  if (spline) {
+    *dX = spline_apply_dbl (spline->xk, spline->yk, spline->y2, spline->Nknots, Minst);
+  }
+
+  spline = splineset[sub][1][chip];
+  if (spline) {
+    *dY = spline_apply_dbl (spline->xk, spline->yk, spline->y2, spline->Nknots, Minst);
+  }
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/uniphot/src/load_zpt_table.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/load_zpt_table.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/load_zpt_table.c	(revision 37807)
@@ -173,7 +173,8 @@
       mjdstart = tstart_uc[i];
       mjdstop  = tstop_uc[i];
-  } else {
+    } else {
       char name[9];
-      snprintf (name, 9, "TS0_%04d", i);
+      // snprintf (name, 9, "TS0_%04d", i);
+      snprintf (name, 9, "S0_MJD%d", i);
       if (!gfits_scan (&header, name, "%lf", 1, &mjdstart)) { 
 	fprintf (stderr, "cannot find %s in header of %s\n", name, filename);
@@ -182,5 +183,6 @@
       }
 
-      snprintf (name, 9, "TS1_%04d", i);
+      // snprintf (name, 9, "TS1_%04d", i);
+      snprintf (name, 9, "S1_MJD%d", i);
       if (!gfits_scan (&header, name, "%lf", 1, &mjdstop)) { 
 	fprintf (stderr, "cannot find %s in header of %s\n", name, filename);
@@ -189,6 +191,6 @@
       }
     }
-    flatcorrTable->tstart[i] = ohana_mjd_to_sec(mjdstart);
-    flatcorrTable->tstop[i]  = ohana_mjd_to_sec(mjdstop);
+    flatcorrTable->tstart[i] = (mjdstart > 0) ? ohana_mjd_to_sec(mjdstart) : ohana_mjd_to_sec(50000.0); // 1995/10/10 (ancient history)
+    flatcorrTable->tstop[i]  = (mjdstop  > 0) ? ohana_mjd_to_sec(mjdstop)  : ohana_mjd_to_sec(60000.0); // 2023/02/25 (infinite future)
   }
 
@@ -247,7 +249,8 @@
     } else {
       if (!gfits_scan (&header, "FILTER", "%s", 1, filter)) { 
-	fprintf (stderr, "cannot find FILTER in header of %s\n", filename);
-	fclose (f);
-	return NULL;
+	fprintf (stderr, "cannot find FILTER in header of %s, using %s\n", filename, filters_uc[nfilter]);
+	strcpy (filter, filters_uc[nfilter]);
+	// fclose (f);
+	// return NULL;
       }
     }
@@ -278,4 +281,19 @@
       REALLOCATE (offset64, double, matrix.Naxis[0]);
       matrix.buffer = (char *) offset64;
+    }
+    if (matrix.Naxes == 1) {
+      int Ntotal = NSEASON*NCHIP_X*NCHIP_Y*NCELL_X*NCELL_Y;
+      if (matrix.Naxis[0] != Ntotal) {
+	int Nextra = Ntotal - matrix.Naxis[0];
+	assert (Nextra >= 0);
+	fprintf (stderr, "warning: images are not fully populated, extending by %d\n", Nextra);
+	
+	REALLOCATE (offset64, double, Ntotal);
+	int npix;
+	for (npix = matrix.Naxis[0]; npix < Ntotal; npix ++) offset64[npix] = 0.0;
+
+	matrix.Naxis[0] = Ntotal;
+	matrix.buffer = (char *) offset64;
+      }
     }
 
Index: /trunk/Ohana/src/uniphot/src/match_zpts_to_images.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/match_zpts_to_images.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/match_zpts_to_images.c	(revision 37807)
@@ -97,4 +97,10 @@
     }
 
+    // if secz is crazy, do not trust ubercal; skip and move along
+    if (image[Ni].secz < 0.1) {
+      i++;
+      continue;
+    }
+
     // we have a match: set zpt and record the match
     // is the zero point supplied nominally corrected or not?
Index: /trunk/Ohana/src/uniphot/src/setgalmodel.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/setgalmodel.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/setgalmodel.c	(revision 37807)
@@ -0,0 +1,22 @@
+# include "setgalmodel.h"
+
+int main (int argc, char **argv) {
+
+  int status;
+
+  /* get configuration info, args, lockfile */
+  initialize_setgalmodel (argc, argv);
+
+  status = update_dvo_setgalmodel ();
+
+  if (!status) exit (1);
+  exit (0);
+}
+  
+
+/* setgalmodel : set starpar.uRA,uDEC based on starpar.DM
+
+   ** load images
+   ** load catalogs
+   ** update detection (& averages?)
+ */
Index: /trunk/Ohana/src/uniphot/src/setgalmodel_client.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/setgalmodel_client.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/setgalmodel_client.c	(revision 37807)
@@ -0,0 +1,20 @@
+# include "setgalmodel.h"
+
+// setgalmodel_client is run on a remote host and is responsible for updating the catalogs
+// owned by that host.  
+
+// setgalmodel_client -hostname (hostname) -catdir (catdir) -hostdir (hostdir) -images (images)
+// load the SkyTable and HostTable from (catdir) [contains the host responsibilities]
+// load the image data from (images) [contains the zp infomation to apply]
+// loop over tables from SkyTable with my host ID
+// the calling program tells the client which host they are (or host ID?); we do not actually have to run this on the real host
+
+int main (int argc, char **argv) {
+
+  // get configuration info, args, lockfile (set CATDIR, HOST_ID, HOSTDIR, IMAGES)
+  initialize_setgalmodel_client (argc, argv);
+
+  update_dvo_setgalmodel ();
+
+  exit (0);
+}
Index: /trunk/Ohana/src/uniphot/src/update_catalog_setastrom.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_catalog_setastrom.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/update_catalog_setastrom.c	(revision 37807)
@@ -1,3 +1,5 @@
 # include "setastrom.h"
+
+// this code does both DCR and KH corrections, if both are specified.  
 
 // XXX this function is dependent in two ways on the specific numerical values of the photcodes
@@ -7,9 +9,20 @@
 int update_catalog_setastrom (Catalog *catalog) {
 
-  off_t i, found;
+  off_t i;
 
   time_t timeRef = ohana_date_to_sec("2011/05/11,00:00:00");
 
-  found = 0;
+  int Nsecfilt = GetPhotcodeNsecfilt ();
+  PhotCode *code_g = GetPhotcodebyName ("g");
+  PhotCode *code_i = GetPhotcodebyName ("i");
+  PhotCode *code_z = GetPhotcodebyName ("z");
+  PhotCode *code_y = GetPhotcodebyName ("y");
+
+  int Nsec_g = GetPhotcodeNsec(code_g->code);
+  int Nsec_i = GetPhotcodeNsec(code_i->code);
+  int Nsec_z = GetPhotcodeNsec(code_z->code);
+  int Nsec_y = GetPhotcodeNsec(code_y->code);
+
+  off_t found = 0;
 
   for (i = 0; i < catalog[0].Nmeasure; i++) {
@@ -17,16 +30,22 @@
     Measure *measure = &catalog[0].measure[i];
 
-    // XXX only do GPC1 data for now
+    // only do GPC1 data for now (true for both DCR and KH)
     if (measure[0].photcode < 10000) continue;
     if (measure[0].photcode > 10600) continue;
       
-    // XXX hardwire the list of chips to correct
+    int averef = measure->averef;
+    Average *average = &catalog[0].average[averef];
+    SecFilt *secfilt = &catalog[0].secfilt[averef*Nsecfilt];
+
+    int chipID = measure[0].photcode % 100;
+
+    // XXX hardwire the list of chips to KH correct
     // some notes:
-    // XY24 surpisingly does not need correction
+    // XY24 surprisingly does not need correction
     // XY27 has poor astrometry with a weird correction for all mags
-    // XY36 surpisingly does not need correction
+    // XY36 surprisingly does not need correction
     // XY67 has poor astrometry with a weird correction for all mags
 
-    int chipID = measure[0].photcode % 100;
+    int doKH = FALSE;
     switch (chipID) {
       case 4:
@@ -57,53 +76,187 @@
       case 72:
       case 73:
+	doKH = TRUE;
 	break;
       default:
-	measure[0].Xfix = measure[0].Xccd;
-	measure[0].Yfix = measure[0].Yccd;
-	continue;
-    }
-
-    double Xccd = measure[0].Xccd;
-    double Yccd = measure[0].Yccd;
+	break;
+    }
+
     double pltScale = fabs(measure[0].pltscale);
-
-    // this is not needed, is it?
-    // double posAngle = FromShortDegrees(measure[0].posangle);
-
-    // I need to know which chip we have.  use photcode assuming range
-    int chip = measure[0].photcode % 100;
-
-    // correction may be Minst or Minst + 5.0*log(fwhm
-    float fwhm_maj = FromShortPixels(measure[0].FWx);
-    float Minst = PhotInst (measure, MAG_CLASS_PSF);
-    float MinstSB = Minst + 5.0*log10(fwhm_maj);
-
-    // I should validate that the spline I'm using is SB corrected not Minst...
-
-    // XXX this is very ad hoc.  the splines are measured based on an extraction from dvo
-    // the dvo values of fwhm_maj are sigmas (NOT FWHM!) in pixels.  
+    double posAngle = FromShortDegrees(measure[0].posangle);
+
+    /**** KH section ****/
+
+    double dX_KH = 0.0;
+    double dY_KH = 0.0;
+    if (KH_FILE && doKH && (measure[0].t < timeRef)) {
+
+      // correction may be Minst or Minst + 5.0*log(fwhm
+      float fwhm_maj = FromShortPixels(measure[0].FWx);
+      float Minst = PhotInst (measure, MAG_CLASS_PSF);
+      // float MinstSB = Minst + 5.0*log10(fwhm_maj);
+
+      // NOTE: the spline correction was measured on the nightly-science database using measurements
+      // from the period in which fwhm_maj & fwhm_min were a factor of sqrt(2) too small.  I am making 
+      // this correction here (but ideally I would re-measure the KH trend from PV2 data and re-generate 
+      // the splines
+      float MinstSB = Minst + 5.0*log10(M_SQRT2*fwhm_maj);
+
+      // I should validate that the spline I'm using is SB corrected not Minst..
+      // NOTE: it is SB, not raw Minst
+
+      // XXX this is very ad hoc.  the splines are measured based on an extraction from dvo
+      // the dvo values of fwhm_maj are sigmas (NOT FWHM!) in pixels.  
     
-    // saturate fwhm_maj in the range 1.0 < fwhm_maj < 5.0
-    // (images with really odd psf probably have poor astrometry anyway...)
-    fwhm_maj = MIN(fwhm_maj,5.0);
-    fwhm_maj = MAX(fwhm_maj,1.0);
-
-    double dX;
-    double dY;
-
-    // correction is in arcseconds
-    if (measure[0].t < timeRef) {
-      get_astrom_correction (0, chip, &dX, &dY, MinstSB);
-    } else {
-      // XXX do not correct new data
-      // get_astrom_correction (1, chip, &dX, &dY, MinstSB);
-      dX = 0.0;
-      dY = 0.0;
-    }
+      // saturate fwhm_maj in the range 1.0 < fwhm_maj < 5.0
+      // (images with really odd psf probably have poor astrometry anyway...)
+      fwhm_maj = MIN(fwhm_maj,5.0);
+      fwhm_maj = MAX(fwhm_maj,1.0);
+
+      // correction is in arcseconds
+      get_kh_correction (0, chipID, &dX_KH, &dY_KH, MinstSB);
+    }
+
+    /**** DCR section ****/
+
+    double dX_DCR = 0.0;
+    double dY_DCR = 0.0;
+    if (DCR_FILE) {
+
+      // check if the color*airmass term is not-nan (Skip otherwise)
+
+      // XXX hard-wire gpc1 photcodes for now:
+      // I should use photcode ops: PhotCode *code = PhotCodeByCode(measure->photcode)
+      // int equiv = code->equiv
+      // Nsec = GetNsec(equiv)
+
+      float dColor = 0.0;
+      int filtCode = (int)(measure->photcode / 100); // eg, 101 = r
+      switch (filtCode) {
+	case 100:
+	case 101:
+	case 102: {
+	  float gmag = secfilt[Nsec_g].M;
+	  float imag = secfilt[Nsec_i].M;
+	  dColor = average->refColorBlue - (gmag - imag);
+	  break;
+	}
+	case 103:
+	case 104: {
+	  float zmag = secfilt[Nsec_z].M;
+	  float ymag = secfilt[Nsec_y].M;
+	  dColor = average->refColorRed - (zmag - ymag);
+	  break;
+	}
+      }
+      int filtSeq = filtCode % 100;
+      if (!isfinite(dColor)) goto skip_DCR;
+
+      // I need to get the parallactic angle, but I only have alt & az, s:o 
+    
+      // airmass = 1.0 / cos(90 - alt)
+      // cos(90 - alt) = cos(zd) = sin(alt) = 1.0 / airmass
+      // airmass = 1.0 / sin(alt)
+
+      // airmass = 1.0 / cos(zd)
+      // cos(zd) = 1.0 / airmass == sn_alt
+      // sin(zd) = sqrt(1.0 - cos(zd)^2) == cs_alt
+      // tan(zd) = sin(zd) / cos(zd) = cs_alt / sn_alt
+
+      double sn_alt = 1.0 / measure->airmass;
+      double cs_alt = sqrt(1.0 - SQ(sn_alt));
+      double tan_zd = cs_alt / sn_alt;
+
+      double cs_az  = cos(RAD_DEG*measure->az);
+      double sn_az  = sin(RAD_DEG*measure->az);
+
+# define LATITUDE (20.7070999146*RAD_DEG)
+      double sn_lat = sin(LATITUDE);
+      double cs_lat = cos(LATITUDE);
+
+      // double sind = sn_alt * sn_lat + cs_alt * cs_az * cs_lat;
+      // *dec  = DEG_RAD * asin (sind);
+
+      // WARNING: these are NOT sin(ha),cos(ha) but are scaled
+      // double ha = DEG_RAD * atan2 (sinh, cosh);
+      double sinh = -cs_alt * sn_az;
+      double cosh =  sn_alt * cs_lat - cs_alt * cs_az * sn_lat;
+      double r_ha = hypot(sinh, cosh);
+      double sn_ha = sinh / r_ha;
+      double cs_ha = cosh / r_ha;
+
+      sinh = -cs_az * sn_alt * sn_ha * sn_lat + sn_az * sn_alt * cs_ha - sn_ha * cs_alt * cs_lat;
+      cosh = -sn_az          * sn_ha * sn_lat - cs_az          * cs_ha;
+      double r_rot = hypot(sinh, cosh);
+
+      double sn_rot = -sinh / r_rot;
+      double cs_rot = +cosh / r_rot;
+
+      double dPx = 0.0; 
+      double dPy = 0.0;
+
+      float colorOffset = dColor * tan_zd;
+      get_dcr_correction (filtSeq, &dPx, &dPy, colorOffset);
+
+      // if we had x-terms, we would have:
+      // double dPx = dRx *cs_rot + dD_s*sn_rot;
+      // double dPy = dD_s*cs_rot - dRx *sn_rot;
+      // double dR = dPx*cs_rot - dPy*sn_rot;
+      // double dD = dPx*sn_rot + dPy*cs_rot;
+
+      // since dPx = 0.0, instead we have: dPx, dPy as measured in dvo.dcr.sh
+      double dR = -dPy*sn_rot;
+      double dD = +dPy*cs_rot;
+
+      // dR = (measure->R - average->R) * 3600 * dcos(dec) [ie, in the projected plane]
+      // dD = (measure->D - average->D) * 3600
+      // average->D = measure->D - dD/3600
+
+      // now we need to rotate dR,dD into the chip frame and correction Xccd,Yccd
+
+      // are the plate-scale and posangle correctly set in the db?
+
+      // XXX the koppenhoefer code implies pos if angle from y -> D, (ie negative of the
+      // below).  if so, apply sin(pos) -> -sin(pos)
+
+      // if parity = sky:
+      // double dR =  dX*cos(pos) + dY*sin(pos);
+      // double dD = -dX*sin(pos) + dY*cos(pos);
+
+      // XXX signs set here to reflect pos = -pos
+      float posAngRad = posAngle * RAD_DEG;
+      dX_DCR =  dR*cos(posAngRad) + dD*sin(posAngRad);
+      dY_DCR = -dR*sin(posAngRad) + dD*cos(posAngRad);
+    }
+
+  skip_DCR:
 
     // do not modify the original Xccd,Yccd values
-    measure[0].Xfix = Xccd + dX / pltScale;
-    measure[0].Yfix = Yccd + dY / pltScale;
-
+    if (KH_FILE) {
+      measure[0].XoffKH = dX_KH / pltScale;
+      measure[0].YoffKH = dY_KH / pltScale;
+    }
+    if (KH_RESET) {
+      measure[0].XoffKH = 0.0;
+      measure[0].YoffKH = 0.0;
+    }
+    if (DCR_FILE) {
+      measure[0].XoffDCR = -dX_DCR / pltScale;
+      measure[0].YoffDCR = -dY_DCR / pltScale;
+    }
+    if (DCR_RESET) {
+      measure[0].XoffDCR = -dX_DCR / pltScale;
+      measure[0].YoffDCR = -dY_DCR / pltScale;
+    }
+
+    measure[0].Xfix = measure[0].Xccd;
+    measure[0].Yfix = measure[0].Yccd;
+    if (isfinite(measure[0].XoffKH) && isfinite(measure[0].YoffKH)) {
+      measure[0].Xfix += measure[0].XoffKH;
+      measure[0].Yfix += measure[0].YoffKH;
+    }
+    if (isfinite(measure[0].XoffDCR) && isfinite(measure[0].YoffDCR)) {
+      measure[0].Xfix += measure[0].XoffDCR;
+      measure[0].Yfix += measure[0].YoffDCR;
+    }
     found ++;
   }
Index: /trunk/Ohana/src/uniphot/src/update_catalog_setastrom_dcr.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_catalog_setastrom_dcr.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/update_catalog_setastrom_dcr.c	(revision 37807)
@@ -0,0 +1,139 @@
+# include "setastrom.h"
+
+// this DCR code is largely hard-wired for gpc1 data
+
+int update_catalog_setastrom_dcr (Catalog *catalog) {
+
+  off_t i, found;
+
+  Nsecfilt = GetPhotcodeNsecfilt ();
+  PhotCode *code_g = GetPhotcodebyName ("g");
+  PhotCode *code_i = GetPhotcodebyName ("i");
+  PhotCode *code_z = GetPhotcodebyName ("z");
+  PhotCode *code_y = GetPhotcodebyName ("y");
+
+  Nsec_g = GetPhotcodeNsec(code_g->code);
+  Nsec_i = GetPhotcodeNsec(code_i->code);
+  Nsec_z = GetPhotcodeNsec(code_z->code);
+  Nsec_y = GetPhotcodeNsec(code_y->code);
+
+  for (i = 0; i < catalog[0].Nmeasure; i++) {
+
+    Measure *measure = &catalog[0].measure[i];
+
+    double Xccd = measure[0].Xccd;
+    double Yccd = measure[0].Yccd;
+    double pltScale = fabs(measure[0].pltscale);
+    double posAngle = FromShortDegrees(measure[0].posangle);
+
+    // I need to get the parallactic angle, but I only have alt & az, s:o 
+    
+    // airmass = 1.0 / cos(90 - alt)
+    // cos(90 - alt) = cos(zd) = sin(alt) = 1.0 / airmass
+    // airmass = 1.0 / sin(alt)
+
+    // airmass = 1.0 / cos(zd)
+    // cos(zd) = 1.0 / airmass == sn_alt
+    // sin(zd) = sqrt(1.0 - cos(zd)^2) == cs_alt
+    // tan(zd) = sin(zd) / cos(zd) = cs_alt / sn_alt
+
+    double sn_alt = 1.0 / measure->airmass;
+    double cs_alt = sqrt(1.0 - SQ(sn_alt));
+    double tan_zd = cs_alt / sn_alt;
+
+    double cs_az  = cos(RAD_DEG*measure->az);
+    double sn_az  = sin(RAD_DEG*measure->az);
+
+# define LATITUDE (20.7070999146*RAD_DEG)
+
+    double sn_ha = -cs_alt * sn_az;
+    double cs_ha =  sn_alt * cos(LATITUDE) - cs_alt * cs_az * sin(LATITUDE);
+
+    double sn_rot = -cs_az * sn_alt * sn_ha * sin(LATITUDE) + sn_az * sn_alt * cs_ha - sn_ha * cs_alt * cos(LATITUDE);
+    double cs_rot = -sn_az *          sn_ha * sin(LATITUDE) - cs_az          * cs_ha;
+
+    float dColor = 0.0;
+    float DCRslope = 0.0;
+
+    // XXX hard-wire gpc1 photcodes for now:
+    // I should use photcode ops: PhotCode *code = PhotCodeByCode(measure->photcode)
+    // int equiv = code->equiv
+    // Nsec = GetNsec(equiv)
+
+    int filtCode = (int)(measure->photcode / 100); // eg, 101 = r
+    switch (filtCode) {
+      case 100:
+      case 101:
+      case 102:
+	float gmag = secfilt[Nsec_g].M;
+	float imag = secfilt[Nsec_i].M;
+	dColor = average->refColorBlue - (gmag - imag);
+	break;
+      case 103:
+      case 104:
+	float zmag = secfilt[Nsec_z].M;
+	float ymag = secfilt[Nsec_y].M;
+	dColor = average->refColorRed - (zmag - ymag);
+	break;
+    }
+    int filtSeq = filtCode % 100;
+    float colorOffset = dColor * tan_dz;
+
+    get_dcr_correction (filtSeq, double *dPx, double *dPy, colorOffset);
+
+    // if we had x-terms, we would have:
+    // double dPx = dRx *cs_rot + dD_s*sn_rot;
+    // double dPy = dD_s*cs_rot - dRx *sn_rot;
+    // double dR = dPx*cs_rot - dPy*sn_rot;
+    // double dD = dPx*sn_rot + dPy*cs_rot;
+
+    // since dPx = 0.0, instead we have: dPx, dPy as measured in dvo.dcr.sh
+    double dR = -dPy*sn_rot;
+    double dD = +dPy*cs_rot;
+
+    // dR = (measure->R - average->R) * 3600 * dcos(dec) [ie, in the projected plane]
+    // dD = (measure->D - average->D) * 3600
+    // average->D = measure->D - dD/3600
+
+    // now we need to rotate dR,dD into the chip frame and correction Xccd,Yccd
+
+    // are the plate-scale and posangle correctly set in the db?
+
+    // XXX the koppenhoefer code implies pos if angle from y -> D, (ie negative of the
+    // below).  if so, apply sin(pos) -> -sin(pos)
+
+    // if parity = sky:
+    // double dR =  dX*cos(pos) + dY*sin(pos);
+    // double dD = -dX*sin(pos) + dY*cos(pos);
+
+    double dX =  dR*cos(pos) - dD*sin(pos);
+    double dY =  dR*sin(pos) + dD*cos(pos);
+
+    // if parity != sky:
+    // double dD =  dY*cos(pos) + dX*sin(pos);
+    // double dR =  dY*sin(pos) - dX*cos(pos);
+    // double dX = -dR*cos(pos) + dD*sin(pos);
+    // double dY =  dR*sin(pos) + dD*cos(pos);
+
+    // do not modify the original Xccd,Yccd values
+
+    // why is KH added but DCR subtracted??
+
+    // ANSWER: both KH and DCR trends are measured by extracted dR,dD from the database
+    // (mextract dR dD).  But the KH analysis was done before measure carried R,D when it
+    // instead carried dR,dD.  In the current definition in dbExtractMeasures, dR,dD =
+    // (measure.R,D - average.R,D) * 3600, but in the old format, measure.dR,dD were
+    // defined as (average.R,D - measure.R,D)*3600).
+
+    measure[0].XoffDCR = -dX / pltScale;
+    measure[0].YoffDCR = -dY / pltScale;
+
+    found ++;
+  }
+
+  if (found) {
+    fprintf (stderr, "found "OFF_T_FMT" matches\n", found);
+  }
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/uniphot/src/update_catalog_setastrom_kh.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_catalog_setastrom_kh.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/update_catalog_setastrom_kh.c	(revision 37807)
@@ -0,0 +1,118 @@
+# include "setastrom.h"
+
+// XXX this function is dependent in two ways on the specific numerical values of the photcodes
+// 1) the examined measurements are limited to GPC1 values based on the photcode range 10000 - 10600
+// 2) the chip ID is determined from the numerical value of the photcode (chip = photcode % 100)
+
+int update_catalog_setastrom (Catalog *catalog) {
+
+  off_t i, found;
+
+  time_t timeRef = ohana_date_to_sec("2011/05/11,00:00:00");
+
+  found = 0;
+
+  for (i = 0; i < catalog[0].Nmeasure; i++) {
+
+    Measure *measure = &catalog[0].measure[i];
+
+    // XXX only do GPC1 data for now
+    if (measure[0].photcode < 10000) continue;
+    if (measure[0].photcode > 10600) continue;
+      
+    // XXX hardwire the list of chips to correct
+    // some notes:
+    // XY24 surprisingly does not need correction
+    // XY27 has poor astrometry with a weird correction for all mags
+    // XY36 surprisingly does not need correction
+    // XY67 has poor astrometry with a weird correction for all mags
+
+    int chipID = measure[0].photcode % 100;
+    switch (chipID) {
+      case 4:
+      case 5:
+      case 6:
+      case 14:
+      case 15:
+      case 16:
+      case 17:
+      case 25:
+      case 26:
+      case 34:
+      case 35:
+      case 37:
+      case 40:
+      case 41:
+      case 42:
+      case 43:
+      case 50:
+      case 51:
+      case 52:
+      case 53:
+      case 60:
+      case 61:
+      case 62:
+      case 63:
+      case 71:
+      case 72:
+      case 73:
+	break;
+      default:
+	measure[0].Xfix = measure[0].Xccd;
+	measure[0].Yfix = measure[0].Yccd;
+	continue;
+    }
+
+    double Xccd = measure[0].Xccd;
+    double Yccd = measure[0].Yccd;
+    double pltScale = fabs(measure[0].pltscale);
+
+    // this is not needed, is it?
+    // double posAngle = FromShortDegrees(measure[0].posangle);
+
+    // I need to know which chip we have.  use photcode assuming range
+    int chip = measure[0].photcode % 100;
+
+    // correction may be Minst or Minst + 5.0*log(fwhm
+    float fwhm_maj = FromShortPixels(measure[0].FWx);
+    float Minst = PhotInst (measure, MAG_CLASS_PSF);
+    float MinstSB = Minst + 5.0*log10(fwhm_maj);
+
+    // I should validate that the spline I'm using is SB corrected not Minst...
+
+    // XXX this is very ad hoc.  the splines are measured based on an extraction from dvo
+    // the dvo values of fwhm_maj are sigmas (NOT FWHM!) in pixels.  
+    
+    // saturate fwhm_maj in the range 1.0 < fwhm_maj < 5.0
+    // (images with really odd psf probably have poor astrometry anyway...)
+    fwhm_maj = MIN(fwhm_maj,5.0);
+    fwhm_maj = MAX(fwhm_maj,1.0);
+
+    double dX;
+    double dY;
+
+    // correction is in arcseconds
+    if (measure[0].t < timeRef) {
+      get_astrom_correction (0, chip, &dX, &dY, MinstSB);
+    } else {
+      // XXX do not correct new data
+      // get_astrom_correction (1, chip, &dX, &dY, MinstSB);
+      dX = 0.0;
+      dY = 0.0;
+    }
+
+    // do not modify the original Xccd,Yccd values
+    measure[0].XoffKH = Xccd + dX / pltScale;
+    measure[0].YoffKH = Yccd + dY / pltScale;
+    measure[0].Xfix   = measure[0].XoffKH;
+    measure[0].Yfix   = measure[0].YoffKH;
+
+    found ++;
+  }
+
+  if (found) {
+    fprintf (stderr, "found "OFF_T_FMT" matches\n", found);
+  }
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/uniphot/src/update_catalog_setgalmodel.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_catalog_setgalmodel.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/update_catalog_setgalmodel.c	(revision 37807)
@@ -0,0 +1,81 @@
+# include "setgalmodel.h"
+
+int update_catalog_setgalmodel (Catalog *catalog) {
+
+  off_t i, j, m;
+
+  // choose this number more judiciously...
+  time_t timeRef = ohana_date_to_sec("2000/01/01,00:00:00");
+
+  Average *average = catalog[0].average;
+
+  if ((catalog[0].Nstarpar_disk > 0) && (catalog[0].Nstarpar == 0)) {
+    fprintf (stderr, "ERROR: failed to load starpar data\n");
+    exit (2);
+  }
+
+  CoordTransform *transform = InitTransform (COORD_CELESTIAL, COORD_GALACTIC);
+
+  // first set the proper motion based on Galactic rotation and solar motion
+  for (i = 0; i < catalog[0].Naverage; i++) {
+
+    { 
+      m = average[i].measureOffset;
+      Measure *measure = &catalog[0].measure[m];
+      for (j = 0; j < average[i].Nmeasure; j++) {
+	measure[j].RoffGAL = 0.0; // XXX divide by dcos? project and calculate?
+	measure[j].DoffGAL = 0.0; // XXX divide by dcos? project and calculate?
+      }
+    }
+
+    if (average[i].Nstarpar == 0) continue;
+
+    m = average[i].starparOffset;
+    StarPar *starpar = &catalog[0].starpar[m];
+
+    // fake or real QSOs are marked with FeH = +/- 100.0
+    if (fabs(starpar->FeH) > 99.0) continue;
+
+    double distance = pow(10.0, 0.2*(starpar->DistMag + 5.0));
+
+    double Lrad = starpar->galLon * RAD_DEG;
+    double Brad = starpar->galLat * RAD_DEG;
+
+    double uL_gal, uB_gal;
+    GalaxyMotionModel_radians(&uL_gal, &uB_gal, Lrad, Brad);
+
+    double uL_sol, uB_sol;
+    SolarMotionModel_radians(&uL_sol, &uB_sol, Lrad, Brad, distance);
+
+    double uL = uL_gal + uL_sol;
+    double uB = uB_gal + uB_sol;
+
+    // XXX: amplify motion to make tests easier:
+    if (TESTING) {
+      uL *= 100.0;
+      uB *= 100.0;
+    }
+    
+    double uR, uD;
+    TransformProperMotion (&uR, &uD, uL, uB, average[i].R, average[i].D, transform);
+
+    starpar->uRA  = uR;
+    starpar->uDEC = uD;
+
+    m = average[i].measureOffset;
+    Measure *measure = &catalog[0].measure[m];
+
+    // use the model uR,uD values to move the expected position based on the reference date;
+    for (j = 0; j < average[i].Nmeasure; j++) {
+
+      double dtime = (measure[j].t - timeRef) / 86400.0 / 365.25;
+
+      // do not modify the original Xccd,Yccd values
+      measure[j].RoffGAL = uR * dtime; // XXX divide by dcos? project and calculate?
+      measure[j].DoffGAL = uD * dtime; // XXX divide by dcos? project and calculate?
+    }
+  }
+
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/uniphot/src/update_dvo_setastrom.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_dvo_setastrom.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/update_dvo_setastrom.c	(revision 37807)
@@ -1,5 +1,5 @@
 # include "setastrom.h"
 
-int update_dvo_setastrom (char *corrfile) {
+int update_dvo_setastrom () {
 
   SkyTable *sky = NULL;
@@ -13,9 +13,10 @@
   
   if (PARALLEL && !HOST_ID) {
-    int status = update_dvo_setastrom_parallel (sky, corrfile);
+    int status = update_dvo_setastrom_parallel (sky);
     return status;
   }
 
-  load_astrom_correction (corrfile);
+  if (KH_FILE)  load_kh_correction (KH_FILE);
+  if (DCR_FILE) load_dcr_correction (DCR_FILE);
 
   // determine the populated SkyRegions overlapping the requested area (default depth)
@@ -78,9 +79,7 @@
 # define DEBUG 1
 
-int update_dvo_setastrom_parallel (SkyTable *sky, char *corrfile) {
+int update_dvo_setastrom_parallel (SkyTable *sky) {
 
   // now launch the setastrom_client jobs to the parallel hosts
-
-  char *abscorrfile = abspath (corrfile, DVO_MAX_PATH);
 
   // load the list of hosts
@@ -96,14 +95,19 @@
 
     char command[1024];
-    snprintf (command, 1024, "setastrom_client %s -hostID %d -catdir %s -hostdir %s -region %f %f %f %f", 
-	      abscorrfile, table->hosts[i].hostID, CATDIR, table->hosts[i].pathname, 
+    snprintf (command, 1024, "setastrom_client -hostID %d -catdir %s -hostdir %s -region %f %f %f %f", 
+	      table->hosts[i].hostID, CATDIR, table->hosts[i].pathname, 
 	      UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax);
-
-    fprintf (stderr, "command: %s\n", command);
 
     char tmpline[1024];
     if (VERBOSE)       	  { snprintf (tmpline, 1024, "%s -v",                	command); 		    strcpy (command, tmpline); }
     if (UPDATE)        	  { snprintf (tmpline, 1024, "%s -update",           	command); 		    strcpy (command, tmpline); }
+    if (KH_FILE)      	  { snprintf (tmpline, 1024, "%s -KH %s",           	command, KH_FILE);	    strcpy (command, tmpline); }
+    if (DCR_FILE)      	  { snprintf (tmpline, 1024, "%s -DCR %s",           	command, DCR_FILE);	    strcpy (command, tmpline); }
     if (UPDATE_CATFORMAT) { snprintf (tmpline, 1024, "%s -update-catformat %s", command, UPDATE_CATFORMAT); strcpy (command, tmpline); }
+
+    if (KH_RESET)      	  { snprintf (tmpline, 1024, "%s -KH-reset",           	command);	            strcpy (command, tmpline); }
+    if (DCR_RESET)     	  { snprintf (tmpline, 1024, "%s -DCR-reset",           command);	            strcpy (command, tmpline); }
+
+    fprintf (stderr, "command: %s\n", command);
 
     if (PARALLEL_MANUAL) continue;
@@ -126,5 +130,4 @@
     }
   }
-  free (abscorrfile);
 
   // wait for the remote jobs to be completed
Index: /trunk/Ohana/src/uniphot/src/update_dvo_setgalmodel.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_dvo_setgalmodel.c	(revision 37807)
+++ /trunk/Ohana/src/uniphot/src/update_dvo_setgalmodel.c	(revision 37807)
@@ -0,0 +1,137 @@
+# include "setgalmodel.h"
+
+int update_dvo_setgalmodel (void) {
+
+  SkyTable *sky = NULL;
+  SkyList *skylist = NULL;
+  Catalog catalog;
+  off_t i;
+
+  // load the current sky table (layout of all SkyRegions) 
+  sky = SkyTableLoadOptimal (CATDIR, NULL, NULL, FALSE, -1, VERBOSE);
+  SkyTableSetFilenames (sky, CATDIR, "cpt");
+  
+  if (PARALLEL && !HOST_ID) {
+    int status = update_dvo_setgalmodel_parallel (sky);
+    return status;
+  }
+
+  // determine the populated SkyRegions overlapping the requested area (default depth)
+  if (SINGLE_CPT) {
+      skylist = SkyRegionByCPT (sky, SINGLE_CPT);
+  } else {
+      skylist = SkyListByPatch (sky, -1, &UserPatch);
+  }
+  myAssert (skylist, "ooops!");
+
+  // update measurements for each populated catalog
+  for (i = 0; i < skylist[0].Nregions; i++) {
+
+    // does this host ID match the desired location for the table?
+    if (!HostTableTestHost(skylist[0].regions[i], HOST_ID)) continue;
+
+    char localFilename[1024];
+    snprintf (localFilename, 1024, "%s/%s.cpt", HOSTDIR, skylist->regions[i]->name);
+
+    // set up the basic catalog info
+    catalog.filename  = HOST_ID ? localFilename : skylist[0].filename[i];
+    catalog.catformat = DVO_FORMAT_UNDEF; // not needed since we skip empty catalogs
+    catalog.catmode   = DVO_MODE_UNDEF;	  // not needed since we skip empty catalogs
+    catalog.catflags  = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF | LOAD_STARPAR;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+
+    if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE, "w")) {
+      fprintf (stderr, "ERROR: failure reading catalog %s\n", catalog.filename);
+      exit (1);
+    }
+    if (!catalog.Naves_disk) {
+      if (VERBOSE) fprintf (stderr, "no data in %s, skipping\n", catalog.filename);
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+
+    update_catalog_setgalmodel (&catalog);
+
+    if (!UPDATE) {
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+    
+    // modify the output format as desired (ignore current format on disk)
+    if (UPDATE_CATFORMAT) {
+      catalog.catformat = dvo_catalog_catformat (UPDATE_CATFORMAT);
+    }
+
+    if (VERBOSE) fprintf (stderr, "saving catalog %s\n", catalog.filename);
+    
+    dvo_catalog_save (&catalog, VERBOSE); 
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+  }
+  return (TRUE);
+}      
+
+# define DEBUG 1
+
+int update_dvo_setgalmodel_parallel (SkyTable *sky) {
+
+  // now launch the setgalmodel_client jobs to the parallel hosts
+
+  // load the list of hosts
+  HostTable *table = HostTableLoad (CATDIR, sky->hosts);
+
+  int i;
+  for (i = 0; i < table->Nhosts; i++) {
+
+    // ensure that the paths are absolute path names
+    char *tmppath = abspath (table->hosts[i].pathname, DVO_MAX_PATH);
+    free (table->hosts[i].pathname);
+    table->hosts[i].pathname = tmppath;
+
+    char command[1024];
+    snprintf (command, 1024, "setgalmodel_client -hostID %d -catdir %s -hostdir %s -region %f %f %f %f", 
+	      table->hosts[i].hostID, CATDIR, table->hosts[i].pathname, 
+	      UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax);
+
+    fprintf (stderr, "command: %s\n", command);
+
+    char tmpline[1024];
+    if (VERBOSE)       	  { snprintf (tmpline, 1024, "%s -v",                	command); 		    strcpy (command, tmpline); }
+    if (TESTING)       	  { snprintf (tmpline, 1024, "%s -testing",           	command); 		    strcpy (command, tmpline); }
+    if (UPDATE)        	  { snprintf (tmpline, 1024, "%s -update",           	command); 		    strcpy (command, tmpline); }
+    if (UPDATE_CATFORMAT) { snprintf (tmpline, 1024, "%s -update-catformat %s", command, UPDATE_CATFORMAT); strcpy (command, tmpline); }
+
+    if (PARALLEL_MANUAL) continue;
+
+    if (PARALLEL_SERIAL) {
+      int status = system (command);
+      if (status) {
+	fprintf (stderr, "ERROR running setgalmodel_client\n");
+	exit (2);
+      }
+    } else {
+      // launch the job on the remote machine (no handshake)
+      int errorInfo = 0;
+      int pid = rconnect ("ssh", table->hosts[i].hostname, command, table->hosts[i].stdio, &errorInfo, FALSE);
+      if (!pid) {
+	if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", table->hosts[i].hostname, errorInfo);
+	exit (1);
+      }
+      table->hosts[i].pid = pid; // save for future reference
+    }
+  }
+
+  // wait for the remote jobs to be completed
+  if (PARALLEL_MANUAL) {
+    fprintf (stderr, "run the setgalmodel_client commands above.  when these are done, hit return\n");
+    getchar();
+  }
+  if (!PARALLEL_MANUAL && !PARALLEL_SERIAL) {
+    int status = HostTableWaitJobsGetIO (table, __FILE__, __LINE__, VERBOSE);
+    if (!status) return FALSE;
+  }
+
+  return (TRUE);
+}      
Index: /trunk/Ohana/src/uniphot/src/update_dvo_uniphot.c
===================================================================
--- /trunk/Ohana/src/uniphot/src/update_dvo_uniphot.c	(revision 37806)
+++ /trunk/Ohana/src/uniphot/src/update_dvo_uniphot.c	(revision 37807)
@@ -57,9 +57,5 @@
   glob (line, 0, NULL, &pglob);
 
-  coords.crpix1 = coords.crpix2 = 0.0;
-  coords.cdelt1 = coords.cdelt2 = 1.0; /* scale is degrees, radius in degrees */
-  coords.pc1_1 = coords.pc2_2 = 1.0;
-  coords.pc1_2 = coords.pc2_1 = 0.0;
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
   
   for (i = 0; i < pglob.gl_pathc; i++) {
