Index: /trunk/Ohana/src/addstar/Makefile
===================================================================
--- /trunk/Ohana/src/addstar/Makefile	(revision 29937)
+++ /trunk/Ohana/src/addstar/Makefile	(revision 29938)
@@ -72,4 +72,6 @@
 $(SRC)/replace_match.$(ARCH).o \
 $(SRC)/resort_catalog.$(ARCH).o \
+$(SRC)/resort_threaded.$(ARCH).o \
+$(SRC)/resort_unthreaded.$(ARCH).o \
 $(SRC)/StarOps.$(ARCH).o \
 $(SRC)/ReadStarsFITS.$(ARCH).o \
Index: /trunk/Ohana/src/addstar/doc/resort_catalog.alloc.c
===================================================================
--- /trunk/Ohana/src/addstar/doc/resort_catalog.alloc.c	(revision 29938)
+++ /trunk/Ohana/src/addstar/doc/resort_catalog.alloc.c	(revision 29938)
@@ -0,0 +1,158 @@
+# include "addstar.h"
+
+void resort_catalog_old (Catalog *catalog) {
+
+  off_t *next_meas;
+  off_t Naves, Nmeas;
+  double dtime;
+  struct timeval start, stop;
+
+  if (catalog[0].sorted == TRUE) return;
+
+  gettimeofday (&start, NULL);
+
+  /* internal counters */
+  Nmeas = catalog[0].Nmeasure;
+  Naves = catalog[0].Naverage;
+  
+  /* set up pointers for linked list of measure, missing */
+  next_meas = build_measure_links (catalog[0].average, Naves, catalog[0].measure, Nmeas);
+
+  catalog[0].sorted = TRUE;
+  catalog[0].measure = sort_measure (catalog[0].average, Naves, catalog[0].measure, Nmeas, next_meas);
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "  match time %9.4f sec for %7lld measures, %6lld average\n", dtime, (long long) Nmeas, (long long) Naves);
+
+  return;
+}
+
+# define myAbort(MSG) { fprintf (stderr, "%s\n", MSG); abort(); }
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
+// sort the measure Sequence based on the average Sequence entries
+void SortAveMeasMatch (off_t *MEAS, off_t *AVE, off_t N) {
+
+# define SWAPFUNC(A,B){ off_t tmp_meas; off_t tmp_ave;	\
+    tmp_meas = MEAS[A]; MEAS[A] = MEAS[B]; MEAS[B] = tmp_meas;		\
+    tmp_ave  = AVE[A];  AVE[A]  = AVE[B];  AVE[B]  = tmp_ave;		\
+  }
+# define COMPARE(A,B)(MEAS[A] < MEAS[B])
+  OHANA_SORT (N, COMPARE, SWAPFUNC);
+# undef SWAPFUNC
+# undef COMPARE
+}
+
+# define MARKTIME(MSG,...) { \
+  float dtime; \
+  gettimeofday (&stop, (void *) NULL); \
+  dtime = DTIME (stop, start); start = stop; \
+  fprintf (stderr, MSG, __VA_ARGS__); }
+
+// XXX : where is the time going?  perhaps the ALLOCATE?
+// XXX : I don't thnk his is getting the right answer yet.
+
+static int NMEASURE = 0;
+static off_t *measureSeq = NULL;
+static off_t *averageSeq = NULL;
+static Measure *measureTMP = NULL;
+
+void resort_catalog (Catalog *catalog) {
+
+  off_t Naverage, Nmeasure;
+  Measure *measure;
+  Average *average;
+  off_t i, j, N, currentAve;
+
+  struct timeval start, stop;
+
+  if (catalog[0].sorted == TRUE) return;
+
+  gettimeofday (&start, NULL);
+
+  /* internal counters */
+  Nmeasure = catalog[0].Nmeasure;
+  Naverage = catalog[0].Naverage;
+
+  measure = catalog[0].measure;
+  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)
+  // measure[i].averef -> average[averef]
+  // measure[i].objID = average[averef].objID
+  // measure[i].catID = average[averef].catID
+
+  // we want a sorted measure array with all averef entries in sequence
+
+  if (!measureSeq) {
+    NMEASURE = MAX(Nmeasure, 1000);
+    ALLOCATE (measureSeq, off_t,   NMEASURE);
+    ALLOCATE (averageSeq, off_t,   NMEASURE);
+    ALLOCATE (measureTMP, Measure, NMEASURE);
+  }    
+
+  if (Nmeasure > NMEASURE) {
+    NMEASURE = Nmeasure;
+    REALLOCATE (measureSeq, off_t,   NMEASURE);
+    REALLOCATE (averageSeq, off_t,   NMEASURE);
+    REALLOCATE (measureTMP, Measure, NMEASURE);
+  }
+  
+  // MARKTIME("array allocation: %f sec\n", dtime);
+
+  for (i = 0; i < Nmeasure; i++) {
+    measureSeq[i] = i;
+    averageSeq[i] = measure[i].averef;
+    
+    myAssert(average[averageSeq[i]].objID == measure[measureSeq[i]].objID, "object / detection mismatch");
+    myAssert(average[averageSeq[i]].catID == measure[measureSeq[i]].catID, "object / detection mismatch");
+  }
+  
+  // MARKTIME("create index: %f sec\n", dtime);
+
+  SortAveMeasMatch(measureSeq, averageSeq, Nmeasure);
+  
+  // MARKTIME("sort : %f sec\n", dtime);
+
+  // copy the measurements in the sorted order
+  for (i = 0; i < Nmeasure; i++) {
+    j = measureSeq[i];
+    measureTMP[i] = measure[j];
+  }
+
+  // save the sorted list in the correct order
+  for (i = 0; i < Nmeasure; i++) {
+    measure[i] = measureTMP[i];
+  }
+
+  // MARKTIME("assign measure : %f sec\n", dtime);
+
+  // update the values of average.measureOffset and average.Nmeasure
+
+  N = 0;
+  currentAve = averageSeq[0];
+  average[currentAve].measureOffset = 0;
+  for (i = 0; i < Nmeasure; i++) {
+    if (averageSeq[i] != currentAve) {
+      average[currentAve].Nmeasure = N;
+      N = 0;
+      currentAve = averageSeq[i];
+      average[currentAve].measureOffset = i;
+    }
+    N++;
+  }
+  N++;
+  average[currentAve].Nmeasure = N;
+
+  // MARKTIME("update Nmeasure : %f sec\n", dtime);
+
+  MARKTIME("  match time %9.4f sec for %7lld measures, %6lld average\n", dtime, (long long) Nmeasure, (long long) Naverage);
+
+  // FREE (measureSeq);
+  // FREE (averageSeq);
+
+  return;
+}
+
Index: /trunk/Ohana/src/addstar/include/addstar.h
===================================================================
--- /trunk/Ohana/src/addstar/include/addstar.h	(revision 29937)
+++ /trunk/Ohana/src/addstar/include/addstar.h	(revision 29938)
@@ -125,4 +125,6 @@
 float   ZPT_ERR_PHU;
 
+int     OLD_RESORT;
+
 // carries the mosaic into gstars
 
@@ -135,4 +137,5 @@
 double MIN_FWHM_X;
 double MIN_FWHM_Y;
+int    NTHREADS;
 
 /* modify server behavior (make this an addstar cleanup mode?) */
@@ -207,5 +210,8 @@
 Stars     *rd_gsc                 PROTO((char *filename, unsigned int *nstars));
 int        replace_match          PROTO((Average *average, Measure *measure, Stars *star));
+int        resort_threaded        PROTO((AddstarClientOptions *options, SkyTable *sky));
+int        resort_unthreaded      PROTO((AddstarClientOptions *options, SkyTable *sky));
 void       resort_catalog         PROTO((Catalog *catalog));
+void       resort_catalog_old     PROTO((Catalog *catalog));
 Stars     *ReadStarsFITS          PROTO((FILE *f, Header *header, Header *in_theader, unsigned int *nstars));
 Stars     *ReadStarsTEXT          PROTO((FILE *f, unsigned int *nstars));
Index: /trunk/Ohana/src/addstar/src/ReadStarsFITS.c
===================================================================
--- /trunk/Ohana/src/addstar/src/ReadStarsFITS.c	(revision 29937)
+++ /trunk/Ohana/src/addstar/src/ReadStarsFITS.c	(revision 29938)
@@ -22,5 +22,5 @@
 
   /* load the table data */
-  if (!gfits_fread_ftable_data (f, &table)) {
+  if (!gfits_fread_ftable_data (f, &table, FALSE)) {
     fprintf (stderr, "ERROR: can't read table header\n");
     exit (1);
Index: /trunk/Ohana/src/addstar/src/ReadStarsSDSS.c
===================================================================
--- /trunk/Ohana/src/addstar/src/ReadStarsSDSS.c	(revision 29937)
+++ /trunk/Ohana/src/addstar/src/ReadStarsSDSS.c	(revision 29938)
@@ -60,5 +60,5 @@
 
   /* load the table data */
-  if (!gfits_fread_ftable_data (f, &table)) {
+  if (!gfits_fread_ftable_data (f, &table, FALSE)) {
     fprintf (stderr, "ERROR: can't read table header\n");
     exit (1);
Index: /trunk/Ohana/src/addstar/src/addstar.c
===================================================================
--- /trunk/Ohana/src/addstar/src/addstar.c	(revision 29937)
+++ /trunk/Ohana/src/addstar/src/addstar.c	(revision 29938)
@@ -34,4 +34,13 @@
   SkyTableSetFilenames (sky, CATDIR, "cpt");
   
+  if (options.mode == M_RESORT) {
+    if (NTHREADS == 0) {
+      resort_unthreaded (&options, sky);
+    } else {
+      resort_threaded (&options, sky);
+    }
+    exit (0);
+  }
+
   stars = NULL;
 
@@ -157,5 +166,9 @@
 	}
 
-	resort_catalog (&catalog);
+	if (OLD_RESORT) { 
+	  resort_catalog_old (&catalog);
+	} else {
+	  resort_catalog (&catalog);
+	}
 	Nsubset = 1;
 	break;
Index: /trunk/Ohana/src/addstar/src/args.c
===================================================================
--- /trunk/Ohana/src/addstar/src/args.c	(revision 29937)
+++ /trunk/Ohana/src/addstar/src/args.c	(revision 29938)
@@ -34,4 +34,9 @@
     remove_argument (N, &argc, argv);
   }
+  OLD_RESORT = FALSE;
+  if ((N = get_argument (argc, argv, "-old-resort"))) {
+    remove_argument (N, &argc, argv);
+    OLD_RESORT = TRUE;
+  }
   if ((N = get_argument (argc, argv, "-fakeimage"))) {
     options.mode = M_FAKEIMAGE;
@@ -62,4 +67,12 @@
     remove_argument (N, &argc, argv);
     PMM_CCD_TABLE = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  // number of worker threads for resort (must have at least one worker thread)
+  NTHREADS = 0;
+  if ((N = get_argument (argc, argv, "-threads"))) {
+    remove_argument (N, &argc, argv);
+    NTHREADS = MAX(0, atoi (argv[N]));
     remove_argument (N, &argc, argv);
   }
Index: /trunk/Ohana/src/addstar/src/resort_catalog.c
===================================================================
--- /trunk/Ohana/src/addstar/src/resort_catalog.c	(revision 29937)
+++ /trunk/Ohana/src/addstar/src/resort_catalog.c	(revision 29938)
@@ -1,5 +1,5 @@
 # include "addstar.h"
 
-void resort_catalog (Catalog *catalog) {
+void resort_catalog_old (Catalog *catalog) {
 
   off_t *next_meas;
@@ -28,2 +28,109 @@
   return;
 }
+
+# define myAbort(MSG) { fprintf (stderr, "%s\n", MSG); abort(); }
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
+// sort the measure Sequence based on the average Sequence entries
+void SortAveMeasMatch (off_t *MEAS, off_t *AVE, off_t N) {
+
+# define SWAPFUNC(A,B){ off_t tmp_meas; off_t tmp_ave;	\
+    tmp_meas = MEAS[A]; MEAS[A] = MEAS[B]; MEAS[B] = tmp_meas;		\
+    tmp_ave  = AVE[A];  AVE[A]  = AVE[B];  AVE[B]  = tmp_ave;		\
+  }
+# define COMPARE(A,B)(MEAS[A] < MEAS[B])
+  OHANA_SORT (N, COMPARE, SWAPFUNC);
+# undef SWAPFUNC
+# undef COMPARE
+}
+
+# define MARKTIME(MSG,...) { \
+  float dtime; \
+  gettimeofday (&stop, (void *) NULL); \
+  dtime = DTIME (stop, start); start = stop; \
+  fprintf (stderr, MSG, __VA_ARGS__); }
+
+// XXX : where is the time going?  perhaps the ALLOCATE?
+// XXX : I don't thnk his is getting the right answer yet.
+
+void resort_catalog (Catalog *catalog) {
+
+  off_t Naverage, Nmeasure;
+  Measure *measure;
+  Average *average;
+  off_t i, j, N, currentAve;
+
+  off_t *measureSeq = NULL;
+  off_t *averageSeq = NULL;
+  Measure *measureTMP = NULL;
+
+  if (catalog[0].sorted == TRUE) return;
+
+  // struct timeval start, stop;
+  // gettimeofday (&start, NULL);
+
+  /* internal counters */
+  Nmeasure = catalog[0].Nmeasure;
+  Naverage = catalog[0].Naverage;
+
+  measure = catalog[0].measure;
+  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)
+  // measure[i].averef -> average[averef]
+  // measure[i].objID = average[averef].objID
+  // measure[i].catID = average[averef].catID
+
+  // we want a sorted measure array with all averef entries in sequence
+
+  ALLOCATE (measureSeq, off_t,   Nmeasure);
+  ALLOCATE (averageSeq, off_t,   Nmeasure);
+
+  for (i = 0; i < Nmeasure; i++) {
+    measureSeq[i] = i;
+    averageSeq[i] = measure[i].averef;
+    
+    myAssert(average[averageSeq[i]].objID == measure[measureSeq[i]].objID, "object / detection mismatch");
+    myAssert(average[averageSeq[i]].catID == measure[measureSeq[i]].catID, "object / detection mismatch");
+  }
+  
+  SortAveMeasMatch(measureSeq, averageSeq, Nmeasure);
+  // MARKTIME("sort : %f sec\n", dtime);
+
+  // copy the measurements in the sorted order
+  ALLOCATE (measureTMP, Measure, Nmeasure);
+  for (i = 0; i < Nmeasure; i++) {
+    j = measureSeq[i];
+    measureTMP[i] = measure[j];
+  }
+  // MARKTIME("assign measure : %f sec\n", dtime);
+
+  // update the values of average.measureOffset and average.Nmeasure
+  FREE(measure);
+  catalog[0].measure = measureTMP;
+
+  N = 0;
+  currentAve = averageSeq[0];
+  average[currentAve].measureOffset = 0;
+  for (i = 0; i < Nmeasure; i++) {
+    if (averageSeq[i] != currentAve) {
+      average[currentAve].Nmeasure = N;
+      N = 0;
+      currentAve = averageSeq[i];
+      average[currentAve].measureOffset = i;
+    }
+    N++;
+  }
+  N++;
+  average[currentAve].Nmeasure = N;
+  // MARKTIME("update Nmeasure : %f sec\n", dtime);
+
+  // MARKTIME("  match time %9.4f sec for %7lld measures, %6lld average\n", dtime, (long long) Nmeasure, (long long) Naverage);
+
+  FREE (measureSeq);
+  FREE (averageSeq);
+
+  return;
+}
+
Index: /trunk/Ohana/src/addstar/src/resort_threaded.c
===================================================================
--- /trunk/Ohana/src/addstar/src/resort_threaded.c	(revision 29938)
+++ /trunk/Ohana/src/addstar/src/resort_threaded.c	(revision 29938)
@@ -0,0 +1,238 @@
+# include "addstar.h"
+# include <pthread.h>
+
+enum {
+  TS_WAIT,
+  TS_RUN,
+  TS_DONE,
+  TS_FAIL,
+  TS_EXIT,
+};
+
+typedef struct {
+  int iThread;
+  int state;
+  int nosort;
+  char *filename;
+  SkyRegion *region;
+  off_t Naverage;
+  off_t Nmeasure;
+} ThreadData;
+
+// the thread manager has three states: 
+// WAIT : no data is available for this thread
+// RUN  : data is available, attempt to process
+// DONE : completed the analysis, wait for harvest
+// FAIL : serious error resulting in system shutdown
+
+void *resort_thread_job (void *inputData) {
+
+  Catalog catalog;
+  ThreadData *threadData = (ThreadData *) inputData;
+
+  while (TRUE) {
+
+    while (threadData->state != TS_RUN) {
+      usleep (100);
+    }
+
+    // set the parameters which guide catalog open/load/create
+    catalog.filename  = threadData->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_MEAS;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+  
+    // an error exit status here is a significant error (disk I/O or file access)
+    if (!dvo_catalog_open (&catalog, threadData->region, VERBOSE, "w")) {
+      fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+      threadData->Naverage = 0;
+      threadData->Nmeasure = 0;
+      threadData->state = TS_FAIL;
+      continue;
+    }
+
+    // Naves_disk == 0 implies an empty catalog file, skip empty catalogs
+    if (catalog.Naves_disk == 0) {
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      threadData->Naverage = 0;
+      threadData->Nmeasure = 0;
+      threadData->state = TS_DONE;
+      continue;
+    }
+
+    // this is an overloaded value to mean 'force sort'
+    if (threadData->nosort == 3) catalog.sorted = FALSE;
+
+    if (OLD_RESORT) { 
+      resort_catalog_old (&catalog);
+    } else {
+      resort_catalog (&catalog);
+    }
+
+    /* report total updated values */
+    threadData->Naverage = catalog.Naverage;
+    threadData->Nmeasure = catalog.Nmeasure;
+    // fprintf (stderr, "done in thread %d, %d aves, %d meas\n", threadData->iThread, (int) threadData->Naverage, (int) threadData->Nmeasure);
+
+    // write out catalog, if appropriate
+    SetProtect (TRUE);
+    dvo_catalog_save (&catalog, VERBOSE);
+    SetProtect (FALSE);
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+    threadData->state = TS_DONE;
+  }
+  return NULL;
+}
+
+int resort_threaded (AddstarClientOptions *options, SkyTable *sky) {
+
+  int j, launched, done, Nwait, Nrun;
+
+  off_t i;
+  off_t Naverage, Nmeasure;
+
+  ThreadData *threadData;
+  pthread_t *threads;
+
+  double dtime;
+  struct timeval start, stop;
+  gettimeofday (&start, NULL);
+
+  SkyList *skylist = SkyListByPatch (sky, -1, &UserPatch);
+  
+  // in these cases, limit the sky catalogs to an existing subset
+  if (options->existing_regions) {
+    SkyList *tmp;
+    tmp = SkyListExistingSubset (skylist, CATDIR);
+    SkyListFree (skylist);
+    skylist = tmp;
+  }
+  if (VERBOSE) fprintf (stderr, "writing to "OFF_T_FMT" regions\n",  skylist[0].Nregions);
+
+  if (options->only_images) {
+    fprintf (stderr, "-image (only images) makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->only_match) {
+    fprintf (stderr, "-only-match makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->nosort == 1) {
+    fprintf (stderr, "-nosort makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->update) {
+    fprintf (stderr, "-update makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->calibrate) {
+    fprintf (stderr, "-cal (calibrate) makes no sense with -resort\n");
+    exit (2);
+  }
+
+  ALLOCATE(threads, pthread_t, NTHREADS);
+  ALLOCATE(threadData, ThreadData, NTHREADS);
+  for (i = 0; i < NTHREADS; i++) {
+    threadData[i].state = TS_WAIT;
+    threadData[i].iThread = i;
+  }
+  for (i = 0; i < NTHREADS; i++) {
+    pthread_create (&threads[i], NULL, &resort_thread_job, (void *) &threadData[i]);
+  }
+
+  // if we catch a failure at any point, goto the failure block and wait for threads to finish
+  // continue in this loop until all regions have been launched (though not finished)
+  Naverage = Nmeasure = 0;
+  for (i = 0; i < skylist[0].Nregions; i++) {
+
+    launched = FALSE;
+    while (!launched) {
+
+      // are any threads done? can we harvest them?
+      for (j = 0; j < NTHREADS; j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_DONE) continue;
+	Naverage += threadData[j].Naverage;
+	Nmeasure += threadData[j].Nmeasure;
+	// fprintf (stderr, "harvested thread %d, %d aves, %d meas (sums: %d aves, %d meas)\n", threadData[j].iThread, (int) threadData[j].Naverage, (int) threadData[j].Nmeasure, (int) Naverage, (int) Nmeasure);
+	threadData[j].state = TS_WAIT;
+      }
+	
+      // are any threads available? can we launch this job?
+      for (j = 0; !launched && (j < NTHREADS); j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_WAIT) continue;
+	threadData[j].nosort = options->nosort;
+	threadData[j].filename = skylist[0].filename[i];
+	threadData[j].region = skylist[0].regions[i];
+	threadData[j].state = TS_RUN;
+	launched = TRUE;
+      }
+      if (!launched) usleep (100);
+    }
+  }
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+    // are any threads done? can we harvest them?
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_DONE) continue;
+      Naverage += threadData[j].Naverage;
+      Nmeasure += threadData[j].Nmeasure;
+      threadData[j].state = TS_WAIT;
+    }
+
+    // how many are waiting now?
+    Nwait = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_WAIT) continue;
+      Nwait ++;
+    }
+
+    if (Nwait < NTHREADS) { 
+      usleep (100); 
+    } else {
+      done = TRUE;
+    }
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec for, "OFF_T_FMT" average, "OFF_T_FMT" measure\n", dtime, Naverage, Nmeasure);
+
+  // for (i = 0; i < NTHREADS; i++) {
+  //   pthread_cancel(threads[i]);
+  // }
+  // free (threads);
+  free (threadData);
+
+  return TRUE;
+
+failure:
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+
+    // how many are still running?
+    Nrun = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state != TS_RUN) continue;
+      Nrun ++;
+    }
+
+    if (Nrun) { 
+      usleep (100); 
+    } else {
+      done = TRUE;
+    }
+  }
+  exit (1);
+}
+
Index: /trunk/Ohana/src/addstar/src/resort_unthreaded.c
===================================================================
--- /trunk/Ohana/src/addstar/src/resort_unthreaded.c	(revision 29938)
+++ /trunk/Ohana/src/addstar/src/resort_unthreaded.c	(revision 29938)
@@ -0,0 +1,105 @@
+# include "addstar.h"
+
+int resort_unthreaded (AddstarClientOptions *options, SkyTable *sky) {
+
+  off_t i;
+  off_t Naverage, Nmeasure;
+  Catalog catalog;
+
+  double dtime;
+  struct timeval start, stop;
+
+  gettimeofday (&start, NULL);
+
+  SkyList *skylist = SkyListByPatch (sky, -1, &UserPatch);
+  
+  // in these cases, limit the sky catalogs to an existing subset
+  if (options->existing_regions) {
+    SkyList *tmp;
+    tmp = SkyListExistingSubset (skylist, CATDIR);
+    SkyListFree (skylist);
+    skylist = tmp;
+  }
+  if (VERBOSE) fprintf (stderr, "writing to "OFF_T_FMT" regions\n",  skylist[0].Nregions);
+
+  if (options->only_images) {
+    fprintf (stderr, "-image (only images) makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->only_match) {
+    fprintf (stderr, "-only-match makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->nosort == 1) {
+    fprintf (stderr, "-nosort makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->update) {
+    fprintf (stderr, "-update makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->calibrate) {
+    fprintf (stderr, "-cal (calibrate) makes no sense with -resort\n");
+    exit (2);
+  }
+
+  // XXX ALLOCATE catalog[Nthreads]
+
+  /* match stars to existing catalog data (or otherwise manipulate catalog data) */
+  Naverage = Nmeasure = 0;
+  for (i = 0; i < skylist[0].Nregions; i++) {
+
+    // XXX BLOCK here for an empty thread
+
+    // 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_MEAS;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+
+    // an error exit status here is a significant error (disk I/O or file access)
+    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);
+    }
+
+    // Naves_disk == 0 implies an empty catalog file, skip empty catalogs
+    if (catalog.Naves_disk == 0) {
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+
+    // this is an overloaded value to mean 'force sort'
+    if (options->nosort == 3) catalog.sorted = FALSE;
+
+    // XXX send these to a free thread
+
+    if (OLD_RESORT) { 
+      resort_catalog_old (&catalog);
+    } else {
+      resort_catalog (&catalog);
+    }
+
+    // XXX wait for completed threads
+
+    /* report total updated values */
+    Naverage += catalog.Naverage;
+    Nmeasure += catalog.Nmeasure;
+
+    // write out catalog, if appropriate
+    SetProtect (TRUE);
+    dvo_catalog_save (&catalog, VERBOSE);
+    SetProtect (FALSE);
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec for, "OFF_T_FMT" average, "OFF_T_FMT" measure\n", dtime, Naverage, Nmeasure);
+
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/addstar/test/dvomerge.dvo
===================================================================
--- /trunk/Ohana/src/addstar/test/dvomerge.dvo	(revision 29937)
+++ /trunk/Ohana/src/addstar/test/dvomerge.dvo	(revision 29938)
@@ -4,5 +4,5 @@
 
 # create 2 populated catdirs, each with a couple of cmf files
-macro test.dvomerge.update
+macro test.dvomerge.continue
 
   tapPLAN 51
@@ -16,4 +16,123 @@
 
   mkinput
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 01:00:00 -radec 10.0 20.0
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 02:00:00 -radec 10.0 20.0
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 03:00:00 -radec 9.9 20.0
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 04:00:00 -radec 9.9 20.0
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 05:00:00 -radec 10.0 19.9
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 06:00:00 -radec 10.0 19.9
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 07:00:00 -radec 9.9 19.9
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 08:00:00 -radec 9.9 19.9
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  break
+
+  exec rsync -auc catdir.test2/ catdir.test3/
+
+  date -var t1 -seconds -reftime 1276000000
+  exec dvomerge catdir.test1 into catdir.test3
+  date -var t2 -seconds -reftime 1276000000
+  echo "merge time: {$t2 - $t1}"
+
+  catdir catdir.test3
+  skyregion {$RA-1} {$RA+1} {$DEC-1} {$DEC+1} 
+  mextract ra dec mag
+  create n 0 ra[]
+  subset r0 = ra if (n % 4 == 0)
+  subset r1 = ra if (n % 4 == 1)
+  subset r2 = ra if (n % 4 == 2)
+  subset r3 = ra if (n % 4 == 3)
+
+  catdir catdir.test1/
+  mextract RA DEC MAG
+  create N 0 RA[]
+  subset R0 = RA if (N % 2 == 0)
+  subset R1 = RA if (N % 2 == 1)
+
+  set dr0 = r0 - R0
+  vstat -q dr0
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  set dr1 = r1 - R1
+  vstat -q dr1
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  catdir catdir.test2/
+  mextract RA DEC MAG
+  create N 0 RA[]
+  subset R2 = RA if (N % 2 == 0)
+  subset R3 = RA if (N % 2 == 1)
+
+  set dr2 = r2 - R2
+  vstat -q dr2
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  set dr3 = r3 - R3
+  vstat -q dr3
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  # check on updates to imageID
+  catdir catdir.test3
+  imextract imageID
+  sort imageID
+  tapOK {imageID[]  == 4} "image IDs exist"
+  tapOK {imageID[0] == 1} "updated image IDs"
+  tapOK {imageID[1] == 2} "updated image IDs"
+  tapOK {imageID[2] == 3} "updated image IDs"
+  tapOK {imageID[3] == 4} "updated image IDs"
+
+  catdir catdir.test3
+  mextract imageID, time
+  set id = imageID
+  set t = time
+  imextract imageID, time
+
+  for i 0 time[]
+    subset T = t if (id == imageID[$i])
+    set dT = T - time[$i]
+    vstat dT 
+    tapOK {abs($MEAN)  < 0.00001} "time for measure ID $i (MEAN)"
+    tapOK {abs($SIGMA) < 0.00001} "time for measure ID $i (SIGMA)"
+  end
+
+  # exec rm test.in.txt test.cmf
+  # exec rm -rf catdir.test1
+  # exec rm -rf catdir.test2
+  # exec rm -rf catdir.test3
+
+  tapDONE
+end
+
+# create 2 populated catdirs, each with a couple of cmf files
+macro test.dvomerge.update
+
+  tapPLAN 51
+
+  exec rm -rf catdir.test1
+  exec rm -rf catdir.test2
+  exec rm -rf catdir.test3
+
+  $RA = 10.0
+  $DEC = 20.0
+
+  mkinput
   exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 01:00:00 -radec $RA $DEC
   exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
@@ -27,4 +146,6 @@
   exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 04:00:00 -radec $RA $DEC
   exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  break
 
   exec rsync -auc catdir.test2/ catdir.test3/
Index: /trunk/Ohana/src/dvomerge/Makefile
===================================================================
--- /trunk/Ohana/src/dvomerge/Makefile	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/Makefile	(revision 29938)
@@ -22,8 +22,13 @@
 all: dvomerge dvoconvert dvosecfilt
 
+#  $(SRC)/dvomergeContinue.$(ARCH).o
+
 DVOMERGE = \
 $(SRC)/dvomerge.$(ARCH).o \
 $(SRC)/dvomergeUpdate.$(ARCH).o \
 $(SRC)/dvomergeCreate.$(ARCH).o \
+$(SRC)/dvomergeUpdate_threaded.$(ARCH).o \
+$(SRC)/dvomergeFromList.$(ARCH).o \
+$(SRC)/dvomergeImageIDs.$(ARCH).o \
 $(SRC)/dvo_image_merge_dbs.$(ARCH).o \
 $(SRC)/SetSignals.$(ARCH).o \
@@ -70,5 +75,29 @@
 $(BIN)/dvosecfilt.$(ARCH) : $(DVOSECFILT)
 
-INSTALL = dvomerge dvoconvert dvosecfilt
+DVOREPAIR = \
+$(SRC)/dvorepair.$(ARCH).o \
+$(SRC)/dvorepairFixCPT.$(ARCH).o \
+$(SRC)/dvorepairImagesVsMeasures.$(ARCH).o \
+$(SRC)/dvorepairDeleteImageList.$(ARCH).o \
+$(SRC)/dvorepairFixImages.$(ARCH).o \
+$(SRC)/psps_ids.$(ARCH).o \
+$(SRC)/LoadImages.$(ARCH).o \
+$(SRC)/ReadDeleteList.$(ARCH).o \
+$(SRC)/match_image.$(ARCH).o \
+$(SRC)/help.$(ARCH).o \
+$(SRC)/ImageOps.$(ARCH).o
+
+$(DVOREPAIR)  : $(INC)/dvomerge.h
+
+$(BIN)/dvorepair.$(ARCH) : $(DVOREPAIR)
+
+DVOVERIFY = \
+$(SRC)/dvoverify.$(ARCH).o
+
+$(DVOVERIFY)  : $(INC)/dvomerge.h
+
+$(BIN)/dvoverify.$(ARCH) : $(DVOVERIFY)
+
+INSTALL = dvomerge dvoconvert dvosecfilt dvorepair dvoverify
 
 # dependancy rules for binary code #########################
Index: /trunk/Ohana/src/dvomerge/doc/notes.txt
===================================================================
--- /trunk/Ohana/src/dvomerge/doc/notes.txt	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/doc/notes.txt	(revision 29938)
@@ -0,0 +1,24 @@
+
+2010.11.08 : DVO repairs
+
+  * I have made a dvorepair program that reconstructs cpt files from a valid cpm file
+
+  * the ThreePi.V1 database got busted; I think we have the following issues:
+
+    * cpt files which are short (and can be fixed with the command above)
+
+    * Image.dat has many image that have no matching detections (repeated dvomerge runs after the failure)
+
+    * a number of cpm files which are short : in this case, I believe
+      these files have all detections before the failure, but are
+      missing some of the detections from the failure. 
+
+  * repair step 1 : scan image IDs on the database : count the number
+    of detections for each image ID and find those image IDs which are
+    missing detections
+
+  * remove images that are missing their detections
+
+  * create repaired cpm files excluding the now-orphaned detections
+
+
Index: /trunk/Ohana/src/dvomerge/include/dvomerge.h
===================================================================
--- /trunk/Ohana/src/dvomerge/include/dvomerge.h	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/include/dvomerge.h	(revision 29938)
@@ -17,4 +17,7 @@
 # include <glob.h>
 
+# define myAbort(MSG) { fprintf (stderr, "%s\n", MSG); abort(); }
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
 int    VERBOSE;
 char   CATDIR[256];
@@ -25,4 +28,5 @@
 float  RADIUS;
 int    SKY_DEPTH;
+int    NTHREADS;
 char   *ALTERNATE_PHOTCODE_FILE;
 
@@ -65,5 +69,5 @@
 SkyList   *SkyTablePopulatedList_old  PROTO((SkyTable *sky, off_t Ns, off_t Ne));
 
-int        LoadCatalog            PROTO((Catalog *catalog, SkyRegion *region, char *filename, char *mode));
+int        LoadCatalog            PROTO((Catalog *catalog, SkyRegion *region, char *filename, char *mode, int Nsecfilt));
 
 int        merge_catalogs_new     PROTO((SkyRegion *region, Catalog *output, Catalog *input, int *secflitMap));
@@ -86,2 +90,25 @@
 off_t 	   dvo_map_image_ID       PROTO((IDmapType *IDmap, off_t oldID));
 int   	   dvo_update_image_IDs   PROTO((IDmapType *IDmap, Catalog *catalog));
+
+// dvorepair prototypes
+Image     *LoadImages         	  PROTO((FITS_DB *db, char *filename, off_t *Nimage));
+Image     *MatchImage         	  PROTO((Image *image, off_t Nimage, unsigned int time, short int source, unsigned int imageID));
+off_t      match_image        	  PROTO((Image *image, off_t Nimage, unsigned int T, short int S));
+
+int        SaveImages             PROTO((FITS_DB *oldDB, char *filename, Image *imagesOut, off_t Nout));
+
+int 	   dvorepairFixCPT        PROTO((int argc, char **argv));
+int 	   dvorepairImagesVsMeasures PROTO((int argc, char **argv));
+int 	   dvorepairDeleteImageList PROTO((int argc, char **argv));
+int 	   dvorepairFixImages     PROTO((int argc, char **argv));
+
+int       *ReadDeleteList         PROTO((char *filename, int *nindex));
+int 	   RepairTableCPT         PROTO((char *cptFilenameSrc, char *cptFilenameTgt, char *cpsFilenameSrc, char *cpsFilenameTgt, Measure *measure, off_t Nmeasure, Image *image, off_t Nimage, char catformat));
+void       dvorepair_help         PROTO((int argc, char **argv));
+
+off_t      getTgtIndex            PROTO((e_time start, e_time stop, short photcode, off_t *TgtIndex, e_time *TgtTimes, short *TgtCodes, off_t NimagesTgt));
+void       SortTgtByTimes         PROTO((e_time *S, off_t *I, short *C, off_t N));
+int 	   dvo_image_match_dbs    PROTO((IDmapType *IDmap, FITS_DB *tgt, FITS_DB *src));
+int 	   dvomergeImagesGetMap   PROTO((IDmapType *IDmap, char *input, char *output));
+int        dvomergeFromList       PROTO((int argc, char **argv));
+int 	   dvomergeUpdate_threaded PROTO((int argc, char **argv));
Index: /trunk/Ohana/src/dvomerge/src/ImageOps.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/ImageOps.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/ImageOps.c	(revision 29938)
@@ -0,0 +1,22 @@
+# include "dvomerge.h"
+
+Image *MatchImage (Image *image, off_t Nimage, unsigned int time, short int source, unsigned int imageID) { 
+
+  int m = -1;
+
+  if ((imageID != 0) && (imageID < Nimage)) {
+    // imageID is in range for the array of images. If the table is still in order and
+    // no images have been deleted the index of the image we are looking for will be imageID - 1
+    // If this is the case, we have it. Otherwise we'll have to go search for it below
+    int guess = (int) imageID - 1;
+    if (image[guess].imageID == imageID) {
+        m = guess;
+    }
+  } 
+  if (m == -1) {
+    m = match_image (image, Nimage, time, source);
+  }
+  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 29937)
+++ /trunk/Ohana/src/dvomerge/src/LoadCatalog.c	(revision 29938)
@@ -1,9 +1,9 @@
 # include "dvomerge.h"
 
-int LoadCatalog (Catalog *catalog, SkyRegion *region, char *filename, char *mode) {
+int LoadCatalog (Catalog *catalog, SkyRegion *region, char *filename, char *mode, int Nsecfilt) {
 
     // set the parameters which guide catalog open/load/create
     catalog[0].filename  = filename;
-    catalog[0].Nsecfilt  = GetPhotcodeNsecfilt ();
+    catalog[0].Nsecfilt  = Nsecfilt;
 
     // always load all of the data (if any exists)
Index: /trunk/Ohana/src/dvomerge/src/LoadImages.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/LoadImages.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/LoadImages.c	(revision 29938)
@@ -0,0 +1,46 @@
+# include "dvomerge.h"
+
+Image *LoadImages (FITS_DB *db, char *filename, off_t *Nimage) {
+
+  int status;
+  Image *image;
+  double ZeroPoint;
+  
+  myAssert(filename, "programming errror");
+  myAssert(Nimage, "programming errror");
+    
+  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);
+    return (NULL);
+  }
+
+  if (db->dbstate == LCK_EMPTY) {
+    fprintf (stderr, "note: image catalog is empty\n");
+    ALLOCATE (image, Image, 1);
+    *Nimage = 1;
+    return (image);
+  }
+
+  status = dvo_image_load (db, TRUE, FALSE);
+  gfits_db_close (db);
+
+  if (!status) {
+    fprintf (stderr, "problem loading image database table\n");
+    return (NULL);
+  }
+
+  image = gfits_table_get_Image (&db->ftable, Nimage, &db->swapped);
+  if (!image) {
+    fprintf (stderr, "ERROR: failed to read images\n");
+    return (NULL);
+  }
+
+  gfits_scan (&db->header, "ZERO_PT", "%lf", 1, &ZeroPoint);
+  SetZeroPoint (ZeroPoint);
+
+  return (image);
+}
Index: /trunk/Ohana/src/dvomerge/src/ReadDeleteList.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/ReadDeleteList.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/ReadDeleteList.c	(revision 29938)
@@ -0,0 +1,100 @@
+# include "dvomerge.h"
+
+int *ReadDeleteList(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 'image' as first token:
+      if (strncmp(c0, "image", 5)) {
+	fprintf (stderr, "error on line %d: missing 'image' token\n", Nline);
+	goto next_line;
+      }
+
+      // confirm we have 7 fields broken by 6 spaces:
+      space = c0;
+      for (j = 0; j < 6; j++) {
+	space = strchr(space, ' '); 
+	if (!space) {
+	  fprintf (stderr, "line only has %d word(s)\n", j + 1);
+	  goto next_line;
+	}
+	space ++;
+	if (j == 1) {
+	  indexPoint = space + 1;
+	}
+      }
+
+      // 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 29937)
+++ /trunk/Ohana/src/dvomerge/src/args.c	(revision 29938)
@@ -15,4 +15,11 @@
     remove_argument (N, argc, argv);
     ALTERNATE_PHOTCODE_FILE = strdup(argv[N]);
+    remove_argument (N, argc, argv);
+  }
+
+  NTHREADS = 0;
+  if ((N = get_argument (*argc, argv, "-threads"))) {
+    remove_argument (N, argc, argv);
+    NTHREADS = MAX(0, atoi(argv[N]));
     remove_argument (N, argc, argv);
   }
@@ -36,5 +43,5 @@
   }
 
-  if ((*argc != 6) && (*argc != 4)) dvomerge_usage();
+  if ((*argc < 4) || (*argc > 6)) dvomerge_usage();
   return TRUE;
 }
Index: /trunk/Ohana/src/dvomerge/src/dvo_image_merge_dbs.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvo_image_merge_dbs.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/dvo_image_merge_dbs.c	(revision 29938)
@@ -2,4 +2,108 @@
 
 void sort_IDmap (IDmapType *IDmap);
+
+off_t getTgtIndex (e_time start, e_time stop, short photcode, off_t *TgtIndex, e_time *TgtTimes, short *TgtCodes, off_t NimagesTgt) {
+
+  // use bisection to find the starting entry by time
+
+  off_t Nlo, Nhi, N;
+
+  // find the last TGT before start
+  Nlo = 0; Nhi = NimagesTgt;
+  while (Nhi - Nlo > 10) {
+    N = 0.5*(Nlo + Nhi);
+    if (TgtTimes[N] < start) {
+      Nlo = MAX(N, 0);
+    } else {
+      Nhi = MIN(N + 1, NimagesTgt);
+    }
+  }
+
+  // check for the matched mosaic starting from Nlo 
+  // we may have to go much beyond Nlo since stop is not sorted
+  // can we use a sorted version of stop to check when we are beyond the valid range??
+  for (N = Nlo; N < NimagesTgt; N++) { 
+    if (TgtTimes[N] < start) continue;
+    if (TgtTimes[N] > stop) return (-1);
+    if (TgtCodes[N] != photcode) continue;
+    return (TgtIndex[N]);
+  }
+  return (-1);
+}
+
+// sort two times vectors and an index by first time vector
+void SortTgtByTimes (e_time *S, off_t *I, short *C, off_t N) {
+
+# define SWAPFUNC(A,B){ e_time tmp_t; off_t tmp_i; short tmp_c; 	\
+  tmp_t = S[A]; S[A] = S[B]; S[B] = tmp_t; \
+  tmp_i = I[A]; I[A] = I[B]; I[B] = tmp_i; \
+  tmp_c = C[A]; C[A] = C[B]; C[B] = tmp_c; \
+}
+# define COMPARE(A,B)(S[A] < S[B])
+
+  OHANA_SORT (N, COMPARE, SWAPFUNC);
+
+# undef SWAPFUNC
+# undef COMPARE
+
+}
+
+// we have two tables; 'tgt' contains some exposures from 'src' : find them and match a map
+int dvo_image_match_dbs (IDmapType *IDmap, FITS_DB *tgt, FITS_DB *src) {
+
+  Image *imagesSrc, *imagesTgt;
+  off_t NimagesSrc, NimagesTgt;
+  off_t iSrc, iTgt;
+  off_t  *TgtIndex;
+  e_time *TgtTimes;
+  short  *TgtCodes;
+ 
+  imagesSrc = gfits_table_get_Image (&src[0].ftable, &NimagesSrc, &src[0].swapped);
+  if (!imagesSrc) {
+    fprintf (stderr, "ERROR: failed to read images from src\n");
+    exit (2);
+  }
+
+  imagesTgt = gfits_table_get_Image (&tgt[0].ftable, &NimagesTgt, &tgt[0].swapped);
+  if (!imagesTgt) {
+    fprintf (stderr, "ERROR: failed to read images from tgt\n");
+    exit (2);
+  }
+
+  ALLOCATE (IDmap->old, off_t, NimagesSrc);
+  ALLOCATE (IDmap->new, off_t, NimagesSrc);
+  IDmap->Nmap = NimagesSrc;
+
+  // match by time and photcode
+  ALLOCATE (TgtIndex, off_t,  NimagesTgt);
+  ALLOCATE (TgtTimes, e_time, NimagesTgt);
+  ALLOCATE (TgtCodes, short,  NimagesTgt);
+
+  // save the Index, Times, Codes for all TGT images
+  for (iTgt = 0; iTgt < NimagesTgt; iTgt++) {
+    TgtIndex[iTgt] = iTgt;
+    TgtTimes[iTgt] = imagesTgt[iTgt].tzero;
+    TgtCodes[iTgt] = imagesTgt[iTgt].photcode;
+  }
+
+  // sort the index, start, and stop by the start times:
+  SortTgtByTimes (TgtTimes, TgtIndex, TgtCodes, NimagesTgt);
+
+  for (iSrc = 0; iSrc < NimagesSrc; iSrc++) {
+    iTgt = getTgtIndex (imagesSrc[iSrc].tzero, imagesSrc[iSrc].tzero + (int) imagesSrc[iSrc].exptime, imagesSrc[iSrc].photcode, TgtIndex, TgtTimes, TgtCodes, NimagesTgt);
+    if (iTgt < 0) Shutdown ("failure to match images: %s\n", imagesSrc[iSrc].name);
+    IDmap[0].old[iSrc] = imagesSrc[iSrc].imageID;
+    IDmap[0].new[iSrc] = imagesTgt[iTgt].imageID;
+  }
+
+  FREE(TgtIndex);
+  FREE(TgtTimes);
+  FREE(TgtCodes);
+
+  // sort IDmap->old,new on the basis of IDmap->old:
+  sort_IDmap (IDmap);
+
+  return TRUE;
+}
 
 // merge db2 into db1
Index: /trunk/Ohana/src/dvomerge/src/dvoconvert.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoconvert.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/dvoconvert.c	(revision 29938)
@@ -6,5 +6,5 @@
   char filename[256], *input, *output;
 
-  int depth;
+  int depth, Nsecfilt;
   off_t i, j, Ns, Ne;
   SkyTable *outsky, *insky;
@@ -32,4 +32,6 @@
       exit (1);
     }
+    Nsecfilt = GetPhotcodeNsecfilt();
+
     // save the photcodes in the output catdir
     sprintf (filename, "%s/Photcodes.dat", output);
@@ -67,5 +69,5 @@
 
     // load / create output catalog
-    LoadCatalog (&outcatalog, &outsky[0].regions[i], outsky[0].filename[i], "w");
+    LoadCatalog (&outcatalog, &outsky[0].regions[i], outsky[0].filename[i], "w", Nsecfilt);
 
     // combine only tables at equal or larger depth
@@ -77,5 +79,5 @@
 
       // load input catalog
-      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r");
+      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r", Nsecfilt);
 
       // skip empty input catalogs
Index: /trunk/Ohana/src/dvomerge/src/dvomerge.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomerge.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/dvomerge.c	(revision 29938)
@@ -8,8 +8,18 @@
   dvomerge_args (&argc, argv);
 
-  // XXX require both inputs to be sorted?
-
-  if (argc == 6) dvomergeCreate (argc, argv);
-  if (argc == 4) dvomergeUpdate (argc, argv);
+  if (argc == 6) {
+    if (!strcasecmp (argv[2], "and")) {
+      dvomergeCreate (argc, argv);
+    } else {
+      dvomergeFromList (argc, argv);
+    }
+  }
+  if ((argc == 4) || (argc == 5)) {
+    if (NTHREADS) {
+      dvomergeUpdate_threaded (argc, argv);
+    } else {
+      dvomergeUpdate (argc, argv);
+    }
+  }
   dvomerge_usage();
   exit (2); // cannot reach here.
@@ -18,6 +28,7 @@
 /* we have two possible modes of operation:
 
-   Create : dvomerge (in1) and (in2) to (out) -- create a new db from two input dbs
-   Update : dvomerge (in) into (out)          -- merge a new db into an existing db
+   Create   : dvomerge (in1) and (in2) to (out) -- create a new db from two input dbs
+   Update   : dvomerge (in) into (out)          -- merge a new db into an existing db
+   Continue : dvomerge (in) into (out) continue -- merge a new db into an existing db
 
 */
Index: /trunk/Ohana/src/dvomerge/src/dvomergeContinue.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeContinue.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeContinue.c	(revision 29938)
@@ -0,0 +1,164 @@
+# include "dvomerge.h"
+
+int dvomergeContinue (int argc, char **argv) {
+
+  int depth;
+  off_t i, j, Ns, Ne;
+  SkyTable *outsky, *insky;
+  SkyList *outlist, *inlist;
+  Catalog incatalog, outcatalog;
+  char filename[256], *input, *output;
+  IDmapType IDmap;
+  PhotCodeData *inputPhotcodes;
+  PhotCodeData *outputPhotcodes;
+  int *secfiltMap = NULL;
+  int NsecfiltInput, NsecfiltOutput;
+
+  double dtime;
+  struct timeval start, stop;
+  gettimeofday (&start, NULL);
+
+  if (strcasecmp (argv[2], "into")) dvomerge_usage();
+  if (strcasecmp (argv[4], "continue")) dvomerge_usage();
+
+  input  = argv[1];
+  output = argv[3];
+
+  if (ALTERNATE_PHOTCODE_FILE) {
+    fprintf (stderr, "cannot specify photcodes when merging into an existing catdir\n");
+    exit (1);
+  }
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", input);
+  if (!LoadPhotcodes (filename, NULL, FALSE)) {
+    fprintf (stderr, "error reading input database directory %s\n", input);
+    exit (1);
+  }	
+  inputPhotcodes = GetPhotcodeTable();
+  NsecfiltInput = GetPhotcodeNsecfilt();
+
+  // since we are merging the input db into the output db, the output defines the photcode
+  // table & db layout but, this requires the output to exist.  if it does not, instead use the
+  // input.
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", output);
+  if (LoadPhotcodes (filename, NULL, FALSE)) {
+
+    outputPhotcodes = GetPhotcodeTable();
+    NsecfiltOutput = GetPhotcodeNsecfilt();
+
+    secfiltMap = GetSecFiltMap(outputPhotcodes, inputPhotcodes);
+    if (!secfiltMap) {
+      fprintf (stderr, "failed to map input secfilt photcodes to output photcodes table\n");
+      exit (1);
+    }
+  } else {
+    fprintf(stderr, "%s not found using photcodes from %s/Photcodes.dat\n", filename, input);
+    outputPhotcodes = inputPhotcodes;
+    NsecfiltOutput = NsecfiltInput;
+
+    if (!check_dir_access (output, VERBOSE)) {
+      fprintf (stderr, "error creating output database directory %s\n", output);
+      exit (1);
+    }
+    SetPhotcodeTable(inputPhotcodes);
+    if (!SavePhotcodesFITS (filename)) {
+      fprintf (stderr, "error saving photcode table in %s/Photcodes.dat\n", output);
+      exit (1);
+    }
+  }
+
+  // need to determine the mapping from the input to the output images
+  dvomergeImagesGetMap (&IDmap, input, output);
+
+  // load the sky table for the existing database
+  insky = SkyTableLoadOptimal (input, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
+  if (!insky) {
+      Shutdown ("can't read SkyTable for %s", input);
+  }
+  SkyTableSetFilenames (insky, input, "cpt");
+
+  // XXX apply this...generate the subset matching the user-selected region
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+
+  // generate an output table populated at the desired depth
+  outsky = SkyTableLoadOptimal (output, NULL, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  if (!outsky) {
+      Shutdown ("can't read or create SkyTable for %s", output);
+  }
+  SkyTableSetFilenames (outsky, output, "cpt");
+
+  // loop over the populatable output tables; check for data in input in the corresponding regions
+
+  SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+  depth = inlist[0].regions[Ns][0].depth;
+  
+  SetPhotcodeTable(NULL);
+
+  // loop over the populated input regions
+  for (i = 0; i < inlist[0].Nregions; i++) {
+    if (!inlist[0].regions[i][0].table) continue;
+    if (VERBOSE) fprintf (stderr, "input: %s\n", inlist[0].regions[i][0].name);
+
+    // load / create output catalog (if catalog does not exist, it will be created)
+    LoadCatalog (&incatalog, &inlist[0].regions[i][0], inlist[0].filename[i], "r", NsecfiltInput);
+    // skip empty input catalogs
+    if (!incatalog.Naves_disk) {
+	dvo_catalog_unlock (&incatalog);
+	dvo_catalog_free (&incatalog);
+	continue;
+    }
+
+    // combine only tables at equal or larger depth
+      
+    // load in all of the tables from input for this region
+    // 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, depth, 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);
+    for (j = 0; j < outlist[0].Nregions; j++) {
+      if (VERBOSE) fprintf (stderr, "output : %s\n", outlist[0].regions[j][0].name);
+
+      // load input catalog
+      LoadCatalog (&outcatalog, outlist[0].regions[j], outlist[0].filename[j], "w", NsecfiltOutput);
+
+      dvo_update_image_IDs (&IDmap, &incatalog);
+      merge_catalogs_old (&outsky[0].regions[j], &outcatalog, &incatalog, RADIUS, secfiltMap);
+
+      outcatalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF;
+
+      // if we receive a signal which would cause us to exit, wait until the full catalog is written
+      SetProtect (TRUE);
+      if (!dvo_catalog_save (&outcatalog, VERBOSE)) {
+	fprintf (stderr, "ERROR: failed to save catalog %s\n", outlist[0].filename[j]);
+	exit (1);
+      }
+      SetProtect (FALSE);
+
+      dvo_catalog_unlock (&outcatalog);
+      dvo_catalog_free (&outcatalog);
+
+      fprintf (stderr, "merged %s into %s\n", inlist[0].regions[i][0].name, outlist[0].regions[j][0].name);
+    }
+    SkyListFree (outlist);
+
+    dvo_catalog_unlock (&incatalog);
+    dvo_catalog_free (&incatalog);
+  }
+
+  // save the output sky table copy
+  sprintf (filename, "%s/SkyTable.fits", output);
+  check_file_access (filename, TRUE, TRUE, VERBOSE);
+  if (!SkyTableSave (outsky, filename)) {
+    fprintf (stderr, "ERROR: failed to save sky table for %s\n", output);
+    exit (1);
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec\n", dtime);
+
+  exit (0);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvomergeContinue_threaded.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeContinue_threaded.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeContinue_threaded.c	(revision 29938)
@@ -0,0 +1,313 @@
+# include "dvomerge.h"
+# include <pthread.h>
+
+enum {
+  TS_WAIT,
+  TS_RUN,
+  TS_DONE,
+  TS_FAIL,
+  TS_EXIT,
+};
+
+typedef struct {
+  int iThread;
+  int state;
+  // elements used to carry data to the threadjob
+  char *name;
+  char *filename;
+  SkyRegion *region;
+  int NsecfiltInput;
+  int NsecfiltOutput;
+  IDmapType *IDmap;
+  SkyTable *outsky;
+  int *secfiltMap;
+  int depth;
+} ThreadData;
+
+void *dvomergeUpdate_threadjob (void *inputData) {
+
+  off_t j;
+  Catalog incatalog, outcatalog;
+  SkyList *outlist;
+
+  ThreadData *threadData = (ThreadData *) inputData;
+
+  while (TRUE) {
+
+    while (threadData->state != TS_RUN) {
+      usleep (100);
+    }
+
+    if (VERBOSE) fprintf (stderr, "input: %s\n", threadData->name);
+
+    // load / create output catalog (if catalog does not exist, it will be created)
+    LoadCatalog (&incatalog, threadData->region, threadData->filename, "r", threadData->NsecfiltInput);
+    // skip empty input catalogs
+    if (!incatalog.Naves_disk) {
+      dvo_catalog_unlock (&incatalog);
+      dvo_catalog_free (&incatalog);
+      threadData->state = TS_DONE;
+      continue;
+    }
+
+    // combine only tables at equal or larger depth.
+
+    // NOTE: this requirement is especially important for the threaded version: if the
+    // output catalog is guaranteed to be the same size or finer than the input, then two
+    // input catalogs cannot collide on their target output catalog.
+      
+    // load in all of the tables from input for this region
+    // 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 (threadData->outsky, threadData->depth, threadData->region->Rmin + dPos, threadData->region->Rmax - dPos, threadData->region->Dmin + dPos, threadData->region->Dmax - dPos);
+    for (j = 0; (j < outlist[0].Nregions) && (threadData->state != TS_FAIL); j++) {
+      if (VERBOSE) fprintf (stderr, "output : %s\n", outlist[0].regions[j][0].name);
+
+      // load input catalog
+      LoadCatalog (&outcatalog, outlist[0].regions[j], outlist[0].filename[j], "w", threadData->NsecfiltOutput);
+
+      dvo_update_image_IDs (threadData->IDmap, &incatalog);
+      merge_catalogs_old (&threadData->outsky->regions[j], &outcatalog, &incatalog, RADIUS, threadData->secfiltMap);
+
+      outcatalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF;
+
+      // if we receive a signal which would cause us to exit, wait until the full catalog is written
+      SetProtect (TRUE);
+      if (!dvo_catalog_save (&outcatalog, VERBOSE)) {
+	fprintf (stderr, "ERROR: failed to save catalog %s\n", outlist[0].filename[j]);
+	threadData->state = TS_FAIL;
+	continue;
+      }
+      SetProtect (FALSE);
+
+      dvo_catalog_unlock (&outcatalog);
+      dvo_catalog_free (&outcatalog);
+
+      fprintf (stderr, "merged %s into %s\n", threadData->name, outlist[0].regions[j][0].name);
+    }
+    SkyListFree (outlist);
+
+    dvo_catalog_unlock (&incatalog);
+    dvo_catalog_free (&incatalog);
+    threadData->state = TS_DONE;
+  }
+  return NULL;
+}
+
+int dvomergeUpdate_threaded (int argc, char **argv) {
+
+  int j, launched, done, Nwait, Nrun;
+
+  ThreadData *threadData;
+  pthread_t *threads;
+
+  int depth;
+  off_t i, Ns, Ne;
+  SkyTable *outsky, *insky;
+  SkyList *inlist;
+  char filename[256], *input, *output;
+  IDmapType IDmap;
+  PhotCodeData *inputPhotcodes;
+  PhotCodeData *outputPhotcodes;
+  int *secfiltMap = NULL;
+  int NsecfiltInput, NsecfiltOutput;
+
+  double dtime;
+  struct timeval start, stop;
+  gettimeofday (&start, NULL);
+
+  CONTINUE = FALSE;
+  if (strcasecmp (argv[2], "into")) dvomerge_usage();
+  if (argc == 5) {
+    if (strcasecmp (argv[4], "continue")) dvomerge_usage();
+    CONTINUE = TRUE;
+  }
+
+  input  = argv[1];
+  output = argv[3];
+
+  if (ALTERNATE_PHOTCODE_FILE) {
+    fprintf (stderr, "cannot specify photcodes when merging into an existing catdir\n");
+    exit (1);
+  }
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", input);
+  if (!LoadPhotcodes (filename, NULL, FALSE)) {
+    fprintf (stderr, "error reading input database directory %s\n", input);
+    exit (1);
+  }	
+  inputPhotcodes = GetPhotcodeTable();
+  NsecfiltInput = GetPhotcodeNsecfilt();
+
+  // since we are merging the input db into the output db, the output defines the photcode
+  // table & db layout but, this requires the output to exist.  if it does not, instead use the
+  // input.
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", output);
+  if (LoadPhotcodes (filename, NULL, FALSE)) {
+
+    outputPhotcodes = GetPhotcodeTable();
+    NsecfiltOutput = GetPhotcodeNsecfilt();
+
+    secfiltMap = GetSecFiltMap(outputPhotcodes, inputPhotcodes);
+    if (!secfiltMap) {
+      fprintf (stderr, "failed to map input secfilt photcodes to output photcodes table\n");
+      exit (1);
+    }
+  } else {
+    fprintf(stderr, "%s not found using photcodes from %s/Photcodes.dat\n", filename, input);
+    outputPhotcodes = inputPhotcodes;
+    NsecfiltOutput = NsecfiltInput;
+
+    if (!check_dir_access (output, VERBOSE)) {
+      fprintf (stderr, "error creating output database directory %s\n", output);
+      exit (1);
+    }
+    SetPhotcodeTable(inputPhotcodes);
+    if (!SavePhotcodesFITS (filename)) {
+      fprintf (stderr, "error saving photcode table in %s/Photcodes.dat\n", output);
+      exit (1);
+    }
+  }
+
+  if (CONTINUE) {
+    // need to determine the mapping from the input to the output images
+    dvomergeImagesGetMap (&IDmap, input, output);
+  } else {
+    dvomergeImagesUpdate (&IDmap, input, output);
+  }
+
+  // load the sky table for the existing database
+  insky = SkyTableLoadOptimal (input, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
+  if (!insky) {
+      Shutdown ("can't read SkyTable for %s", input);
+  }
+  SkyTableSetFilenames (insky, input, "cpt");
+
+  // XXX apply this...generate the subset matching the user-selected region
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+
+  // generate an output table populated at the desired depth
+  outsky = SkyTableLoadOptimal (output, NULL, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  if (!outsky) {
+      Shutdown ("can't read or create SkyTable for %s", output);
+  }
+  SkyTableSetFilenames (outsky, output, "cpt");
+
+  // loop over the populatable output tables; check for data in input in the corresponding regions
+
+  SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+  depth = inlist[0].regions[Ns][0].depth;
+  
+  SetPhotcodeTable(NULL);
+
+  ALLOCATE(threads, pthread_t, NTHREADS);
+  ALLOCATE(threadData, ThreadData, NTHREADS);
+  for (i = 0; i < NTHREADS; i++) {
+    threadData[i].state = TS_WAIT;
+    threadData[i].iThread = i;
+  }
+  for (i = 0; i < NTHREADS; i++) {
+    pthread_create (&threads[i], NULL, &dvomergeUpdate_threadjob, (void *) &threadData[i]);
+  }
+
+  // loop over the populated input regions
+  for (i = 0; i < inlist[0].Nregions; i++) {
+    if (!inlist[0].regions[i][0].table) continue;
+
+    launched = FALSE;
+    while (!launched) {
+
+      // are any threads done? can we harvest them?
+      for (j = 0; j < NTHREADS; j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_DONE) continue;
+	// fprintf (stderr, "harvested thread %d\n", threadData[j].iThread);
+	threadData[j].state = TS_WAIT;
+      }
+	
+      // are any threads available? can we launch this job?
+      for (j = 0; !launched && (j < NTHREADS); j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_WAIT) continue;
+
+	threadData[j].name = inlist[0].regions[i][0].name;
+	threadData[j].filename = inlist[0].filename[i];
+	threadData[j].region = inlist[0].regions[i];
+	threadData[j].NsecfiltInput = NsecfiltInput;
+	threadData[j].NsecfiltOutput = NsecfiltOutput;
+	threadData[j].IDmap = &IDmap;
+	threadData[j].outsky = outsky;
+	threadData[j].secfiltMap = secfiltMap;
+	threadData[j].depth = depth;
+	threadData[j].state = TS_RUN;
+	launched = TRUE;
+      }
+      if (!launched) usleep (100);
+    }
+  }
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+    // are any threads done? can we harvest them?
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_DONE) continue;
+      threadData[j].state = TS_WAIT;
+    }
+
+    // how many are waiting now?
+    Nwait = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_WAIT) continue;
+      Nwait ++;
+    }
+
+    if (Nwait < NTHREADS) { 
+      usleep (100); 
+    } else {
+      done = TRUE;
+    }
+  }
+
+  // save the output sky table copy
+  sprintf (filename, "%s/SkyTable.fits", output);
+  check_file_access (filename, TRUE, TRUE, VERBOSE);
+  if (!SkyTableSave (outsky, filename)) {
+    fprintf (stderr, "ERROR: failed to save sky table for %s\n", output);
+    exit (1);
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec\n", dtime);
+
+  exit (0);
+
+failure:
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+
+    // how many are still running?
+    Nrun = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state != TS_RUN) continue;
+      Nrun ++;
+    }
+
+    if (Nrun) { 
+      usleep (100); 
+    } else {
+      done = TRUE;
+    }
+  }
+  exit (1);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvomergeCreate.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeCreate.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeCreate.c	(revision 29938)
@@ -15,4 +15,5 @@
   char *input1, *input2, *output;
   IDmapType IDmap1, IDmap2;
+  int NsecfiltInput1, NsecfiltInput2, NsecfiltOutput;
 
   if (strcasecmp (argv[2], "and")) dvomerge_usage();
@@ -39,4 +40,5 @@
   }
   input1Photcodes = GetPhotcodeTable();
+  NsecfiltInput1 = GetPhotcodeNsecfilt();
 
   // Read the input2 photcodes
@@ -48,4 +50,5 @@
   }
   input2Photcodes = GetPhotcodeTable();
+  NsecfiltInput2 = GetPhotcodeNsecfilt();
 
   if (ALTERNATE_PHOTCODE_FILE) {
@@ -60,4 +63,5 @@
     }
     outputPhotcodes = GetPhotcodeTable();
+    NsecfiltOutput = GetPhotcodeNsecfilt();
 
     secfiltMap1 = GetSecFiltMap(outputPhotcodes, input1Photcodes);
@@ -74,4 +78,5 @@
     outputPhotcodes = input1Photcodes;
     codesFilename = filename1;
+    NsecfiltOutput = NsecfiltInput1;
     // secfitMap1 can remain NULL since input1 defines the set of codes
   }
@@ -84,6 +89,9 @@
   }
 
+  // trigger any dependencies, if any
+  SetPhotcodeTable(NULL);
+
   // save the output photcodes in the output catdir
-  SetPhotcodeTable(outputPhotcodes);
+  // SetPhotcodeTable(outputPhotcodes);
   sprintf (filename, "%s/Photcodes.dat", output);
   if (!check_file_access (filename, TRUE, TRUE, VERBOSE)) {
@@ -121,13 +129,13 @@
     if (VERBOSE) fprintf (stderr, "output: %s\n", outsky[0].regions[i].name);
 
-    if (outputPhotcodes) {
-        SetPhotcodeTable(outputPhotcodes);
-    }
+    // if (outputPhotcodes) {
+    //     SetPhotcodeTable(outputPhotcodes);
+    // }
     // load / create output catalog
-    LoadCatalog (&outcatalog, &outsky[0].regions[i], outsky[0].filename[i], "w");
-
-    if (input1Photcodes) {
-        SetPhotcodeTable(input1Photcodes);
-    }
+    LoadCatalog (&outcatalog, &outsky[0].regions[i], outsky[0].filename[i], "w", NsecfiltOutput);
+
+    // if (input1Photcodes) {
+    // SetPhotcodeTable(input1Photcodes);
+    // }
     // combine only tables at equal or larger depth
       
@@ -138,5 +146,5 @@
 
       // load input catalog (1)
-      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r");
+      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r", NsecfiltInput1);
 
       // skip empty input catalogs
@@ -153,7 +161,7 @@
     SkyListFree (inlist);
 
-    if (input2Photcodes) {
-      SetPhotcodeTable(input2Photcodes);
-    }
+    // if (input2Photcodes) {
+    //   SetPhotcodeTable(input2Photcodes);
+    // }
 
     // load in all of the tables from input2 for this region
@@ -163,5 +171,5 @@
 
       // load input catalog (2)
-      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r");
+      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r", NsecfiltInput2);
 
       // skip empty input catalogs
Index: /trunk/Ohana/src/dvomerge/src/dvomergeFromList.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeFromList.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeFromList.c	(revision 29938)
@@ -0,0 +1,158 @@
+# include "dvomerge.h"
+
+int dvomergeFromList (int argc, char **argv) {
+
+  off_t i, j;
+  Catalog incatalog, outcatalog;
+  char filename[256], *input, *output;
+  IDmapType IDmap;
+  PhotCodeData *inputPhotcodes;
+  PhotCodeData *outputPhotcodes;
+  int *secfiltMap = NULL;
+  char *listname, **list, inputfile[256], outputfile[256];
+  int Nlist, NLIST;
+  int NsecfiltInput, NsecfiltOutput;
+
+  if (strcasecmp (argv[2], "into")) dvomerge_usage();
+  if (strcasecmp (argv[4], "from")) dvomerge_usage();
+
+  input  = argv[1];
+  output = argv[3];
+  listname = argv[5];
+
+  Nlist = 0;
+  NLIST = 100;
+  ALLOCATE(list, char *, NLIST);
+  for (i = 0; i < NLIST; i++) {
+    ALLOCATE(list[i], char, 256);
+    memset(list[i], 0, 256);
+  }
+  
+  FILE *listfile = fopen(listname, "r");
+  myAssert(listfile, "error opening list");
+
+  for (i = 0; fscanf (listfile, "%s", list[i]) != EOF; i++) {
+    if (i == NLIST - 1) {
+      NLIST += 100;
+      REALLOCATE(list, char *, NLIST);
+      for (j = i + 1; j < NLIST; j++) {
+	ALLOCATE(list[j], char, 256);
+	memset(list[j], 0, 256);
+      }
+    }
+  }      
+  Nlist = i;
+  fclose(listfile);
+
+  if (ALTERNATE_PHOTCODE_FILE) {
+    fprintf (stderr, "cannot specify photcodes when merging into an existing catdir\n");
+    exit (1);
+  }
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", input);
+  if (!LoadPhotcodes (filename, NULL, FALSE)) {
+    fprintf (stderr, "error reading input database directory %s\n", input);
+    exit (1);
+  }	
+  inputPhotcodes = GetPhotcodeTable();
+  NsecfiltInput = GetPhotcodeNsecfilt();
+
+  // since we are merging the input db into the output db, the output defines the photcode
+  // table & db layout but, this requires the output to exist.  if it does not, instead use the
+  // input.
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", output);
+  if (LoadPhotcodes (filename, NULL, FALSE)) {
+
+    outputPhotcodes = GetPhotcodeTable();
+    NsecfiltOutput = GetPhotcodeNsecfilt();
+
+    secfiltMap = GetSecFiltMap(outputPhotcodes, inputPhotcodes);
+    if (!secfiltMap) {
+      fprintf (stderr, "failed to map input secfilt photcodes to output photcodes table\n");
+      exit (1);
+    }
+  } else {
+    fprintf(stderr, "%s not found using photcodes from %s/Photcodes.dat\n", filename, input);
+    outputPhotcodes = inputPhotcodes;
+    NsecfiltOutput = NsecfiltInput;
+
+    if (!check_dir_access (output, VERBOSE)) {
+      fprintf (stderr, "error creating output database directory %s\n", output);
+      exit (1);
+    }
+    SetPhotcodeTable(inputPhotcodes);
+    if (!SavePhotcodesFITS (filename)) {
+      fprintf (stderr, "error saving photcode table in %s/Photcodes.dat\n", output);
+      exit (1);
+    }
+  }
+
+  // XXX need to determine the mapping from the input to the output images
+  dvomergeImagesGetMap (&IDmap, input, output);
+
+  SetPhotcodeTable(NULL);
+
+  // loop over the populated input regions
+  for (i = 0; i < Nlist; i++) {
+    if (VERBOSE) fprintf (stderr, "input: %s\n", list[i]);
+
+    sprintf (inputfile,  "%s/%s", input,  list[i]);
+    sprintf (outputfile, "%s/%s", output, list[i]);
+
+    LoadCatalog (&incatalog, NULL, inputfile, "r", NsecfiltInput);
+
+    // skip empty input catalogs
+    if (!incatalog.Naves_disk) {
+	dvo_catalog_unlock (&incatalog);
+	dvo_catalog_free (&incatalog);
+	continue;
+    }
+
+    // combine only tables at equal depth
+      
+    SkyRegion skyregion;
+    gfits_scan (&incatalog.header, "RA0",  "%f", 1, &skyregion.Rmin);
+    gfits_scan (&incatalog.header, "RA1",  "%f", 1, &skyregion.Rmax);
+    gfits_scan (&incatalog.header, "DEC0", "%f", 1, &skyregion.Dmin);
+    gfits_scan (&incatalog.header, "DEC1", "%f", 1, &skyregion.Dmax);
+
+    // load input catalog
+    // SetPhotcodeTable(outputPhotcodes);
+    LoadCatalog (&outcatalog, NULL, outputfile, "w", NsecfiltOutput);
+
+    if (outcatalog.Naverage == 0) {
+      gfits_modify (&outcatalog.header, "RA0",  "%f", 1, skyregion.Rmin);
+      gfits_modify (&outcatalog.header, "DEC0", "%f", 1, skyregion.Dmin);
+      gfits_modify (&outcatalog.header, "RA1",  "%f", 1, skyregion.Rmax);
+      gfits_modify (&outcatalog.header, "DEC1", "%f", 1, skyregion.Dmax);
+      gfits_modify (&outcatalog.header, "CATID", "%d", 1, incatalog.catID);
+      outcatalog.catID = incatalog.catID;
+    }
+
+    dvo_update_image_IDs (&IDmap, &incatalog);
+    merge_catalogs_old (&skyregion, &outcatalog, &incatalog, RADIUS, secfiltMap);
+    
+    outcatalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF;
+
+    // if we receive a signal which would cause us to exit, wait until the full catalog is written
+    SetProtect (TRUE);
+    if (!dvo_catalog_save (&outcatalog, VERBOSE)) {
+      fprintf (stderr, "ERROR: failed to save catalog %s\n", outputfile);
+      exit (1);
+    }
+    SetProtect (FALSE);
+    
+    fprintf (stderr, "merged %s into %s\n", inputfile, outputfile);
+
+    dvo_catalog_unlock (&outcatalog);
+    dvo_catalog_free (&outcatalog);
+    
+    dvo_catalog_unlock (&incatalog);
+    dvo_catalog_free (&incatalog);
+  }
+
+  exit (0);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvomergeImageIDs.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeImageIDs.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeImageIDs.c	(revision 29938)
@@ -0,0 +1,100 @@
+# include "dvomerge.h"
+
+/*** update the image table ***/
+int dvomergeImagesUpdate (IDmapType *IDmap, char *input, char *output) { 
+
+  FITS_DB inDB;
+  FITS_DB outDB;
+  int    status;
+
+  /*** load input1/Images.dat ***/
+  sprintf (ImageCat, "%s/Images.dat", input);
+  inDB.mode   = dvo_catalog_catmode (CATMODE);
+  inDB.format = dvo_catalog_catformat (CATFORMAT);
+  status       = dvo_image_lock (&inDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", inDB.filename);
+
+  // load the image table 
+  if (inDB.dbstate == LCK_EMPTY) {
+    IDmap->Nmap = 0;
+    return TRUE;
+  }
+  if (!dvo_image_load (&inDB, VERBOSE, TRUE)) {
+    Shutdown ("can't read input image catalog %s", inDB.filename);
+  }
+
+  /*** load output/Images.dat ***/
+  sprintf (ImageCat, "%s/Images.dat", output);
+  outDB.mode   = dvo_catalog_catmode (CATMODE);
+  outDB.format = dvo_catalog_catformat (CATFORMAT);
+  status       = dvo_image_lock (&outDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", outDB.filename);
+
+  /* load the image table */
+  if (outDB.dbstate == LCK_EMPTY) {
+    dvo_image_create (&outDB, GetZeroPoint());
+  } else {
+    if (!dvo_image_load (&outDB, VERBOSE, TRUE)) {
+      Shutdown ("can't read output image catalog %s", outDB.filename);
+    }
+  }
+
+  // convert database table to internal structure & add to output image db
+  dvo_image_merge_dbs(IDmap, &outDB, &inDB);
+  dvo_image_unlock (&inDB); // unlock input
+
+  SetProtect (TRUE);
+  dvo_image_save (&outDB, VERBOSE);
+  SetProtect (FALSE);
+  dvo_image_unlock (&outDB); // unlock output
+
+  return TRUE;
+}
+
+/*** update the image table ***/
+int dvomergeImagesGetMap (IDmapType *IDmap, char *input, char *output) { 
+
+  FITS_DB inDB;
+  FITS_DB outDB;
+  int    status;
+
+  /*** load input1/Images.dat ***/
+  sprintf (ImageCat, "%s/Images.dat", input);
+  inDB.mode   = dvo_catalog_catmode (CATMODE);
+  inDB.format = dvo_catalog_catformat (CATFORMAT);
+  status       = dvo_image_lock (&inDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", inDB.filename);
+
+  // load the image table
+  if (inDB.dbstate == LCK_EMPTY) {
+    IDmap->Nmap = 0;
+    return TRUE;
+  }
+
+  // the input database is allowed to have no images 
+  if (!dvo_image_load (&inDB, VERBOSE, TRUE)) {
+    Shutdown ("can't read input image catalog %s", inDB.filename);
+  }
+
+  /*** load output/Images.dat ***/
+  sprintf (ImageCat, "%s/Images.dat", output);
+  outDB.mode   = dvo_catalog_catmode (CATMODE);
+  outDB.format = dvo_catalog_catformat (CATFORMAT);
+  status       = dvo_image_lock (&outDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", outDB.filename);
+
+  /* load the image table */
+  if (outDB.dbstate == LCK_EMPTY) {
+    Shutdown ("only use -continue for an existing, partially merged database");
+  } 
+  if (!dvo_image_load (&outDB, VERBOSE, TRUE)) {
+    Shutdown ("can't read output image catalog %s", outDB.filename);
+  }
+
+  // convert database table to internal structure & add to output image db
+  dvo_image_match_dbs(IDmap, &outDB, &inDB);
+  dvo_image_unlock (&inDB); // unlock input
+  dvo_image_unlock (&outDB); // unlock output
+
+  return TRUE;
+}
Index: /trunk/Ohana/src/dvomerge/src/dvomergeUpdate.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeUpdate.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeUpdate.c	(revision 29938)
@@ -3,5 +3,5 @@
 int dvomergeUpdate (int argc, char **argv) {
 
-  int depth;
+  int depth, CONTINUE;
   off_t i, j, Ns, Ne;
   SkyTable *outsky, *insky;
@@ -13,6 +13,16 @@
   PhotCodeData *outputPhotcodes;
   int *secfiltMap = NULL;
+  int NsecfiltInput, NsecfiltOutput;
 
+  double dtime;
+  struct timeval start, stop;
+  gettimeofday (&start, NULL);
+
+  CONTINUE = FALSE;
   if (strcasecmp (argv[2], "into")) dvomerge_usage();
+  if (argc == 5) {
+    if (strcasecmp (argv[4], "continue")) dvomerge_usage();
+    CONTINUE = TRUE;
+  }
 
   input  = argv[1];
@@ -31,4 +41,5 @@
   }	
   inputPhotcodes = GetPhotcodeTable();
+  NsecfiltInput = GetPhotcodeNsecfilt();
 
   // since we are merging the input db into the output db, the output defines the photcode
@@ -41,4 +52,5 @@
 
     outputPhotcodes = GetPhotcodeTable();
+    NsecfiltOutput = GetPhotcodeNsecfilt();
 
     secfiltMap = GetSecFiltMap(outputPhotcodes, inputPhotcodes);
@@ -50,4 +62,6 @@
     fprintf(stderr, "%s not found using photcodes from %s/Photcodes.dat\n", filename, input);
     outputPhotcodes = inputPhotcodes;
+    NsecfiltOutput = NsecfiltInput;
+
     if (!check_dir_access (output, VERBOSE)) {
       fprintf (stderr, "error creating output database directory %s\n", output);
@@ -61,5 +75,10 @@
   }
 
-  dvomergeImagesUpdate (&IDmap, input, output);
+  if (CONTINUE) {
+    // need to determine the mapping from the input to the output images
+    dvomergeImagesGetMap (&IDmap, input, output);
+  } else {
+    dvomergeImagesUpdate (&IDmap, input, output);
+  }
 
   // load the sky table for the existing database
@@ -85,4 +104,6 @@
   depth = inlist[0].regions[Ns][0].depth;
   
+  SetPhotcodeTable(NULL);
+
   // loop over the populated input regions
   for (i = 0; i < inlist[0].Nregions; i++) {
@@ -90,7 +111,6 @@
     if (VERBOSE) fprintf (stderr, "input: %s\n", inlist[0].regions[i][0].name);
 
-    SetPhotcodeTable(inputPhotcodes);
     // load / create output catalog (if catalog does not exist, it will be created)
-    LoadCatalog (&incatalog, &inlist[0].regions[i][0], inlist[0].filename[i], "r");
+    LoadCatalog (&incatalog, &inlist[0].regions[i][0], inlist[0].filename[i], "r", NsecfiltInput);
     // skip empty input catalogs
     if (!incatalog.Naves_disk) {
@@ -111,6 +131,5 @@
 
       // load input catalog
-      SetPhotcodeTable(outputPhotcodes);
-      LoadCatalog (&outcatalog, outlist[0].regions[j], outlist[0].filename[j], "w");
+      LoadCatalog (&outcatalog, outlist[0].regions[j], outlist[0].filename[j], "w", NsecfiltOutput);
 
       dvo_update_image_IDs (&IDmap, &incatalog);
@@ -121,5 +140,8 @@
       // if we receive a signal which would cause us to exit, wait until the full catalog is written
       SetProtect (TRUE);
-      dvo_catalog_save (&outcatalog, VERBOSE);
+      if (!dvo_catalog_save (&outcatalog, VERBOSE)) {
+	fprintf (stderr, "ERROR: failed to save catalog %s\n", outlist[0].filename[j]);
+	exit (1);
+      }
       SetProtect (FALSE);
 
@@ -143,56 +165,8 @@
   }
 
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec\n", dtime);
+
   exit (0);
 }
-
-/*** update the image table ***/
-int dvomergeImagesUpdate (IDmapType *IDmap, char *input, char *output) { 
-
-  FITS_DB inDB;
-  FITS_DB outDB;
-  int    status;
-
-  /*** load input1/Images.dat ***/
-  sprintf (ImageCat, "%s/Images.dat", input);
-  inDB.mode   = dvo_catalog_catmode (CATMODE);
-  inDB.format = dvo_catalog_catformat (CATFORMAT);
-  status       = dvo_image_lock (&inDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
-  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", inDB.filename);
-
-  // load the image table 
-  if (inDB.dbstate == LCK_EMPTY) {
-    // Shutdown ("can't find input image catalog %s", inDB.filename);
-    IDmap->Nmap = 0;
-    return TRUE;
-  }
-  if (!dvo_image_load (&inDB, VERBOSE, TRUE)) {
-    Shutdown ("can't read input image catalog %s", inDB.filename);
-  }
-
-  /*** load output/Images.dat ***/
-  sprintf (ImageCat, "%s/Images.dat", output);
-  outDB.mode   = dvo_catalog_catmode (CATMODE);
-  outDB.format = dvo_catalog_catformat (CATFORMAT);
-  status       = dvo_image_lock (&outDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
-  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", outDB.filename);
-
-  /* load the image table */
-  if (outDB.dbstate == LCK_EMPTY) {
-    dvo_image_create (&outDB, GetZeroPoint());
-  } else {
-    if (!dvo_image_load (&outDB, VERBOSE, TRUE)) {
-      Shutdown ("can't read output image catalog %s", outDB.filename);
-    }
-  }
-
-  // convert database table to internal structure & add to output image db
-  dvo_image_merge_dbs(IDmap, &outDB, &inDB);
-  dvo_image_unlock (&inDB); // unlock input
-
-  SetProtect (TRUE);
-  dvo_image_save (&outDB, VERBOSE);
-  SetProtect (FALSE);
-  dvo_image_unlock (&outDB); // unlock output
-
-  return TRUE;
-}
Index: /trunk/Ohana/src/dvomerge/src/dvomergeUpdate_threaded.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvomergeUpdate_threaded.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvomergeUpdate_threaded.c	(revision 29938)
@@ -0,0 +1,313 @@
+# include "dvomerge.h"
+# include <pthread.h>
+
+enum {
+  TS_WAIT,
+  TS_RUN,
+  TS_DONE,
+  TS_FAIL,
+  TS_EXIT,
+};
+
+typedef struct {
+  int iThread;
+  int state;
+  // elements used to carry data to the threadjob
+  char *name;
+  char *filename;
+  SkyRegion *region;
+  int NsecfiltInput;
+  int NsecfiltOutput;
+  IDmapType *IDmap;
+  SkyTable *outsky;
+  int *secfiltMap;
+  int depth;
+} ThreadData;
+
+void *dvomergeUpdate_threadjob (void *inputData) {
+
+  off_t j;
+  Catalog incatalog, outcatalog;
+  SkyList *outlist;
+
+  ThreadData *threadData = (ThreadData *) inputData;
+
+  while (TRUE) {
+
+    while (threadData->state != TS_RUN) {
+      usleep (100);
+    }
+
+    if (VERBOSE) fprintf (stderr, "input: %s\n", threadData->name);
+
+    // load / create output catalog (if catalog does not exist, it will be created)
+    LoadCatalog (&incatalog, threadData->region, threadData->filename, "r", threadData->NsecfiltInput);
+    // skip empty input catalogs
+    if (!incatalog.Naves_disk) {
+      dvo_catalog_unlock (&incatalog);
+      dvo_catalog_free (&incatalog);
+      threadData->state = TS_DONE;
+      continue;
+    }
+
+    // combine only tables at equal or larger depth.
+
+    // NOTE: this requirement is especially important for the threaded version: if the
+    // output catalog is guaranteed to be the same size or finer than the input, then two
+    // input catalogs cannot collide on their target output catalog.
+      
+    // load in all of the tables from input for this region
+    // 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 (threadData->outsky, threadData->depth, threadData->region->Rmin + dPos, threadData->region->Rmax - dPos, threadData->region->Dmin + dPos, threadData->region->Dmax - dPos);
+    for (j = 0; (j < outlist[0].Nregions) && (threadData->state != TS_FAIL); j++) {
+      if (VERBOSE) fprintf (stderr, "output : %s\n", outlist[0].regions[j][0].name);
+
+      // load input catalog
+      LoadCatalog (&outcatalog, outlist[0].regions[j], outlist[0].filename[j], "w", threadData->NsecfiltOutput);
+
+      dvo_update_image_IDs (threadData->IDmap, &incatalog);
+      merge_catalogs_old (&threadData->outsky->regions[j], &outcatalog, &incatalog, RADIUS, threadData->secfiltMap);
+
+      outcatalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF;
+
+      // if we receive a signal which would cause us to exit, wait until the full catalog is written
+      SetProtect (TRUE);
+      if (!dvo_catalog_save (&outcatalog, VERBOSE)) {
+	fprintf (stderr, "ERROR: failed to save catalog %s\n", outlist[0].filename[j]);
+	threadData->state = TS_FAIL;
+	continue;
+      }
+      SetProtect (FALSE);
+
+      dvo_catalog_unlock (&outcatalog);
+      dvo_catalog_free (&outcatalog);
+
+      fprintf (stderr, "merged %s into %s\n", threadData->name, outlist[0].regions[j][0].name);
+    }
+    SkyListFree (outlist);
+
+    dvo_catalog_unlock (&incatalog);
+    dvo_catalog_free (&incatalog);
+    threadData->state = TS_DONE;
+  }
+  return NULL;
+}
+
+int dvomergeUpdate_threaded (int argc, char **argv) {
+
+  int j, launched, done, Nwait, Nrun;
+
+  ThreadData *threadData;
+  pthread_t *threads;
+
+  int depth, CONTINUE;
+  off_t i, Ns, Ne;
+  SkyTable *outsky, *insky;
+  SkyList *inlist;
+  char filename[256], *input, *output;
+  IDmapType IDmap;
+  PhotCodeData *inputPhotcodes;
+  PhotCodeData *outputPhotcodes;
+  int *secfiltMap = NULL;
+  int NsecfiltInput, NsecfiltOutput;
+
+  double dtime;
+  struct timeval start, stop;
+  gettimeofday (&start, NULL);
+
+  CONTINUE = FALSE;
+  if (strcasecmp (argv[2], "into")) dvomerge_usage();
+  if (argc == 5) {
+    if (strcasecmp (argv[4], "continue")) dvomerge_usage();
+    CONTINUE = TRUE;
+  }
+
+  input  = argv[1];
+  output = argv[3];
+
+  if (ALTERNATE_PHOTCODE_FILE) {
+    fprintf (stderr, "cannot specify photcodes when merging into an existing catdir\n");
+    exit (1);
+  }
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", input);
+  if (!LoadPhotcodes (filename, NULL, FALSE)) {
+    fprintf (stderr, "error reading input database directory %s\n", input);
+    exit (1);
+  }	
+  inputPhotcodes = GetPhotcodeTable();
+  NsecfiltInput = GetPhotcodeNsecfilt();
+
+  // since we are merging the input db into the output db, the output defines the photcode
+  // table & db layout but, this requires the output to exist.  if it does not, instead use the
+  // input.
+
+  SetPhotcodeTable(NULL);
+  sprintf (filename, "%s/Photcodes.dat", output);
+  if (LoadPhotcodes (filename, NULL, FALSE)) {
+
+    outputPhotcodes = GetPhotcodeTable();
+    NsecfiltOutput = GetPhotcodeNsecfilt();
+
+    secfiltMap = GetSecFiltMap(outputPhotcodes, inputPhotcodes);
+    if (!secfiltMap) {
+      fprintf (stderr, "failed to map input secfilt photcodes to output photcodes table\n");
+      exit (1);
+    }
+  } else {
+    fprintf(stderr, "%s not found using photcodes from %s/Photcodes.dat\n", filename, input);
+    outputPhotcodes = inputPhotcodes;
+    NsecfiltOutput = NsecfiltInput;
+
+    if (!check_dir_access (output, VERBOSE)) {
+      fprintf (stderr, "error creating output database directory %s\n", output);
+      exit (1);
+    }
+    SetPhotcodeTable(inputPhotcodes);
+    if (!SavePhotcodesFITS (filename)) {
+      fprintf (stderr, "error saving photcode table in %s/Photcodes.dat\n", output);
+      exit (1);
+    }
+  }
+
+  if (CONTINUE) {
+    // need to determine the mapping from the input to the output images
+    dvomergeImagesGetMap (&IDmap, input, output);
+  } else {
+    dvomergeImagesUpdate (&IDmap, input, output);
+  }
+
+  // load the sky table for the existing database
+  insky = SkyTableLoadOptimal (input, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
+  if (!insky) {
+      Shutdown ("can't read SkyTable for %s", input);
+  }
+  SkyTableSetFilenames (insky, input, "cpt");
+
+  // XXX apply this...generate the subset matching the user-selected region
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+
+  // generate an output table populated at the desired depth
+  outsky = SkyTableLoadOptimal (output, NULL, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
+  if (!outsky) {
+      Shutdown ("can't read or create SkyTable for %s", output);
+  }
+  SkyTableSetFilenames (outsky, output, "cpt");
+
+  // loop over the populatable output tables; check for data in input in the corresponding regions
+
+  SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+  depth = inlist[0].regions[Ns][0].depth;
+  
+  SetPhotcodeTable(NULL);
+
+  ALLOCATE(threads, pthread_t, NTHREADS);
+  ALLOCATE(threadData, ThreadData, NTHREADS);
+  for (i = 0; i < NTHREADS; i++) {
+    threadData[i].state = TS_WAIT;
+    threadData[i].iThread = i;
+  }
+  for (i = 0; i < NTHREADS; i++) {
+    pthread_create (&threads[i], NULL, &dvomergeUpdate_threadjob, (void *) &threadData[i]);
+  }
+
+  // loop over the populated input regions
+  for (i = 0; i < inlist[0].Nregions; i++) {
+    if (!inlist[0].regions[i][0].table) continue;
+
+    launched = FALSE;
+    while (!launched) {
+
+      // are any threads done? can we harvest them?
+      for (j = 0; j < NTHREADS; j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_DONE) continue;
+	// fprintf (stderr, "harvested thread %d\n", threadData[j].iThread);
+	threadData[j].state = TS_WAIT;
+      }
+	
+      // are any threads available? can we launch this job?
+      for (j = 0; !launched && (j < NTHREADS); j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_WAIT) continue;
+
+	threadData[j].name = inlist[0].regions[i][0].name;
+	threadData[j].filename = inlist[0].filename[i];
+	threadData[j].region = inlist[0].regions[i];
+	threadData[j].NsecfiltInput = NsecfiltInput;
+	threadData[j].NsecfiltOutput = NsecfiltOutput;
+	threadData[j].IDmap = &IDmap;
+	threadData[j].outsky = outsky;
+	threadData[j].secfiltMap = secfiltMap;
+	threadData[j].depth = depth;
+	threadData[j].state = TS_RUN;
+	launched = TRUE;
+      }
+      if (!launched) usleep (100);
+    }
+  }
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+    // are any threads done? can we harvest them?
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_DONE) continue;
+      threadData[j].state = TS_WAIT;
+    }
+
+    // how many are waiting now?
+    Nwait = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_WAIT) continue;
+      Nwait ++;
+    }
+
+    if (Nwait < NTHREADS) { 
+      usleep (100); 
+    } else {
+      done = TRUE;
+    }
+  }
+
+  // save the output sky table copy
+  sprintf (filename, "%s/SkyTable.fits", output);
+  check_file_access (filename, TRUE, TRUE, VERBOSE);
+  if (!SkyTableSave (outsky, filename)) {
+    fprintf (stderr, "ERROR: failed to save sky table for %s\n", output);
+    exit (1);
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec\n", dtime);
+
+  exit (0);
+
+failure:
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+
+    // how many are still running?
+    Nrun = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state != TS_RUN) continue;
+      Nrun ++;
+    }
+
+    if (Nrun) { 
+      usleep (100); 
+    } else {
+      done = TRUE;
+    }
+  }
+  exit (1);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvorepair.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepair.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepair.c	(revision 29938)
@@ -0,0 +1,14 @@
+# include "dvomerge.h"
+
+int main (int argc, char **argv) {
+
+  dvorepair_help(argc, argv);
+  
+  if (!strcmp(argv[1], "-fix-cpt")) dvorepairFixCPT(argc, argv);
+  // if (!strcmp(argv[1], "-fix-tables")) dvorepairFixTables(argc, argv);
+  if (!strcmp(argv[1], "-images-vs-measures")) dvorepairImagesVsMeasures(argc, argv);
+  if (!strcmp(argv[1], "-delete-image-list")) dvorepairDeleteImageList(argc, argv);
+  if (!strcmp(argv[1], "-fix-images")) dvorepairFixImages(argc, argv);
+  dvorepair_help(0, NULL);
+  exit (2);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvorepairCPT.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairCPT.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairCPT.c	(revision 29938)
@@ -0,0 +1,190 @@
+# include "dvomerge.h"
+
+# define myAbort(MSG) { fprintf (stderr, "%s\n", MSG); abort(); }
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
+// broken cpt file, valid cpm file: we can recover everything in the cpt file from the cpm file:
+// * load the full cpm file
+// * loop over detections
+// * if ave_ref is new: create a new object
+//   * determine RA & DEC from ext_id
+//   * obj_id, cat_id are defined in detection
+//   * 
+
+// XXX absorb this code into a unified dvorepair program
+
+int main (int argc, char **argv) {
+
+  off_t Nmeasure, Nimage;
+  int i, Nbytes, NaveMax, Naverage, NAVERAGE, Nave, Nold;
+  int *found;
+
+  Image *image, *thisImage;
+  Average *average;
+  Measure *measure;
+
+  Matrix matrix;
+
+  char *cpmFilename, *cptFilenameSrc, *cptFilenameTgt, *imageFilename;
+
+  Header cptHeaderPHU, cptHeaderTBL;
+  Header cpmHeaderPHU, cpmHeaderTBL;
+  FTable cpmFtable, cptFtable;
+
+  FILE *cptFileSrc = NULL;
+  FILE *cptFileTgt = NULL;
+  FILE *cpmFile = NULL;
+
+  char catformat;
+
+  if (argc != 5) {
+    fprintf (stderr, "USAGE: dvorepair (images) (cpm) (cptInput) (cptOutput)\n");
+    exit (2);
+  }
+
+  imageFilename  = argv[1];
+  cpmFilename    = argv[2];
+  cptFilenameSrc = argv[3];
+  cptFilenameTgt = argv[4];
+
+  cpmFtable.header = &cpmHeaderTBL;
+  cptFtable.header = &cptHeaderTBL;
+
+  // XXX don't bother locking for now: this is totally manual..
+
+  // load the image data
+  if ((image = LoadImages (imageFilename, &Nimage)) == NULL) return (FALSE);
+  BuildChipMatch (image, Nimage);
+
+  // open cpm file
+  cpmFile = fopen(cpmFilename, "r");
+  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
+  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, FALSE)) { 
+    myAbort("can't read data for cpm table");
+  }
+
+  measure = FtableToMeasure (&cpmFtable, &Nmeasure, &catformat);
+  myAssert(measure, "failed to convert ftable to measure data");
+
+  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 new objects as needed
+  for (i = 0; i < Nmeasure; i++) {
+    Nave = measure[i].averef;
+    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;
+    }
+
+    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));
+    }
+
+    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
+
+    // fields we have to set:
+
+    // need to find image so we can use ccd coordinates to determine RA & DEC
+    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].Nmeasure = 1;
+    average[Nave].Nmissing = 0;
+
+    // assume the resulting table set is unsorted
+    average[Nave].measureOffset = -1;
+    average[Nave].missingOffset = -1;
+    average[Nave].extendOffset = -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;
+
+  // have we created all objects in the range 0 - Naverage?
+  for (i = 0; i < Naverage; i++) {
+    myAssert(found[i], "failed to find one");
+  }
+
+  // open source cpt file
+  cptFileSrc = fopen(cptFilenameSrc, "r");
+  myAssert(cptFileSrc, "failed to open cpt file");
+
+  // load the cpt header (use for CATID, RA,DEC range, filenames)
+  if (!gfits_fread_header (cptFileSrc, &cptHeaderPHU)) {
+    myAbort("failure to cpt header");
+  }
+
+  // update the output header
+  gfits_modify (&cptHeaderPHU, "NSTARS",     "%d",      1,  Naverage);
+  gfits_modify (&cptHeaderPHU, "NMEAS",      OFF_T_FMT, 1,  Nmeasure);
+  gfits_modify (&cptHeaderPHU, "NMISS",      "%d",      1,  0);
+  gfits_modify_alt (&cptHeaderPHU, "SORTED", "%t",      1,  FALSE);
+
+  /* convert internal to external format */
+  if (!AverageToFtable (&cptFtable, average, Naverage, catformat, NULL)) {
+    myAbort("trouble converting format");
+  }
+
+  // create and write the output file
+  cptFileTgt = fopen(cptFilenameTgt, "w");
+  myAssert(cptFileTgt, "failed to open cpt file");
+    
+  // write PHU header
+  if (!gfits_fwrite_header (cptFileTgt, &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  (cptFileTgt, &matrix)) {
+    myAbort("can't write primary matrix");
+  }
+  gfits_free_matrix (&matrix);
+
+  // write the table data
+  if (!gfits_fwrite_ftable_range (cptFileTgt, &cptFtable, 0, Naverage, 0, Naverage)) {
+    myAbort("can't write table data");
+  }
+  
+  fclose(cptFileTgt);
+  fclose(cptFileSrc);
+  fclose(cpmFile);
+
+  exit (0);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImageList.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImageList.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairDeleteImageList.c	(revision 29938)
@@ -0,0 +1,568 @@
+# 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 dvorepairDeleteImageList (int argc, char **argv) {
+
+  FITS_DB db;  // database handle pointing to input image table
+
+  off_t i, j, Nmeasure, NmeasureNew, Ndelete, Nvalid, Nimage, Nindex, *imageIdx, index;
+  int N, seq, Nbytes, NbytesPerRow, Ncheck, nPass, raPass;
+  double Rthis, Dthis, Rmin, Rmax, Dmin, Dmax, Qthis, Qmin, Qmax, dR, dQ;
+
+  Image *image;
+  Measure *measure;
+  Measure *measureNew;
+
+  Matrix matrix;
+
+  SkyTable *insky;
+  SkyList *inlist;
+
+  char *cpmFilenameSrc = NULL;
+  char *cptFilenameSrc = NULL;
+  char *cpsFilenameSrc = NULL;
+  char *cpmFilenameTgt = NULL;
+  char *cptFilenameTgt = NULL;
+  char *cpsFilenameTgt = NULL;
+
+  char *imageFilename = NULL;
+
+  Header cpmHeaderPHU;
+  Header cpmHeaderTBL;
+  FTable cpmFtable;
+
+  FILE *cpmFile = NULL;
+
+  char catformat;
+
+  N = get_argument (argc, argv, "-delete-image-list");
+  myAssert(N == 1, "programming error: -delete-image-list must be first from main");
+  remove_argument (N, &argc, argv);
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: dvorepair -delete-image-list (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);
+
+  // generate an index for imageIDs
+
+  // find the full range of the imageID values
+  Nindex = 0;
+  for (i = 0; i < Nimage; i++) {
+    Nindex = MAX(image[i].imageID, Nindex);
+  }
+
+  // create the index vector
+  ALLOCATE (imageIdx, off_t, (Nindex + 1));
+  memset (imageIdx, 0, (Nindex + 1)*sizeof(off_t));
+
+  // assign the imageIDs to the index vector
+  for (i = 0; i < Nimage; i++) {
+    index = image[i].imageID;
+    myAssert(index, "image index cannot be 0");
+    myAssert(!imageIdx[index], "stomping on an earlier index");
+    imageIdx[index] = i;
+  }
+
+  // generate an index of the deletion image
+  int *deleteIndex;
+  ALLOCATE (deleteIndex, int, Nindex + 1);
+  memset (deleteIndex, 0, (Nindex + 1)*sizeof(int));
+
+  int NdeleteIDs;
+  int *deleteIDs = ReadDeleteList(delList, &NdeleteIDs);
+
+  for (i = 0; i < NdeleteIDs; i++) {
+    index = deleteIDs[i];
+    myAssert(index > 0, "image index cannot be <= 0");
+    myAssert(index <= Nindex, "delete index out of range of real data");
+    myAssert(!deleteIndex[index], "stomping on an earlier index");
+    deleteIndex[index] = TRUE;
+  }
+
+  // find the RA & DEC range of the images we want to delete
+  Rmin = 360.0; 
+  Rmax = 0.0;
+  Dmin = +90.0;
+  Dmax = -90.0;
+  Qmin = 360.0;
+  Qmax =   0.0;
+  for (i = 0; i < NdeleteIDs; i++) {
+    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);
+    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[seq].NX, 0, &image[seq].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[seq].NY, &image[seq].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[seq].NX, image[seq].NY, &image[seq].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);
+  }
+
+  dQ = Qmax - Qmin;
+  dR = Rmax - Rmin;
+
+  // load the sky table for the existing database
+  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) {
+    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);
+
+  for (raPass = 0; raPass < nPass; raPass++) {
+    
+    UserPatch.Dmin = Dmin + 0.1;
+    UserPatch.Dmax = Dmax + 0.1;
+
+    if (dR < dQ) {
+      UserPatch.Rmin = Rmin;
+      UserPatch.Rmax = Rmax;
+    } else {
+      if (raPass == 0) {
+	UserPatch.Rmin = 0.0;
+	UserPatch.Rmax = Qmax;
+      } else {
+	UserPatch.Rmin = Qmin + 360.0;
+	UserPatch.Rmax = 360.0;
+      }
+    }
+
+    inlist = SkyListByPatch (insky, -1, &UserPatch);
+  
+    // SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+    // depth = inlist[0].regions[Ns][0].depth;
+  
+    // loop over the populated input regions
+    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.fixed", catdir, inlist[0].regions[i][0].name);
+      sprintf (cptFilenameTgt, "%s/%s.cpt.fixed", catdir, inlist[0].regions[i][0].name);
+      sprintf (cpsFilenameTgt, "%s/%s.cps.fixed", catdir, inlist[0].regions[i][0].name);
+      cpmFtable.header = &cpmHeaderTBL;
+
+      /*** read and examine the CPM file ***/
+      {
+	// fprintf (stderr, "check %s\n", cpmFilenameSrc);
+
+	// open cpm 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
+	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");
+	}
+
+	gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
+
+	measure = FtableToMeasure (&cpmFtable, &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++) {
+	  index = measure[j].imageID;
+	  myAssert(index, "measure is missing an image ID");
+
+	  if (deleteIndex[index]) {
+	    Ndelete ++;
+	    continue;
+	  }
+	  measureNew[NmeasureNew] = measure[j];
+	  NmeasureNew ++;
+	}
+      }
+
+      fprintf (stderr, "deleting %d of %d (keep %d) detections from %s -> %s\n", (int) Ndelete, (int) Nvalid, (int) NmeasureNew, cpmFilenameSrc, cpmFilenameTgt);
+
+      // if we actually want to delete any measurements, write out a new cpm file
+      if (Ndelete > 0) {
+
+	// the CPT and CPS tables need to be regenerated.  This must happen first because, in the process, we also update measure->averef
+	RepairTableCPT(cptFilenameSrc, cptFilenameTgt, cpsFilenameSrc, cpsFilenameTgt, measureNew, NmeasureNew, image, Nimage, catformat);
+
+	// convert internal to external format 
+	if (!MeasureToFtable (&cpmFtable, measureNew, NmeasureNew, catformat)) {
+	  myAbort("trouble converting format");
+	}
+
+	// create and write the output file
+	cpmFile = fopen(cpmFilenameTgt, "w");
+	myAssert(cpmFile, "failed to open cpt file");
+	
+	// write PHU header
+	if (!gfits_fwrite_header (cpmFile, &cpmHeaderPHU)) {
+	  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 (&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_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);
+  }
+
+  free (imageFilename);
+  free (imageIdx);
+  free (deleteIndex);
+  free (deleteIDs);
+
+  free(cpmFilenameSrc);
+  free(cptFilenameSrc);
+  free(cpsFilenameSrc);
+  free(cpmFilenameTgt);
+  free(cptFilenameTgt);
+  free(cpsFilenameTgt);
+
+  SkyTableFree(insky);
+
+  exit (0);
+}
+
+int RepairTableCPT(char *cptFilenameSrc, char *cptFilenameTgt, char *cpsFilenameSrc, char *cpsFilenameTgt, Measure *measure, off_t Nmeasure, Image *image, off_t Nimage, char catformat) {
+
+  off_t *averefMatch;
+  off_t i, NaveMax, Naverage, NAVERAGE, NaverageOut, Nave, Nout, Nold;
+  int *found, Nsecfilt;
+
+  Image *thisImage;
+  Average *average, *averageOut;
+
+  Matrix matrix;
+
+  Header cptHeaderPHU;
+  Header cptHeaderTBL;
+  FTable cptFtable;
+
+  FILE *cptFileSrc = NULL;
+  FILE *cptFileTgt = NULL;
+
+  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 new objects as needed
+  for (i = 0; i < Nmeasure; i++) {
+    Nave = measure[i].averef;
+
+    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));
+    }
+
+    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
+    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].Nmeasure = 1;
+    average[Nave].Nmissing = 0;
+
+    // assume the resulting table set is unsorted
+    average[Nave].measureOffset = -1;
+    average[Nave].missingOffset = -1;
+    average[Nave].extendOffset = -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;
+
+  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
+  cptFileSrc = fopen(cptFilenameSrc, "r");
+  myAssert(cptFileSrc, "failed to open cpt file");
+
+  // load the cpt header (use for CATID, RA,DEC range, filenames)
+  if (!gfits_fread_header (cptFileSrc, &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");
+  }
+
+  // create and write the output file
+  cptFileTgt = fopen(cptFilenameTgt, "w");
+  myAssert(cptFileTgt, "failed to open cpt file");
+    
+  // write PHU header
+  if (!gfits_fwrite_header (cptFileTgt, &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  (cptFileTgt, &matrix)) {
+    myAbort("can't write primary matrix");
+  }
+  gfits_free_matrix (&matrix);
+
+  // write the table data
+  if (!gfits_fwrite_ftable_range (cptFileTgt, &cptFtable, 0, NaverageOut, 0, NaverageOut)) {
+    myAbort("can't write table data");
+  }
+  
+  fclose(cptFileTgt);
+  fclose(cptFileSrc);
+
+  gfits_free_header (&cptHeaderPHU);
+  gfits_free_header (&cptHeaderTBL);
+  free (average);
+  free (averageOut);
+
+  free (found);
+  free (averefMatch);
+
+  { 
+    Header cpsHeaderPHU;
+    Header cpsHeaderTBL;
+    FTable cpsFtable;
+
+    FILE *cpsFileSrc = NULL;
+    FILE *cpsFileTgt = NULL;
+
+    SecFilt *secfilt = NULL;
+
+    cpsFtable.header = &cpsHeaderTBL;
+
+    // open source cpt file
+    cpsFileSrc = fopen(cpsFilenameSrc, "r");
+    myAssert(cpsFileSrc, "failed to open cps file");
+    
+    // load the cps header (use for CATID, RA,DEC range, filenames)
+    if (!gfits_fread_header (cpsFileSrc, &cpsHeaderPHU)) {
+      myAbort("failure to cps header");
+    }
+
+    int Nrows = Nsecfilt*NaverageOut;
+    ALLOCATE (secfilt, SecFilt, Nrows);
+
+    /* convert internal to external format */
+    if (!SecFiltToFtable (&cpsFtable, secfilt, Nrows, catformat)) {
+      myAbort("trouble converting format");
+    }
+
+    // create and write the output file
+    cpsFileTgt = fopen(cpsFilenameTgt, "w");
+    myAssert(cpsFileTgt, "failed to open cps file");
+    
+    // write PHU header
+    if (!gfits_fwrite_header (cpsFileTgt, &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  (cpsFileTgt, &matrix)) {
+      myAbort("can't write primary matrix");
+    }
+    gfits_free_matrix (&matrix);
+
+    // write the table data
+    if (!gfits_fwrite_ftable_range (cpsFileTgt, &cpsFtable, 0, Nrows, 0, Nrows)) {
+      myAbort("can't write table data");
+    }
+  
+    fclose(cpsFileTgt);
+    fclose(cpsFileSrc);
+
+    gfits_free_header (&cpsHeaderPHU);
+    gfits_free_header (&cpsHeaderTBL);
+    free (secfilt);
+  }
+
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvorepairFixCPT.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairFixCPT.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairFixCPT.c	(revision 29938)
@@ -0,0 +1,160 @@
+# include "dvomerge.h"
+
+// broken cpt file, valid cpm file: we can recover everything in the cpt file from the cpm file:
+// * load the full cpm file
+// * loop over detections
+// * if ave_ref is new: create a new object
+//   * determine RA & DEC from ext_id
+//   * obj_id, cat_id are defined in detection
+
+int FixCPTfile (char *rootname, Image *image, off_t Nimage);
+
+int dvorepairFixCPT (int argc, char **argv) {
+
+  int i, j, N, Nfiles, NFILES;
+  off_t Nimage;
+  FITS_DB db;  // database handle pointing to input image table
+  Image *image;
+  char *listFilename, *imageFilename;
+  char **files;
+
+  N = get_argument (argc, argv, "-fix-cpt");
+  myAssert(N == 1, "programming error: -fix-cpt must be first from main");
+  remove_argument (N, &argc, argv);
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: dvorepair -fix-cpt (images) (filelist)\n");
+    exit (2);
+  }
+
+  imageFilename  = argv[1];
+  listFilename   = argv[2];
+
+  // XXX don't bother locking for now: this is totally manual..
+
+  // load the image data
+  if ((image = LoadImages (&db, imageFilename, &Nimage)) == NULL) return (FALSE);
+  BuildChipMatch (image, Nimage);
+
+  FILE *file = fopen (listFilename, "r");
+  myAssert(file, "failed to open list");
+
+  Nfiles = 0;
+  NFILES = 100;
+  ALLOCATE(files, char *, NFILES);
+  for (i = 0; i < NFILES; i++) {
+    ALLOCATE (files[i], char, 256);
+    memset (files[i], 0, 256);
+  }
+
+  for (i = 0; TRUE; i++) {
+    if (fscanf (file, "%s", files[i]) == EOF) {
+      break;
+    }
+    if (i == NFILES - 1) {
+      NFILES += 100;
+      REALLOCATE(files, char *, NFILES);
+      for (j = i + 1; j < NFILES; j++) {
+	ALLOCATE (files[j], char, 256);
+	memset (files[j], 0, 256);
+      }
+    }
+  }
+  Nfiles = i;
+  fclose(file);
+  
+  for (i = 0; i < Nfiles; i++) {
+    FixCPTfile(files[i], image, Nimage);
+  }
+
+  exit (0);
+}
+
+// fix the CPT file.  in the process, we rewrite the CPM file so that the averef entries are correctly set.
+int FixCPTfile (char *rootFilename, Image *image, off_t Nimage) {
+
+  char cptFilenameSrc[256], cptFilenameTgt[256], cpsFilenameSrc[256], cpsFilenameTgt[256], cpmFilenameSrc[256], cpmFilenameTgt[256];
+
+  Header cpmHeaderPHU, cpmHeaderTBL;
+  FTable cpmFtable;
+  FILE  *cpmFile = NULL;
+
+  off_t Nbytes, Nmeasure;
+
+  Measure *measure;
+
+  Matrix matrix;
+
+  char catformat;
+
+  cpmFtable.header = &cpmHeaderTBL;
+
+  sprintf (cpmFilenameSrc, "%s.cpm", rootFilename);
+  sprintf (cpmFilenameTgt, "%s.cpm.fixed", rootFilename);
+  sprintf (cptFilenameSrc, "%s.cpt", rootFilename);
+  sprintf (cptFilenameTgt, "%s.cpt.fixed", rootFilename);
+  sprintf (cpsFilenameSrc, "%s.cps", rootFilename);
+  sprintf (cpsFilenameTgt, "%s.cps.fixed", rootFilename);
+
+  // open cpm file
+  cpmFile = fopen(cpmFilenameSrc, "r");
+  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
+  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 */
+  // XXX this should not be running on broken CPM files
+  if (!gfits_fread_ftable_data (cpmFile, &cpmFtable, FALSE)) { 
+    myAbort("can't read data for cpm table");
+  }
+
+  measure = FtableToMeasure (&cpmFtable, &Nmeasure, &catformat);
+  myAssert(measure, "failed to convert ftable to measure data");
+
+  // the CPT and CPS tables need to be regenerated.  This must happen first because, in the process, we also update measure->averef
+  RepairTableCPT(cptFilenameSrc, cptFilenameTgt, cpsFilenameSrc, cpsFilenameTgt, measure, Nmeasure, image, Nimage, catformat);
+
+  // convert internal to external format 
+  if (!MeasureToFtable (&cpmFtable, measure, Nmeasure, catformat)) {
+    myAbort("trouble converting format");
+  }
+
+  // create and write the output file
+  cpmFile = fopen(cpmFilenameTgt, "w");
+  myAssert(cpmFile, "failed to open cpt file");
+	
+  // write PHU header
+  if (!gfits_fwrite_header (cpmFile, &cpmHeaderPHU)) {
+    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 (&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, Nmeasure, 0, Nmeasure)) {
+    myAbort("can't write table data");
+  }
+
+  gfits_free_header (&cpmHeaderPHU);
+  gfits_free_header (&cpmHeaderTBL);
+  free (measure);
+  fclose(cpmFile);
+  
+  return TRUE;
+}
Index: /trunk/Ohana/src/dvomerge/src/dvorepairFixImages.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairFixImages.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairFixImages.c	(revision 29938)
@@ -0,0 +1,289 @@
+# 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])
+// * XXX need to find the DIS images for the WRP images to be deleted 
+// ** BuildChipMatch
+// ** copy ChipMatch, create index
+// ** sort index, ChipMatch by ChipMatch
+// ** loop over ChipMatch, counting delete/no delete for uniq values
+
+void SortChipMatch (off_t *M, off_t *I, off_t N);
+int MarkMosaicsToDelete(Image *image, off_t Nimage, int *deleteIndex);
+
+int dvorepairFixImages (int argc, char **argv) {
+
+  FITS_DB db;  // database handle pointing to input image table
+
+  off_t i, Nimage, Nout, Nindex, *imageIdx, index;
+  
+  int N;
+
+  Image *image, *imageOut;
+
+  char *imageFilenameOld = NULL;
+  char *imageFilenameNew = NULL;
+
+  N = get_argument (argc, argv, "-fix-images");
+  myAssert(N == 1, "programming error: -fix-images must be first from main");
+  remove_argument (N, &argc, argv);
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: dvorepair -fix-images (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(imageFilenameOld, char, strlen(catdir) + 12);
+  sprintf (imageFilenameOld, "%s/Images.dat", catdir);
+
+  ALLOCATE(imageFilenameNew, char, strlen(catdir) + 24);
+  sprintf (imageFilenameNew, "%s/Images.dat.fixed", catdir);
+
+  if ((image = LoadImages (&db, imageFilenameOld, &Nimage)) == NULL) {
+    fprintf (stderr, "error loading images\n");
+    exit (1);
+  }
+  BuildChipMatch (image, Nimage);
+
+  // generate an index for imageIDs
+
+  // find the full range of the imageID values
+  Nindex = 0;
+  for (i = 0; i < Nimage; i++) {
+    Nindex = MAX(image[i].imageID, Nindex);
+  }
+
+  // create the index vector
+  ALLOCATE (imageIdx, off_t, (Nindex + 1));
+  memset (imageIdx, 0, (Nindex + 1)*sizeof(off_t));
+
+  // assign the imageIDs to the index vector
+  for (i = 0; i < Nimage; i++) {
+    index = image[i].imageID;
+    myAssert(index, "image index cannot be 0");
+    myAssert(!imageIdx[index], "stomping on an earlier index");
+    imageIdx[index] = i;
+  }
+
+  // generate an index of the deletion image
+  int *deleteIndex;
+  ALLOCATE (deleteIndex, int, Nindex + 1);
+  memset (deleteIndex, 0, (Nindex + 1)*sizeof(int));
+
+  int NdeleteIDs;
+  int *deleteIDs = ReadDeleteList(delList, &NdeleteIDs);
+
+  for (i = 0; i < NdeleteIDs; i++) {
+    index = deleteIDs[i];
+    myAssert(index > 0, "image index cannot be <= 0");
+    myAssert(index <= Nindex, "delete index out of range of real data");
+    myAssert(!deleteIndex[index], "stomping on an earlier index");
+    deleteIndex[index] = TRUE;
+  }
+
+  MarkMosaicsToDelete(image, Nimage, deleteIndex);
+
+  // now create a new output Image table with only the non-deletions
+  ALLOCATE (imageOut, Image, Nimage);
+  
+  Nout = 0;
+  for (i = 0; i < Nimage; i++) {
+    index = image[i].imageID;
+    if (deleteIndex[index]) continue;
+    imageOut[Nout] = image[i];
+    Nout++;
+  }
+
+  SaveImages(&db, imageFilenameNew, imageOut, Nout);
+
+  free (imageFilenameOld);
+  free (imageFilenameNew);
+  free (imageIdx);
+  free (deleteIndex);
+  free (deleteIDs);
+
+  exit (0);
+}
+
+// sort two times vectors and an index by first time vector
+void SortChipMatch (off_t *M, off_t *I, off_t N) {
+
+# define SWAPFUNC(A,B){ off_t tmp_m; off_t tmp_i;	\
+    tmp_m = M[A]; M[A] = M[B]; M[B] = tmp_m;		\
+    tmp_i = I[A]; I[A] = I[B]; I[B] = tmp_i;		\
+  }
+# define COMPARE(A,B)(M[A] < M[B])
+  OHANA_SORT (N, COMPARE, SWAPFUNC);
+# undef SWAPFUNC
+# undef COMPARE
+}
+
+int MarkMosaicsToDelete(Image *image, off_t Nimage, int *deleteIndex) {
+
+  off_t i;
+  
+  off_t *ChipByMosaicID, *ChipIndex, *mosaicIDs;
+  off_t mosaic, Nmosaic, NMOSAIC, chipSeq, chipID, mosaicID;
+  int *nChildren, *nToDelete;
+  int Na, Nb, Nc;
+
+  /* We now have a table of images to delete by image ID : but only WRP images have been
+     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) 
+  // generate an associated image index and sort by the mosaic IDs (value of ChipMatch table)
+  ALLOCATE(ChipByMosaicID, off_t, Nimage);
+  off_t *ChipMatchRaw = GetChipMatch();
+  myAssert(ChipMatchRaw, "need to run BuildChipMatch first");
+
+  memcpy (ChipByMosaicID, ChipMatchRaw, sizeof(off_t)*Nimage);
+  ALLOCATE (ChipIndex, off_t, Nimage);
+  for (i = 0; i < Nimage; i++) {
+    ChipIndex[i] = i;
+  }
+  SortChipMatch(ChipByMosaicID, ChipIndex, Nimage);
+
+  // now count the number of children images with none/some/all marked for deletion
+  // we don't know the number of mosaic images, so allocate as we go
+
+  Nmosaic = 0;
+  NMOSAIC = 1000;
+  ALLOCATE (nChildren, int,   NMOSAIC); memset(&nChildren[Nmosaic], 0, (NMOSAIC-Nmosaic)*sizeof(int));
+  ALLOCATE (nToDelete, int,   NMOSAIC); memset(&nToDelete[Nmosaic], 0, (NMOSAIC-Nmosaic)*sizeof(int));
+  ALLOCATE (mosaicIDs, off_t, NMOSAIC); memset(&mosaicIDs[Nmosaic], 0, (NMOSAIC-Nmosaic)*sizeof(off_t));
+
+  // scan past the mosaic (-3), unassigned (-2) and non-chip (-1) entries 
+  for (i = 0; (i < Nimage) && (ChipByMosaicID[i] == -3); i++);
+  Na = i;
+  fprintf (stderr, "skipping %d DIS entries\n", Na);
+
+  for (; (i < Nimage) && (ChipByMosaicID[i] == -2); i++);
+  Nb = i - Na;
+  fprintf (stderr, "skipping %d failed entries\n", Nb);
+  
+  for (; (i < Nimage) && (ChipByMosaicID[i] == -1); i++);
+  Nc = i - Na - Nb;
+  fprintf (stderr, "skipping %d single-chip entries\n", Nc);
+  
+  if (i == Nimage) {
+    fprintf (stderr, "there are no mosaic images in this database\n");
+    free (nChildren);
+    free (nToDelete);
+    free (mosaicIDs);
+    
+    free (ChipIndex);
+    free (ChipByMosaicID);
+    
+    return TRUE;
+  }
+
+  // mosaicIDs[] actually contains the sequence in images[] of the mosaic images
+  // do not confuse the sequence numbers and the imageID values
+
+  mosaic = ChipByMosaicID[i];
+  mosaicIDs[Nmosaic] = mosaic;
+  for (; i < Nimage; i++) {
+    if (ChipByMosaicID[i] != mosaic) {
+      // time for the next entry
+      Nmosaic ++;
+      if (Nmosaic == NMOSAIC - 1) {
+	NMOSAIC += 1000;
+	REALLOCATE (nChildren, int,   NMOSAIC); memset(&nChildren[Nmosaic], 0, (NMOSAIC-Nmosaic)*sizeof(int));
+	REALLOCATE (nToDelete, int,   NMOSAIC); memset(&nToDelete[Nmosaic], 0, (NMOSAIC-Nmosaic)*sizeof(int));
+	REALLOCATE (mosaicIDs, off_t, NMOSAIC); memset(&mosaicIDs[Nmosaic], 0, (NMOSAIC-Nmosaic)*sizeof(off_t));
+      }
+      mosaic = ChipByMosaicID[i];
+      mosaicIDs[Nmosaic] = mosaic;
+    }
+    nChildren[Nmosaic] ++;
+    chipSeq = ChipIndex[i];
+    chipID = image[chipSeq].imageID;
+    if (deleteIndex[chipID]) {
+      nToDelete[Nmosaic] ++;
+    }
+  }
+  Nmosaic ++;
+
+  // XXX test:
+  for (i = 0; i < Nmosaic; i++) {
+    if (nToDelete[i]) {
+      fprintf (stderr, "mosaic %lld (%lld) : %s : %d children : %d to delete\n",
+	       (long long) i, (long long) mosaicIDs[i], image[mosaicIDs[i]].name, nChildren[i], nToDelete[i]);
+    }
+
+    // mark the mosaic for deletion IFF all children are to be deleted
+    if (nToDelete[i] == nChildren[i]) {
+      mosaic = mosaicIDs[i];
+      mosaicID = image[mosaic].imageID;
+      deleteIndex[mosaicID] = TRUE;
+    }	    
+  }
+
+  free (nChildren);
+  free (nToDelete);
+  free (mosaicIDs);
+
+  free (ChipIndex);
+  free (ChipByMosaicID);
+
+  return TRUE;
+}
+
+int SaveImages(FITS_DB *oldDB, char *filename, Image *imageOut, off_t Nout) {
+
+  int status, Nx, nbytes;
+  off_t IDstart;
+  FITS_DB db;
+
+  /* setup image table format and lock */
+  db.mode   = oldDB->mode;
+  db.format = oldDB->format;
+  status    = dvo_image_lock (&db, filename, 3600.0, LCK_XCLD);  // shorter timeout?
+  if (!status) {
+    fprintf (stderr, "ERROR: failure to lock image catalog %s\n", db.filename);
+    exit (1);
+  }
+
+  /* load or create the image table */
+  myAssert (db.dbstate == LCK_EMPTY, "do not overwrite exiting image table");
+  myAssert (db.format == DVO_FORMAT_PS1_V2, "format mismatch");
+
+  dvo_image_create (&db, GetZeroPoint());
+  
+  // replace the existing buffer with the image array
+  gfits_scan (db.ftable.header, "NAXIS1", "%d", 1,  &Nx);
+  gfits_modify (db.ftable.header, "NAXIS2",  OFF_T_FMT, 1, Nout);
+  db.ftable.header[0].Naxis[1] = Nout;
+
+  nbytes = gfits_data_size (db.ftable.header);
+  db.ftable.datasize = nbytes;
+  db.ftable.buffer = (char *) imageOut;
+  REALLOCATE (db.ftable.buffer, char, MAX (nbytes, 1));
+  memset (&db.ftable.buffer[Nx*Nout], ' ', nbytes - Nx*Nout);
+
+  db.swapped = TRUE; // internal representation
+
+  gfits_modify (&db.header, "NIMAGES", OFF_T_FMT, 1,  Nout);
+
+  status = gfits_scan (&oldDB->header, "IMAGEID", OFF_T_FMT, 1, &IDstart);
+  myAssert(status, "db image table lacks IMAGEID");
+
+  gfits_modify (&db.header, "IMAGEID", OFF_T_FMT, 1,  IDstart);
+
+  dvo_image_save (&db, TRUE);
+
+  dvo_image_unlock (&db);
+
+  return TRUE;
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvorepairFixTables.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairFixTables.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairFixTables.c	(revision 29938)
@@ -0,0 +1,423 @@
+# include "dvomerge.h"
+
+// repair short cpm files, fix inconsistent cpt / cps / cpm files
+// * 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 dvorepairDeleteImageList (int argc, char **argv) {
+
+  FITS_DB db;  // database handle pointing to input image table
+
+  off_t i, j, Nmeasure, NmeasureNew, Ndelete, Nvalid, Nimage, Nindex, *imageIdx, index;
+  int N, seq, Nbytes, NbytesPerRow, Ncheck, nPass, raPass;
+  double Rthis, Dthis, Rmin, Rmax, Dmin, Dmax, Qthis, Qmin, Qmax, dR, dQ;
+
+  Measure *measure;
+  Measure *measureNew;
+
+  SkyTable *insky;
+  SkyList *inlist;
+
+  char *cpmFilenameSrc = NULL;
+  char *cptFilenameSrc = NULL;
+  char *cpsFilenameSrc = NULL;
+  char *cpmFilenameTgt = NULL;
+  char *cptFilenameTgt = NULL;
+  char *cpsFilenameTgt = NULL;
+
+  Header cpmHeaderPHU;
+  Header cpmHeaderTBL;
+  FTable cpmFtable;
+
+  FILE *cpmFile = NULL;
+
+  char catformat;
+
+  N = get_argument (argc, argv, "-fix-tables");
+  myAssert(N == 1, "programming error: -fix-tables must be first from main");
+  remove_argument (N, &argc, argv);
+
+  // restrict to a portion of the 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 (argc != 2) {
+    fprintf (stderr, "USAGE: dvorepair -fix-tables (catdir) [-region Rmin Rmax Dmin Dmax]\n");
+    fprintf (stderr, "  catdir : database of interest\n");
+    exit (2);
+  }
+
+  char *catdir = argv[1];
+
+  // load the sky table for the existing database
+  insky = SkyTableLoadOptimal (catdir, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
+  myAssert(insky, "can't read SkyTable");
+  SkyTableSetFilenames (insky, catdir, "cpt");
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+  
+  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);
+
+  // loop over the tables
+  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.fixed", catdir, inlist[0].regions[i][0].name);
+    sprintf (cptFilenameTgt, "%s/%s.cpt.fixed", catdir, inlist[0].regions[i][0].name);
+    sprintf (cpsFilenameTgt, "%s/%s.cps.fixed", catdir, inlist[0].regions[i][0].name);
+    cpmFtable.header = &cpmHeaderTBL;
+
+    measure = LoadTableCPM(cpmFilenameSrc, &Nmeasure, &Nexpect);
+
+    // the CPT and CPS tables need to be regenerated.  This must happen first because, in the process, we also update measure->averef
+    RepairTableCPT(cptFilenameSrc, cptFilenameTgt, cpsFilenameSrc, cpsFilenameTgt, measureNew, NmeasureNew, image, Nimage, catformat);
+
+    // if the file is short, create
+    if (Nmeasure != Nexpect) { 
+      fprintf (stderr, "deleting %d of %d (keep %d) detections from %s -> %s\n", (int) Ndelete, (int) Nvalid, (int) NmeasureNew, cpmFilenameSrc, cpmFilenameTgt);
+
+      // convert internal to external format 
+      if (!MeasureToFtable (&cpmFtable, measureNew, NmeasureNew, catformat)) {
+	myAbort("trouble converting format");
+      }
+
+      // create and write the output file
+      cpmFile = fopen(cpmFilenameTgt, "w");
+      myAssert(cpmFile, "failed to open cpt file");
+	
+      // write PHU header
+      if (!gfits_fwrite_header (cpmFile, &cpmHeaderPHU)) {
+	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 (&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_header (&cpmHeaderPHU);
+    gfits_free_header (&cpmHeaderTBL);
+    free (measure);
+    free (measureNew);
+
+    Ncheck ++;
+    if (Ncheck % 1000 == 0) {
+      fprintf (stderr, "%s...", inlist[0].regions[i][0].name);
+    }
+  }
+  SkyListFree(inlist);
+  SkyTableFree(insky);
+
+  free(cpmFilenameSrc);
+  free(cptFilenameSrc);
+  free(cpsFilenameSrc);
+  free(cpmFilenameTgt);
+  free(cptFilenameTgt);
+  free(cpsFilenameTgt);
+
+  exit (0);
+}
+
+// load this CPM file : is it short?
+Measure *LoadTableCPM (char *cpmFilenameSrc, off_t *nmeasure, off_t *nexpect) {
+
+  fprintf (stderr, "check %s\n", cpmFilenameSrc);
+
+  // open cpm file
+  cpmFile = fopen(cpmFilenameSrc, "r");
+  if (!cpmFile) continue;
+    
+  // load the cpm header
+  if (!gfits_fread_header (cpmFile, &cpmHeaderPHU)) {
+    myAbort("failure to cpm header");
+  }
+
+  // move to TBL header
+  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");
+  }
+
+  gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
+  gfits_scan(&cpmHeaderTBL, "NAXIS2", "%d", 1, &Nrows);
+
+  measure = FtableToMeasure (&cpmFtable, &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);
+
+  myAssert(Nrows >= Nvalid, "how can we find more entries than expected??");
+
+  *nmeasure = Nvalid;
+  *nexpect = Nrows;
+
+  return measure;
+}
+
+int RepairTableCPT(char *cptFilenameSrc, char *cptFilenameTgt, char *cpsFilenameSrc, char *cpsFilenameTgt, Measure *measure, off_t Nmeasure, Image *image, off_t Nimage, char catformat) {
+
+  off_t *averefMatch;
+  off_t i, NaveMax, Naverage, NAVERAGE, NaverageOut, Nave, Nout, Nold;
+  int *found, Nsecfilt;
+
+  Image *thisImage;
+  Average *average, *averageOut;
+
+  Matrix matrix;
+
+  Header cptHeaderPHU;
+  Header cptHeaderTBL;
+  FTable cptFtable;
+
+  FILE *cptFileSrc = NULL;
+  FILE *cptFileTgt = NULL;
+
+  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 new objects as needed
+  for (i = 0; i < Nmeasure; i++) {
+    Nave = measure[i].averef;
+
+    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));
+    }
+
+    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
+    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].Nmeasure = 1;
+    average[Nave].Nmissing = 0;
+
+    // assume the resulting table set is unsorted
+    average[Nave].measureOffset = -1;
+    average[Nave].missingOffset = -1;
+    average[Nave].extendOffset = -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;
+
+  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
+  cptFileSrc = fopen(cptFilenameSrc, "r");
+  myAssert(cptFileSrc, "failed to open cpt file");
+
+  // load the cpt header (use for CATID, RA,DEC range, filenames)
+  if (!gfits_fread_header (cptFileSrc, &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");
+  }
+
+  // create and write the output file
+  cptFileTgt = fopen(cptFilenameTgt, "w");
+  myAssert(cptFileTgt, "failed to open cpt file");
+    
+  // write PHU header
+  if (!gfits_fwrite_header (cptFileTgt, &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  (cptFileTgt, &matrix)) {
+    myAbort("can't write primary matrix");
+  }
+  gfits_free_matrix (&matrix);
+
+  // write the table data
+  if (!gfits_fwrite_ftable_range (cptFileTgt, &cptFtable, 0, NaverageOut, 0, NaverageOut)) {
+    myAbort("can't write table data");
+  }
+  
+  fclose(cptFileTgt);
+  fclose(cptFileSrc);
+
+  gfits_free_header (&cptHeaderPHU);
+  gfits_free_header (&cptHeaderTBL);
+  free (average);
+  free (averageOut);
+
+  free (found);
+  free (averefMatch);
+
+  { 
+    Header cpsHeaderPHU;
+    Header cpsHeaderTBL;
+    FTable cpsFtable;
+
+    FILE *cpsFileSrc = NULL;
+    FILE *cpsFileTgt = NULL;
+
+    SecFilt *secfilt = NULL;
+
+    cpsFtable.header = &cpsHeaderTBL;
+
+    // open source cpt file
+    cpsFileSrc = fopen(cpsFilenameSrc, "r");
+    myAssert(cpsFileSrc, "failed to open cps file");
+    
+    // load the cps header (use for CATID, RA,DEC range, filenames)
+    if (!gfits_fread_header (cpsFileSrc, &cpsHeaderPHU)) {
+      myAbort("failure to cps header");
+    }
+
+    int Nrows = Nsecfilt*NaverageOut;
+    ALLOCATE (secfilt, SecFilt, Nrows);
+
+    /* convert internal to external format */
+    if (!SecFiltToFtable (&cpsFtable, secfilt, Nrows, catformat)) {
+      myAbort("trouble converting format");
+    }
+
+    // create and write the output file
+    cpsFileTgt = fopen(cpsFilenameTgt, "w");
+    myAssert(cpsFileTgt, "failed to open cps file");
+    
+    // write PHU header
+    if (!gfits_fwrite_header (cpsFileTgt, &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  (cpsFileTgt, &matrix)) {
+      myAbort("can't write primary matrix");
+    }
+    gfits_free_matrix (&matrix);
+
+    // write the table data
+    if (!gfits_fwrite_ftable_range (cpsFileTgt, &cpsFtable, 0, Nrows, 0, Nrows)) {
+      myAbort("can't write table data");
+    }
+  
+    fclose(cpsFileTgt);
+    fclose(cpsFileSrc);
+
+    gfits_free_header (&cpsHeaderPHU);
+    gfits_free_header (&cpsHeaderTBL);
+    free (secfilt);
+  }
+
+  return (TRUE);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/dvorepairImageVsMeasure.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairImageVsMeasure.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairImageVsMeasure.c	(revision 29938)
@@ -0,0 +1,178 @@
+# include "dvomerge.h"
+
+# define myAbort(MSG) { fprintf (stderr, "%s\n", MSG); abort(); }
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
+// find images which are missing detections:
+// * load the Images.dat file
+// * create an index for imageID (seq = imageIDindex[i])
+// * scan over all catalog files
+// * load the cpm file (pad if short, identify the padded section)
+// * loop over detections: increment detection count for each imageID
+
+int main (int argc, char **argv) {
+
+  off_t i, j, Nmeasure, Nvalid, Nimage, Nindex, *imageIdx, index, imageSeq;
+  int Nbytes, NbytesPerRow, Nbad, Ncheck, Ntol;
+  int *detCounts;
+
+  Image *image;
+  Measure *measure;
+
+  SkyTable *insky;
+  SkyList *inlist;
+
+  char *catdir = NULL;
+  char *cpmFilename = NULL;
+  char *imageFilename = NULL;
+
+  Header cpmHeaderPHU;
+  Header cpmHeaderTBL;
+  FTable cpmFtable;
+
+  FILE *cpmFile = NULL;
+
+  char catformat;
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: dvorepair (catdir) (Ntol)\n");
+    fprintf (stderr, "  catdir : database of interest\n");
+    fprintf (stderr, "  Ntol : allow Ntol missing detections\n");
+    exit (2);
+  }
+
+  catdir = argv[1];
+  Ntol = atoi(argv[2]);
+
+  // load the image data
+  ALLOCATE(imageFilename, char, strlen(catdir) + 12);
+  sprintf (imageFilename, "%s/Images.dat", catdir);
+  if ((image = LoadImages (imageFilename, &Nimage)) == NULL) return (FALSE);
+  BuildChipMatch (image, Nimage);
+
+  // generate an index for imageIDs:
+  Nindex = 0;
+  for (i = 0; i < Nimage; i++) {
+    Nindex = MAX(image[i].imageID, Nindex);
+  }
+  ALLOCATE (imageIdx, off_t, (Nindex + 1));
+  memset (imageIdx, 0, (Nindex + 1)*sizeof(off_t));
+
+  for (i = 0; i < Nimage; i++) {
+    index = image[i].imageID;
+    if (index == 0) {
+      fprintf (stderr, "?");
+      continue;
+    }
+    if (imageIdx[index]) {
+      fprintf (stderr, "!");
+      continue;
+    }
+    imageIdx[index] = i;
+  }
+
+  // generate a list of the detection counts:
+  ALLOCATE (detCounts, int, Nimage);
+  memset (detCounts, 0, Nimage*sizeof(int));
+
+  // load the sky table for the existing database
+  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;
+  UserPatch.Rmin = 0.0;
+  UserPatch.Rmax = 360.0;
+  UserPatch.Dmin = -90.0;
+  UserPatch.Dmax = +90.0;
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+
+  // SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+  // depth = inlist[0].regions[Ns][0].depth;
+  
+  ALLOCATE(cpmFilename, char, strlen(catdir) + 64);
+
+  // loop over the populated input regions
+  Ncheck = 0;
+  for (i = 0; i < inlist[0].Nregions; i++) {
+    if (!inlist[0].regions[i][0].table) continue;
+
+    if (1) {
+      sprintf (cpmFilename, "%s/%s.cpm", catdir, inlist[0].regions[i][0].name);
+      cpmFtable.header = &cpmHeaderTBL;
+
+      // open cpm file
+      cpmFile = fopen(cpmFilename, "r");
+      if (!cpmFile) continue;
+      // myAssert(cpmFile, "failed to open cpm file");
+    
+      // fprintf (stderr, "input: %s\n", inlist[0].regions[i][0].name);
+
+      // load the cpm header
+      if (!gfits_fread_header (cpmFile, &cpmHeaderPHU)) {
+	myAbort("failure to cpm header");
+      }
+
+      // move to TBL header
+      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");
+      }
+
+      gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
+
+      measure = FtableToMeasure (&cpmFtable, &Nmeasure, &catformat);
+      myAssert(measure, "failed to convert ftable to measure data");
+    
+      Nvalid = (int)(cpmFtable.validsize / NbytesPerRow);
+      Nvalid = MIN(Nmeasure, Nvalid);
+
+      // examine all measurements and new objects as needed
+      for (j = 0; j < Nvalid; j++) {
+	index = measure[j].imageID;
+	if (!index) {
+	  fprintf (stderr, "?");
+	  continue;
+	}
+
+	imageSeq = imageIdx[index];
+	// XXX check the range?
+
+	detCounts[imageSeq] ++;
+      }
+      fclose(cpmFile);
+      gfits_free_header (&cpmHeaderPHU);
+      gfits_free_header (&cpmHeaderTBL);
+      free (measure);
+
+      Ncheck ++;
+      if (Ncheck % 1000 == 0) {
+	fprintf (stderr, "%s...", inlist[0].regions[i][0].name);
+      }
+    }
+  }
+  fprintf (stderr, "\n");
+
+  Nbad = 0;
+  for (i = 0; i < Nimage; i++) {
+    // careful: off_t math does not do well with subtractions...
+    if (detCounts[i] + Ntol < image[i].nstar) {
+      fprintf (stdout, "image %s (%d) : %d vs %d\n", image[i].name, image[i].imageID, detCounts[i], image[i].nstar);
+      Nbad ++;
+    }
+  }
+  if (!Nbad) {
+    fprintf (stderr, "no bad images found\n");
+  }
+
+  exit (0);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvorepairImagesVsMeasures.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvorepairImagesVsMeasures.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/dvorepairImagesVsMeasures.c	(revision 29938)
@@ -0,0 +1,181 @@
+# include "dvomerge.h"
+
+// find images which are missing detections:
+// * load the Images.dat file
+// * create an index for imageID (seq = imageIDindex[i])
+// * scan over all catalog files
+// * load the cpm file (pad if short, identify the padded section)
+// * loop over detections: increment detection count for each imageID
+
+int dvorepairImagesVsMeasures (int argc, char **argv) {
+
+  FITS_DB db;  // database handle pointing to input image table
+
+  off_t i, j, Nmeasure, Nvalid, Nimage, Nindex, *imageIdx, index, imageSeq;
+  int N, Nbytes, NbytesPerRow, Nbad, Ncheck, Ntol;
+  int *detCounts;
+
+  Image *image;
+  Measure *measure;
+
+  SkyTable *insky;
+  SkyList *inlist;
+
+  char *catdir = NULL;
+  char *cpmFilename = NULL;
+  char *imageFilename = NULL;
+
+  Header cpmHeaderPHU;
+  Header cpmHeaderTBL;
+  FTable cpmFtable;
+
+  FILE *cpmFile = NULL;
+
+  char catformat;
+
+  N = get_argument (argc, argv, "-images-vs-measures");
+  myAssert(N == 1, "programming error: -images-vs-measures must be first from main");
+  remove_argument (N, &argc, argv);
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: dvorepair -images-vs-measures (catdir) (Ntol)\n");
+    fprintf (stderr, "  catdir : database of interest\n");
+    fprintf (stderr, "  Ntol : allow Ntol missing detections\n");
+    exit (2);
+  }
+
+  catdir = argv[1];
+  Ntol = atoi(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);
+
+  // generate an index for imageIDs:
+  Nindex = 0;
+  for (i = 0; i < Nimage; i++) {
+    Nindex = MAX(image[i].imageID, Nindex);
+  }
+  ALLOCATE (imageIdx, off_t, (Nindex + 1));
+  memset (imageIdx, 0, (Nindex + 1)*sizeof(off_t));
+
+  for (i = 0; i < Nimage; i++) {
+    index = image[i].imageID;
+    if (index == 0) {
+      fprintf (stderr, "?");
+      continue;
+    }
+    if (imageIdx[index]) {
+      fprintf (stderr, "!");
+      continue;
+    }
+    imageIdx[index] = i;
+  }
+
+  // generate a list of the detection counts:
+  ALLOCATE (detCounts, int, Nimage);
+  memset (detCounts, 0, Nimage*sizeof(int));
+
+  // load the sky table for the existing database
+  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;
+  UserPatch.Rmin = 0.0;
+  UserPatch.Rmax = 360.0;
+  UserPatch.Dmin = -90.0;
+  UserPatch.Dmax = +90.0;
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+
+  // SkyListPopulatedRange (&Ns, &Ne, inlist, 0);
+  // depth = inlist[0].regions[Ns][0].depth;
+  
+  ALLOCATE(cpmFilename, char, strlen(catdir) + 64);
+
+  // loop over the populated input regions
+  Ncheck = 0;
+  for (i = 0; i < inlist[0].Nregions; i++) {
+    if (!inlist[0].regions[i][0].table) continue;
+
+    if (1) {
+      sprintf (cpmFilename, "%s/%s.cpm", catdir, inlist[0].regions[i][0].name);
+      cpmFtable.header = &cpmHeaderTBL;
+
+      // open cpm file
+      cpmFile = fopen(cpmFilename, "r");
+      if (!cpmFile) continue;
+      // myAssert(cpmFile, "failed to open cpm file");
+    
+      // fprintf (stderr, "input: %s\n", inlist[0].regions[i][0].name);
+
+      // load the cpm header
+      if (!gfits_fread_header (cpmFile, &cpmHeaderPHU)) {
+	myAbort("failure to cpm header");
+      }
+
+      // move to TBL header
+      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");
+      }
+
+      gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
+
+      measure = FtableToMeasure (&cpmFtable, &Nmeasure, &catformat);
+      myAssert(measure, "failed to convert ftable to measure data");
+    
+      Nvalid = (int)(cpmFtable.validsize / NbytesPerRow);
+      Nvalid = MIN(Nmeasure, Nvalid);
+
+      // examine all measurements and new objects as needed
+      for (j = 0; j < Nvalid; j++) {
+	index = measure[j].imageID;
+	if (!index) {
+	  fprintf (stderr, "?");
+	  continue;
+	}
+
+	imageSeq = imageIdx[index];
+	// XXX check the range?
+
+	detCounts[imageSeq] ++;
+      }
+      fclose(cpmFile);
+      gfits_free_header (&cpmHeaderPHU);
+      gfits_free_header (&cpmHeaderTBL);
+      free (measure);
+
+      Ncheck ++;
+      if (Ncheck % 1000 == 0) {
+	fprintf (stderr, "%s...", inlist[0].regions[i][0].name);
+      }
+    }
+  }
+  fprintf (stderr, "\n");
+
+  Nbad = 0;
+  for (i = 0; i < Nimage; i++) {
+    // careful: off_t math does not do well with subtractions...
+    if (detCounts[i] + Ntol < image[i].nstar) {
+      fprintf (stdout, "image %s (%d) : %d vs %d\n", image[i].name, image[i].imageID, detCounts[i], image[i].nstar);
+      Nbad ++;
+    }
+  }
+  if (!Nbad) {
+    fprintf (stderr, "no bad images found\n");
+  }
+
+  exit (0);
+}
Index: /trunk/Ohana/src/dvomerge/src/dvoverify.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/dvoverify.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/dvoverify.c	(revision 29938)
@@ -8,170 +8,210 @@
 */
 
+int VerifyTableFile (char *filename);
+
+# define DEBUG 0
+
 int main (int argc, char **argv) {
 
-  char filename[256], *input, *output;
-
-  int depth;
-  off_t i, j, Ns, Ne;
-  SkyTable *outsky, *insky;
+  char filename[1024];
+
+  int N, Nbad;
+  off_t i;
+  SkyTable *insky;
   SkyList *inlist;
-  Catalog incatalog, outcatalog;
-
-  SetSignals ();
-  dvoconvert_help (argc, argv);
-  ConfigInit (&argc, argv);
-  dvoconvert_args (&argc, argv);
-
-  if (strcasecmp (argv[2], "to")) dvoconvert_usage();
-  input = argv[1];
-  output = argv[3];
-
-  // load input images, save to output images
-  dvoConvert_copy_images (input, output);
-
-  // copy photcode table
-  { 
-    // the first input defines the photcode table & db layout
-    sprintf (filename, "%s/Photcodes.dat", input);
-    if (!LoadPhotcodes (filename, NULL, FALSE)) {
-      fprintf (stderr, "error loading photcode table %s\n", filename);
-      exit (1);
-    }
-    // save the photcodes in the output catdir
-    sprintf (filename, "%s/Photcodes.dat", output);
-    if (!check_file_access (filename, TRUE, TRUE, VERBOSE)) {
-      fprintf (stderr, "error creating output catdir %s\n", output);
-      exit (1);
-    }
-    if (!SavePhotcodesFITS (filename)) {
-      fprintf (stderr, "error saving photcode table %s\n", filename);
-      exit (1);
-    }
-  }
-
-  // copy skytable
-  { 
-    // load the sky table for the existing database
-    insky = SkyTableLoadOptimal (input, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
-    SkyTableSetFilenames (insky, input, "cpt");
-
-    // generate an output table populated at the desired depth
-    // XXX force this to match the input?
-    outsky = SkyTableLoadOptimal (output, NULL, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
-    SkyTableSetFilenames (outsky, output, "cpt");
-
-    SkyTablePopulatedRange (&Ns, &Ne, insky, 0);
-    depth = insky[0].regions[Ns].depth;
-    // XXX this seems to imply that insky is a uniform depth...
-  }
-
-  // loop over all input catalogs, save to output catalogs
-  // loop over the populatable output regions
-  for (i = 0; i < outsky[0].Nregions; i++) {
-    if (!outsky[0].regions[i].table) continue;
-    if (VERBOSE) fprintf (stderr, "output: %s\n", outsky[0].regions[i].name);
-
-    // load / create output catalog
-    LoadCatalog (&outcatalog, &outsky[0].regions[i], outsky[0].filename[i], "w");
-
-    // combine only tables at equal or larger depth
-      
-    // load in all of the tables from input for this region
-    inlist = SkyListByBounds (insky, depth, outsky[0].regions[i].Rmin, outsky[0].regions[i].Rmax, outsky[0].regions[i].Dmin, outsky[0].regions[i].Dmax);
-    for (j = 0; j < inlist[0].Nregions; j++) {
-      if (VERBOSE) fprintf (stderr, "input : %s\n", inlist[0].regions[j][0].name);
-
-      // load input catalog
-      LoadCatalog (&incatalog, inlist[0].regions[j], inlist[0].filename[j], "r");
-
-      // skip empty input catalogs
-      if (!incatalog.Naves_disk) {
-	dvo_catalog_unlock (&incatalog);
-	dvo_catalog_free (&incatalog);
-	continue;
-      }
-      merge_catalogs_new (&outsky[0].regions[i], &outcatalog, &incatalog);
-      dvo_catalog_unlock (&incatalog);
-      dvo_catalog_free (&incatalog);
-    }
-    SkyListFree (inlist);
-
-    outcatalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF;
-    dvo_catalog_save (&outcatalog, VERBOSE);
-    dvo_catalog_unlock (&outcatalog);
-    dvo_catalog_free (&outcatalog);
-  }
-
-  // save the output sky table copy
-  sprintf (filename, "%s/SkyTable.fits", output);
-  check_file_access (filename, TRUE, TRUE, VERBOSE);
-  if (!SkyTableSave (outsky, filename)) {
-    fprintf (stderr, "ERROR: failed to save sky table for %s\n", output);
+  SkyRegion UserPatch;
+  // Catalog catalog;
+
+  // restrict to a portion of the 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 (argc != 2) {
+    fprintf (stderr, "USAGE: dvoverify (catdir) [-region Rmin Rmax Dmin Dmax]\n");
+    fprintf (stderr, "  catdir : database of interest\n");
+    exit (2);
+  }
+
+  char *catdir = argv[1];
+
+  Nbad = 0;
+
+  // XXX make this step optional
+  if (1) {
+    // check the photcode table
+    sprintf (filename, "%s/Photcodes.dat", catdir);
+    if (!VerifyTableFile (filename)) {
+      Nbad ++;
+    }
+
+    // check the skytable
+    sprintf (filename, "%s/SkyTable.fits", catdir);
+    if (!VerifyTableFile (filename)) {
+      Nbad ++;
+    }
+
+    // check the image table
+    sprintf (filename, "%s/Images.dat", catdir);
+    if (!VerifyTableFile (filename)) {
+      Nbad ++;
+    }
+  }
+
+  // load the sky table for the existing database
+  insky = SkyTableLoadOptimal (catdir, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
+  myAssert(insky, "can't read SkyTable");
+  SkyTableSetFilenames (insky, catdir, "cpt");
+  inlist = SkyListByPatch (insky, -1, &UserPatch);
+  
+  // loop over all catalogs, save to output catalogs
+  for (i = 0; i < inlist[0].Nregions; i++) {
+    if (!inlist[0].regions[i][0].table) continue;
+
+    if (i % 1000 == 0) fprintf (stderr, ".");
+
+    sprintf (filename, "%s/%s.cpt", catdir, inlist[0].regions[i][0].name);
+    if (!VerifyTableFile (filename)) {
+      Nbad ++;
+    }
+
+    sprintf (filename, "%s/%s.cps", catdir, inlist[0].regions[i][0].name);
+    if (!VerifyTableFile (filename)) {
+      Nbad ++;
+    }
+
+    sprintf (filename, "%s/%s.cpm", catdir, inlist[0].regions[i][0].name);
+    if (!VerifyTableFile (filename)) {
+      Nbad ++;
+    }
+  }
+
+  if (Nbad > 0) {
+    fprintf (stderr, "ERROR: %d files are bad\n", Nbad);
     exit (1);
   }
 
+  fprintf (stderr, "SUCCESS: no files are bad\n");
   exit (0);
 }
 
-int dvoConvert_copy_images (char *input, char *output) {
-
-  FITS_DB inDB;
-  FITS_DB outDB;
-  int    status;
-
-  Image *images;
-  off_t Nimages;
-  off_t ID;
-
-  /*** load output/Images.dat ***/
-  sprintf (ImageCat, "%s/Images.dat", output);
-  outDB.mode   = dvo_catalog_catmode (CATMODE);
-  outDB.format = dvo_catalog_catformat (CATFORMAT);
-  status       = dvo_image_lock (&outDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
-  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", outDB.filename);
-
-  // output image table should not already exist
-  if (outDB.dbstate != LCK_EMPTY) {
-    Shutdown ("ERROR: image table %s already exists", outDB.filename);
-  }
-  dvo_image_create (&outDB, GetZeroPoint());
-
-  /*** load input/Images.dat ***/
-  sprintf (ImageCat, "%s/Images.dat", input);
-  // inDB.mode   = dvo_catalog_catmode (CATMODE);
-  // inDB.format = dvo_catalog_catformat (CATFORMAT);
-  status       = dvo_image_lock (&inDB, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
-  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", inDB.filename);
-
-  // load the image table 
-  if (inDB.dbstate == LCK_EMPTY) {
-    Shutdown ("can't find input image catalog %s", inDB.filename);
-  }
-  if (!dvo_image_load (&inDB, VERBOSE, TRUE)) {
-    Shutdown ("can't read input image catalog %s", inDB.filename);
-  }
-
-  // convert the raw image table to Image type (byteswap if needed)
-  images = gfits_table_get_Image (&inDB.ftable, &Nimages, &inDB.swapped);
-  if (!images) {
-    fprintf (stderr, "ERROR: failed to read images\n");
-    exit (2);
-  }
-
-  // update additional metadata
-  gfits_scan (&inDB.header, "IMAGEID", OFF_T_FMT, 1,  &ID);
-  gfits_modify (&outDB.header, "NIMAGES", OFF_T_FMT, 1,  Nimages);
-  gfits_modify (&outDB.header, "IMAGEID", OFF_T_FMT, 1,  ID);
-
-  // copy input rows to output table
-  gfits_add_rows (&outDB.ftable, (char *) images, Nimages, sizeof(Image));
-
-  // write out the image table to disk
-  SetProtect (TRUE);
-  dvo_image_save (&outDB, VERBOSE);
-  SetProtect (FALSE);
-  dvo_image_unlock (&outDB); // unlock output
-  dvo_image_unlock (&inDB); // unlock input1
-
+// is this file a consistent FITS file?
+int VerifyTableFile (char *filename) {
+
+  int status;
+  off_t Nbytes;
+  Header header;
+
+  struct stat fileStats;
+  FILE *file;
+
+  // does the file exist?
+  status = stat (filename, &fileStats);
+  if (status) {
+    // some error accessing the file.  there is only one acceptable error: file not found
+    switch (errno) {
+      case ENOENT:
+	if (DEBUG) fprintf (stderr, "file does not exist, skipping %s\n", filename);
+	return TRUE;
+      case ENOMEM:
+	fprintf (stderr, "Out of memory: %s\n", filename);
+	return TRUE;
+      case EACCES:
+	fprintf (stderr, "Permission error on %s\n", filename);
+	return FALSE;
+      case EFAULT:
+	fprintf (stderr, "Bad address: %s\n", filename);
+	return FALSE;
+      case ELOOP:
+	fprintf (stderr, "Too many symbolic links encountered while traversing the path: %s\n", filename);
+	return FALSE;
+      case ENAMETOOLONG:
+	fprintf (stderr, "File name too long: %s\n", filename);
+	return FALSE;
+      case ENOTDIR:
+	fprintf (stderr, "A component of the path is not a directory: %s\n", filename);
+	return FALSE;
+      case EOVERFLOW:
+	fprintf (stderr, "file too large for program version: %s\n", filename);
+	return FALSE;
+      default:
+	fprintf (stderr, "unknown error: %s\n", filename);
+	return FALSE;
+    }
+  }
+
+  // does it have any data?
+  if (fileStats.st_size == 0) {
+    fprintf (stderr, "file is empty: %s\n", filename);
+    return FALSE;
+  }
+
+  // can we open it?
+  file = fopen(filename, "r");
+  if (!file) {
+    fprintf (stderr, "unable to open valid file: %s\n", filename);
+    return FALSE;
+  }
+
+  // scan all extentions
+  Nbytes = 0;
+  if (DEBUG) fprintf (stderr, "sizes: ("OFF_T_FMT" vs "OFF_T_FMT")\n", Nbytes, fileStats.st_size);
+  while (Nbytes < fileStats.st_size) {
+
+    // Check on the PHU
+    if (!gfits_fread_header (file, &header)) {
+      fprintf (stderr, "unable to read PHU header for %s\n", filename);
+      fclose(file);
+      return (FALSE);
+    }
+
+    // move to TBL header
+    Nbytes += header.datasize + gfits_data_size (&header);
+    if (DEBUG) fprintf (stderr, "sizes: ("OFF_T_FMT" vs "OFF_T_FMT")\n", Nbytes, fileStats.st_size);
+    if (Nbytes > fileStats.st_size) {
+      fprintf (stderr, "file is short ("OFF_T_FMT" vs "OFF_T_FMT"): %s\n", Nbytes, fileStats.st_size, filename);
+      gfits_free_header(&header);
+      fclose (file);
+      return FALSE;
+    }
+    gfits_free_header(&header);
+
+    status = fseeko (file, Nbytes, SEEK_SET);
+    if (status) {
+      switch (errno) {
+	case EBADF:
+	  fprintf (stderr, "something wrong with file handle: %s\n", filename);
+	  fclose (file);
+	  return FALSE;
+	case EINVAL:
+	  fprintf (stderr, "invalid offset: %s\n", filename);
+	  fclose (file);
+	  return FALSE;
+	default:
+	  fprintf (stderr, "other error in fseeko: %s\n", filename);
+	  fclose (file);
+	  return FALSE;
+      }
+    }
+  }
+  if (DEBUG) fprintf (stderr, "file is good: %s\n", filename);
+  fclose (file);
   return TRUE;
 }
+
+  // gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
+  // gfits_scan(&cpmHeaderTBL, "NAXIS2", "%d", 1, &Nrows);
+    
+  
Index: /trunk/Ohana/src/dvomerge/src/help.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/help.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/help.c	(revision 29938)
@@ -4,4 +4,6 @@
   fprintf (stderr, "USAGE: dvomerge (input1) and (input2) to (output)\n");
   fprintf (stderr, "   OR: dvomerge (input) into (output)\n");
+  fprintf (stderr, "   OR: dvomerge (input) into (output) continue\n");
+  fprintf (stderr, "   OR: dvomerge (input) into (output) from (list)\n");
   fprintf (stderr, "   [-region Rmin Rmax Dmin Dmax]\n");
   exit (2);
@@ -17,5 +19,5 @@
 void dvosecfilt_usage(void) {
 
-  fprintf (stderr, "USAGE: dvosecfilt (input) -photcodes (photcodes.txt)\n");
+  fprintf (stderr, "USAGE: dvosecfilt (catdir) (Nsecfilt)\n");
 
   exit (2);
@@ -33,5 +35,7 @@
   fprintf (stderr, "USAGE\n");
   fprintf (stderr, "  dvomerge (input1) and (input2) to (output)\n");
-  fprintf (stderr, "  dvomerge (input) into (output)\n\n");
+  fprintf (stderr, "  dvomerge (input) into (output)\n");
+  fprintf (stderr, "  dvomerge (input) into (output) continue\n\n");
+  fprintf (stderr, "  dvomerge (input) into (output) from (list)\n\n");
   fprintf (stderr, "  merge DVO databases\n");
   fprintf (stderr, "  optional flags:\n");
@@ -39,4 +43,5 @@
   fprintf (stderr, "  -help                 	  : this list\n");
   fprintf (stderr, "  -h                    	  : this list\n\n");
+  fprintf (stderr, "  -region Rmin Rmax Dmin Dmax : region to merge\n\n");
   exit (2);
 }
@@ -75,7 +80,8 @@
 
   fprintf (stderr, "USAGE\n");
-  fprintf (stderr, "  dvosecfilt (input) (Nsecfilt)\n\n");
+  fprintf (stderr, "  dvosecfilt (catdir) (Nsecfilt)\n\n");
 
-  fprintf (stderr, "  change number of secfilt entries in photcode table (updates secfilt tables only)\n");
+  fprintf (stderr, "  change number of secfilt entries in photcode table (updates secfilt tables (cps), NSECFILT in cpt files)\n");
+  fprintf (stderr, "  NOTE: the user must change the photcode table to reflect the change (use photcode-table -export / -import)\n");
  
   fprintf (stderr, "  optional flags:\n");
@@ -86,2 +92,26 @@
 }
 
+void dvorepair_help (int argc, char **argv) {
+
+  /* check for help request */
+  if (!argv) goto show_help;
+  if (get_argument (argc, argv, "-help")) goto show_help;
+  if (get_argument (argc, argv, "-h"))    goto show_help;
+  if (argc < 2) goto show_help;
+  return;
+
+show_help:
+
+  fprintf (stderr, "USAGE\n");
+  fprintf (stderr, "  dvorepair -fix-cpt (images) (rootlist) - regenerate cpt & cps files from the cpm files\n");
+  fprintf (stderr, "  dvorepair -images-vs-measures (catdir) (Ntol) - find images with too many missing detections\n");
+  fprintf (stderr, "  dvorepair -delete-image-list (catdir) (deleteList) - delete a set of images based on image IDs (output from -images-vs-measures)\n");
+  fprintf (stderr, "  dvorepair -fix-images (catdir) (deleteList) - delete a set of images based on image IDs (output from -images-vs-measures)\n");
+  fprintf (stderr, "\n");
+
+  fprintf (stderr, "  optional flags:\n");
+  fprintf (stderr, "  -help                 	  : this list\n");
+  fprintf (stderr, "  -h                    	  : this list\n\n");
+  exit (2);
+}
+
Index: /trunk/Ohana/src/dvomerge/src/match_image.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/match_image.c	(revision 29938)
+++ /trunk/Ohana/src/dvomerge/src/match_image.c	(revision 29938)
@@ -0,0 +1,37 @@
+# include "dvomerge.h"
+
+off_t match_image (Image *image, off_t Nimage, unsigned int T, short int S) {
+
+  off_t N, Nlo, Nhi, N1, N2;
+
+  /* bracket first value of interest */
+  Nlo = 0; Nhi = Nimage;
+  while (Nhi - Nlo > 10) {
+    N = 0.5*(Nlo + Nhi);
+    if (image[N].tzero < T) {
+      Nlo = N;
+    } else {
+      Nhi = N + 1;
+    }
+  }
+  N1 = Nlo;
+
+  /* bracket last value of interest */
+  Nlo = 0; Nhi = Nimage;
+  while (Nhi - Nlo > 10) {
+    N = 0.5*(Nlo + Nhi);
+    if (image[N].tzero > T) {
+      Nhi = N;
+    } else {
+      Nlo = N - 1;
+    }
+  }
+  N2 = Nhi;
+
+  for (N = N1; N < N2; N++) {
+    if ((image[N].tzero == T) && (image[N].photcode == S)) {
+      return (N);
+    }
+  }
+  return (-1);
+}
Index: /trunk/Ohana/src/dvomerge/src/merge_catalogs_old.c
===================================================================
--- /trunk/Ohana/src/dvomerge/src/merge_catalogs_old.c	(revision 29937)
+++ /trunk/Ohana/src/dvomerge/src/merge_catalogs_old.c	(revision 29938)
@@ -28,5 +28,5 @@
   unsigned int objID, catID;
   Coords tcoords;
-
+  
   // struct timeval start, stop;
   // gettimeofday (&start, (void *) NULL);
@@ -240,4 +240,6 @@
     } else {
       for (k = 0; k < NsecfiltIn; k++) {
+	if (secfiltMap[k] < 0) continue;  // skip secfilt entries from input that are not in the output
+
         // index for this entry in output's secfilt list
         int outputIndex = n * NsecfiltOut + secfiltMap[k];
@@ -254,5 +256,5 @@
     i++;
   }
-  // MARKTIME("find matched stars: %f sec for "OFF_T_FMT","OFF_T_FMT" stars\n", dtime, Nstars, Nave);
+  // MARKTIME("find matched stars: %f sec for "OFF_T_FMT","OFF_T_FMT" stars ("OFF_T_FMT" meas)\n", dtime, Nstars, Nave, Nmeas);
 
   /** incorporate unmatched image stars, if this star is in field of this catalog **/
Index: /trunk/Ohana/src/imregister/imphot/rfits.c
===================================================================
--- /trunk/Ohana/src/imregister/imphot/rfits.c	(revision 29937)
+++ /trunk/Ohana/src/imregister/imphot/rfits.c	(revision 29938)
@@ -17,5 +17,5 @@
     return (FALSE);
   }
-  if (!gfits_fread_ftable_data (db[0].f, &db[0].ftable)) {
+  if (!gfits_fread_ftable_data (db[0].f, &db[0].ftable, FALSE)) {
     fprintf (stderr, "can't read table data");
     return (FALSE);
Index: /trunk/Ohana/src/kapa2/Makefile
===================================================================
--- /trunk/Ohana/src/kapa2/Makefile	(revision 29937)
+++ /trunk/Ohana/src/kapa2/Makefile	(revision 29938)
@@ -42,4 +42,5 @@
 $(SRC)/bDrawObjects.$(ARCH).o	  	  $(SRC)/bDrawFrame.$(ARCH).o         \
 $(SRC)/bDrawLabels.$(ARCH).o              $(SRC)/bDrawIt.$(ARCH).o            \
+$(SRC)/bDrawImage.$(ARCH).o               \
 $(SRC)/PNGit.$(ARCH).o                    $(SRC)/PPMit.$(ARCH).o	      \
 $(SRC)/PSit.$(ARCH).o                     $(SRC)/CrossHairs.$(ARCH).o         \
Index: /trunk/Ohana/src/kapa2/include/prototypes.h
===================================================================
--- /trunk/Ohana/src/kapa2/include/prototypes.h	(revision 29937)
+++ /trunk/Ohana/src/kapa2/include/prototypes.h	(revision 29938)
@@ -40,10 +40,11 @@
 void          DrawYErrors         PROTO((KapaGraphWidget *graph, Gobjects *objects));
 void	      DrawTick		  PROTO((Graphic *graphic, Axis *axis, int P, TickMarkData *tick, int naxis));
-void	      AxisTickScale	  PROTO((Axis *axis, double *major, double *minor));
+void	      AxisTickScale	  PROTO((Axis *axis, double *major, double *minor, int *nsignif));
 TickMarkData *CreateAxisTicks     PROTO((Axis *axis, int *nticks));
-int           PrintTick           PROTO((char *string, double value, double min, double max));
+int           PrintTick           PROTO((char *string, double value, double min, double max, int nsignif));
 
 /* EventLoop */
 int           PScommand           PROTO((int sock));
+int           PNGcommand          PROTO((int sock));
 int           CheckPipe           PROTO((void));
 int           Reconfig            PROTO((XEvent *event));
@@ -52,5 +53,5 @@
 
 /* CheckPipe */
-int           PNGit               PROTO((int sock));
+int           PNGit               PROTO((char *filename));
 int           PPMit               PROTO((int sock));
 int           LoadFrame           PROTO((int sock));
@@ -67,4 +68,5 @@
 int           MoveSection         PROTO((int sock));
 int           DefineSection       PROTO((int sock));
+int           DefineSectionByImage PROTO((int sock));
 int           SetFont             PROTO((int sock));
 int           EraseCurrentPlot    PROTO((void));
@@ -126,17 +128,18 @@
 
 /* kapa bDraw Functions */
-int	      bDrawFrame	  PROTO((KapaGraphWidget *graph));
-int	      bDrawObjects	  PROTO((KapaGraphWidget *graph));
-void	      bDrawLabels	  PROTO((KapaGraphWidget *graph));
-void	      bDrawTextlines	  PROTO((KapaGraphWidget *graph));
-void	      bDrawConnect	  PROTO((KapaGraphWidget *graph, Gobjects *object));
-void	      bDrawHistogram	  PROTO((KapaGraphWidget *graph, Gobjects *object));
-void	      bDrawPoints	  PROTO((KapaGraphWidget *graph, Gobjects *object));
-void	      bDrawXErrors	  PROTO((KapaGraphWidget *graph, Gobjects *object));
-void	      bDrawYErrors	  PROTO((KapaGraphWidget *graph, Gobjects *object));
-void	      bDrawTick		  PROTO((Axis *axis, int P, TickMarkData *tick, int naxis));
-void	      bDrawClipLine	  PROTO((double x0, double y0, double x1, double y1, double X0, double Y1, double X1, double Y0));
-bDrawBuffer  *bDrawIt		  PROTO((void));
-void          bDrawGraph          PROTO((KapaGraphWidget *graph));
+int	      bDrawFrame	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph));
+int	      bDrawObjects	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph));
+void	      bDrawLabels	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph));
+void	      bDrawTextlines	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph));
+void	      bDrawConnect	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object));
+void	      bDrawHistogram	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object));
+void	      bDrawPoints	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object));
+void	      bDrawXErrors	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object));
+void	      bDrawYErrors	  PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object));
+void	      bDrawTick		  PROTO((bDrawBuffer *buffer, Axis *axis, int P, TickMarkData *tick, int naxis));
+void	      bDrawClipLine	  PROTO((bDrawBuffer *buffer, double x0, double y0, double x1, double y1, double X0, double Y1, double X1, double Y0));
+void          bDrawGraph          PROTO((bDrawBuffer *buffer, KapaGraphWidget *graph));
+int           bDrawImage          PROTO((bDrawBuffer *buffer, KapaImageWidget *image, Graphic *graphic));
+bDrawBuffer  *bDrawIt		  PROTO((png_color *palette, int Npalette, int Nbyte));
 
 /* misc support */
@@ -212,4 +215,6 @@
 int	      Overlay3		  PROTO((Graphic *graphic, KapaImageWidget *image));
 int	      PSfunction	  PROTO((Graphic *graphic, KapaImageWidget *image));
+int	      PNGfunction	  PROTO((Graphic *graphic, KapaImageWidget *image));
+int	      JPEGfunction	  PROTO((Graphic *graphic, KapaImageWidget *image));
 
 /* misc functions */
@@ -219,5 +224,6 @@
 int  	      SaveOverlay  (int sock);
 int  	      CSaveOverlay (int sock);
-int  	      JPEGit24     (int sock);
+int  	      JPEGit24     (char *filename);
+int  	      JPEGcommand  (int sock);
 
 int  	      UpdatePointer (Graphic *graphic, XMotionEvent *event);
@@ -241,5 +247,5 @@
 int  	      GetActiveSocket (void);
 void 	      InvertButton (Graphic *graphic, Button *button);
-void 	      bDrawOverlay (KapaImageWidget *image, int N);
+void 	      bDrawOverlay (bDrawBuffer *buffer, KapaImageWidget *image, int N);
 
 /* color cube tools */
@@ -261,2 +267,5 @@
 int SetColorScale3D_CC (Graphic *graphic, KapaImageWidget *image);
 int SetColorCubeHistogram (void);
+int GetGraphBoundary (Section *section, double *x0, double *y0, double *x1, double *y1, int *dXm, int *dXp, int *dYm, int *dYp);
+int ResizeByImage (int sock);
+
Index: /trunk/Ohana/src/kapa2/include/structures.h
===================================================================
--- /trunk/Ohana/src/kapa2/include/structures.h	(revision 29937)
+++ /trunk/Ohana/src/kapa2/include/structures.h	(revision 29938)
@@ -148,4 +148,5 @@
   int IsMajor;
   int IsLabel;
+  int nsignif;
 } TickMarkData;
 
Index: /trunk/Ohana/src/kapa2/src/ButtonFunctions.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/ButtonFunctions.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/ButtonFunctions.c	(revision 29938)
@@ -7,4 +7,20 @@
 
   status = PSit ("kapa.ps", "default", TRUE, KAPA_PS_NEWPLOT);
+  return (status);
+}
+
+int PNGfunction (Graphic *graphic, KapaImageWidget *image) {
+
+  int status;
+
+  status = PNGit ("kapa.png");
+  return (status);
+}
+
+int JPEGfunction (Graphic *graphic, KapaImageWidget *image) {
+
+  int status;
+
+  status = JPEGit24 ("kapa.jpg");
   return (status);
 }
Index: /trunk/Ohana/src/kapa2/src/CheckPipe.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/CheckPipe.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/CheckPipe.c	(revision 29938)
@@ -89,9 +89,15 @@
   
   if (!strcmp (word, "PNGF")) {
-    status = PNGit (sock);
-    KiiSendCommand (sock, 4, "DONE");
-    FINISHED (status);
-  }
-  
+    status = PNGcommand (sock);
+    KiiSendCommand (sock, 4, "DONE");
+    FINISHED (status);
+  }
+  
+  if (!strcmp (word, "JPEG")) {
+    status = JPEGcommand (sock);
+    KiiSendCommand (sock, 4, "DONE");
+    FINISHED (status);
+  }
+
   if (!strcmp (word, "PPMF")) {
     status = PPMit (sock);
@@ -131,4 +137,10 @@
   }
 
+  if (!strcmp (word, "ISIZ")) {
+    status = ResizeByImage (sock);
+    KiiSendCommand (sock, 4, "DONE");
+    FINISHED (TRUE);
+  }
+  
   if (!strcmp (word, "MOVE")) {
     status = Relocate (sock);
@@ -211,4 +223,10 @@
   if (!strcmp (word, "DSEC")) {
     status = DefineSection (sock);
+    KiiSendCommand (sock, 4, "DONE");
+    FINISHED (TRUE);
+  }
+  
+  if (!strcmp (word, "ISEC")) {
+    status = DefineSectionByImage (sock);
     KiiSendCommand (sock, 4, "DONE");
     FINISHED (TRUE);
@@ -300,10 +318,4 @@
   if (!strcmp (word, "CSVE")) {
     status = CSaveOverlay (sock);
-    KiiSendCommand (sock, 4, "DONE");
-    FINISHED (status);
-  }
-
-  if (!strcmp (word, "JPEG")) {
-    status = JPEGit24 (sock);
     KiiSendCommand (sock, 4, "DONE");
     FINISHED (status);
Index: /trunk/Ohana/src/kapa2/src/DefineSection.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/DefineSection.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/DefineSection.c	(revision 29938)
@@ -40,2 +40,58 @@
   return (TRUE);
 }
+
+// define the section, but do not create a graph or image 
+int DefineSectionByImage (int sock) {
+  
+  int N, bg;
+  char name[128];
+  double x, y, dx, dy;
+  int dXm, dXp, dYm, dYp;
+  double x0, y0, x1, y1, expand;
+  Section *section;
+  Graphic *graphic;
+  KapaImageWidget *image;
+
+  KiiScanMessage (sock, "%s %lf %lf %d", name, &x, &y, &bg);
+
+  N = GetSectionByName (name);
+  section = GetSectionByNumber (N);
+  image = section->image;
+  if (!image) {
+    fprintf (stderr, "no image to define section\n");
+    return (TRUE);
+  }
+  SetActiveSectionByNumber (N);
+
+  graphic = GetGraphic ();
+
+  GetGraphBoundary (section, &x0, &y0, &x1, &y1, &dXm, &dXp, &dYm, &dYp);
+
+  expand = 1.0;
+  if (image[0].picture.expand > 0) {
+    expand = image[0].picture.expand;
+  }
+  if (image[0].picture.expand < 0) {
+    expand = 1.0 / fabs((double)image[0].picture.expand);
+  }
+
+  // pixel dimensions of the imaging region + boundary
+  dx = image[0].image[0].matrix.Naxis[0]*expand + x1;
+  dy = image[0].image[0].matrix.Naxis[1]*expand + y1;
+
+  section[0].x = x;
+  section[0].y = y;
+  section[0].bg = bg;
+  section[0].dx = dx / graphic[0].dx;
+  section[0].dy = dy / graphic[0].dy;
+
+  // if (section[0].x != x)   MoveSection = TRUE;
+  // if (section[0].y != y)   MoveSection = TRUE;
+  // if (section[0].dx != dx) MoveSection = TRUE;
+  // if (section[0].dy != dy) MoveSection = TRUE;
+
+  SetSectionSizes (section);
+  Refresh ();
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/kapa2/src/DrawFrame.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/DrawFrame.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/DrawFrame.c	(revision 29938)
@@ -5,6 +5,6 @@
 int DrawFrame (KapaGraphWidget *graph) {
   
-  int i, j, Nticks, P;
-  double fx, fy, dfx, dfy, lweight;
+  int i, j, Nticks, P, doffset;
+  double fx, fy, dfx, dfy, dx, dy, lweight;
   Graphic *graphic;
   TickMarkData *ticks;
@@ -15,12 +15,21 @@
   /* each axis is drawn independently, but ticks and labels are placed according to perpendicular distance. */
   for (i = 0; i < 4; i++) {
-    fx  = graph[0].axis[i].fx;
-    fy  = graph[0].axis[i].fy;
-    dfx = graph[0].axis[i].dfx;
-    dfy = graph[0].axis[i].dfy;
-    P = hypot (graph[0].axis[(i+1)%2].dfx, graph[0].axis[(i+1)%2].dfy);
-
     lweight = MAX (0, MIN (10, graph[0].axis[i].lweight));
     color = MAX (0, MIN (15, graph[0].axis[i].color));
+
+    /* temporarily assume rectilinear axes */
+    doffset = ((int)(lweight) % 2) ? 0.5*(lweight - 1) : 0.5*lweight;
+    if (i == 0) { dx = doffset; dy = 0.0; }
+    if (i == 2) { dx = doffset; dy = 0.0; }
+    if (i == 1) { dx = 0.0; dy = doffset; }
+    if (i == 3) { dx = 0.0; dy = doffset; }
+
+    fx  = graph[0].axis[i].fx - dx;
+    fy  = graph[0].axis[i].fy - dy;
+    dfx = graph[0].axis[i].dfx + 2*dx;
+    dfy = graph[0].axis[i].dfy + 2*dy;
+
+    P = hypot (graph[0].axis[(i+1)%2].dfx, graph[0].axis[(i+1)%2].dfy);
+    P *= (1 + 0.25*lweight);
 
     XSetLineAttributes (graphic->display, graphic->gc, lweight, LineSolid, CapNotLast, JoinMiter);
@@ -43,5 +52,5 @@
 }
 
-int PrintTick (char *string, double value, double min, double max) {
+int PrintTick (char *string, double value, double min, double max, int nsignif) {
 
   int Nexp = 0;
@@ -54,16 +63,23 @@
   }
 
-  double lvalue = log10(fabs(value));
-
-  if (isfinite(lvalue)) {
-    Nexp = fabs(lvalue);
-  } else {
-    Nexp = 0;
-  }
-  
+//   double lvalue = log10(fabs(value));
+//   if (isfinite(lvalue)) {
+//     Nexp = fabs(lvalue);
+//   } else {
+//     Nexp = 0;
+//   }
+  
+  Nexp = abs(nsignif);
+
   if (Nexp > 3) {
     Nchar = sprintf (string, "%.1e", value);
   } else {
-    Nchar = sprintf (string, "%.1f", value);
+    if (nsignif < 0) {
+      char format[16];
+      snprintf (format, 16, "%%.%df", -1 * nsignif);
+      Nchar = sprintf (string, format, value);
+    } else {
+      Nchar = sprintf (string, "%.1f", value);
+    }
   }
   return Nchar;
@@ -129,5 +145,5 @@
     yt = fy + (value-min)*dfy/(max - min) + dy;
 
-    PrintTick (string, value, min, max);
+    PrintTick (string, value, min, max, tick->nsignif);
     DrawRotText (xt, yt, string, pos, 0.0);
   }
@@ -136,5 +152,5 @@
 # define MIN_RANGE 1e-10
 
-void AxisTickScale (Axis *axis, double *major, double *minor) {
+void AxisTickScale (Axis *axis, double *major, double *minor, int *nsignif) {
 
   double range, lrange, factor, mantis, fmantis, power;
@@ -152,4 +168,6 @@
   }
   
+  // how many significant digits are needed?
+
   power = MAX(pow(10.0, factor), MIN_RANGE);
   fmantis = pow(10.0, mantis);
@@ -160,7 +178,9 @@
     *major = 0.5 * power;
     *minor = 0.1 * power;
+    *nsignif = factor - 1;
     if (axis[0].areticks == 1) {
       *major = 0.25 * power;
       *minor = 0.05 * power;
+      *nsignif = factor - 2;
     }	  
   }
@@ -168,7 +188,9 @@
     *major = 1.0 * power;
     *minor = 0.2 * power;
+    *nsignif = factor;
     if (axis[0].areticks == 1) {
       *major = 0.5 * power;
       *minor = 0.1 * power;
+      *nsignif = factor - 1;
     }	  
   }
@@ -176,7 +198,9 @@
     *major = 1.0 * power;
     *minor = 0.5 * power;
+    *nsignif = factor;
     if (axis[0].areticks == 1) {
       *major = 1.0 * power;
       *minor = 0.2 * power;
+      *nsignif = factor;
     }	  
   }
@@ -184,7 +208,9 @@
     *major = 2.0 * power;
     *minor = 0.5 * power;
+    *nsignif = factor;
     if (axis[0].areticks == 1) {
       *major = 1.0 * power;
       *minor = 0.5 * power;
+      *nsignif = factor;
     }	  
   }
@@ -192,7 +218,9 @@
     *major = 2.5 * power;
     *minor = 0.5 * power;
+      *nsignif = factor - 1;
     if (axis[0].areticks == 1) {
       *major = 2.0 * power;
       *minor = 0.5 * power;
+      *nsignif = factor;
     }	  
   }
@@ -203,5 +231,5 @@
   TickMarkData *ticks;
   double range, major, minor, first, value, dPixels, overshoot;
-  int i, NTICKS, nPixels, done, ifirst;
+  int i, NTICKS, nPixels, done, ifirst, nsignif;
 
   *nticks = 0;
@@ -223,5 +251,5 @@
   dPixels = nPixels / range; // axis pixel-scale
 
-  AxisTickScale (axis, &major, &minor);
+  AxisTickScale (axis, &major, &minor, &nsignif);
 
   // be a little generous 
@@ -256,4 +284,5 @@
     ticks[i].IsLabel = (ticks[i].IsMajor && axis->islabel);
     ticks[i].value = value;
+    ticks[i].nsignif = nsignif;
     if (range > 0) 
       value += minor;
Index: /trunk/Ohana/src/kapa2/src/Image.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/Image.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/Image.c	(revision 29938)
@@ -88,5 +88,8 @@
 
   InitButtonSize (&image[0].PS_button, PS_width, PS_height, PS_bits);
-  InitButtonFunc (&image[0].PS_button, PSfunction);
+  // InitButtonFunc (&image[0].PS_button, PSfunction);
+  image->PS_button.function_1 = PSfunction;
+  image->PS_button.function_2 = PNGfunction;
+  image->PS_button.function_3 = JPEGfunction;
 
   InitButtonSize (&image[0].grey_button, grey_width, grey_height, grey_bits);
Index: /trunk/Ohana/src/kapa2/src/JPEGit24.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/JPEGit24.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/JPEGit24.c	(revision 29938)
@@ -8,6 +8,16 @@
 # define WHITE_B 255
 
+int JPEGcommand (int sock) {
+
+  int status;
+  char filename[1024];
+
+  KiiScanMessage (sock, "%s", filename);
+  status = JPEGit24 (filename);
+  return (status);
+}
+
 // XXX this currently writes out the jpeg for just the active image
-int JPEGit24 (int sock) {
+int JPEGit24 (char *filename) {
 
   struct jpeg_compress_struct cinfo;
@@ -31,9 +41,5 @@
   unsigned short *in_pix, *in_pix_ref;
   unsigned char *pixel1, *pixel2, *pixel3;
-  char filename[1024];
   FILE *f;
-
-  /* expect a line telling the number of bytes and a filename */
-  KiiScanMessage (sock, "%s", filename);
 
   graphic = GetGraphic();
@@ -196,8 +202,7 @@
     palette = KapaPNGPalette (&Npalette);
 
-    buffer = bDrawBufferCreate (dx, dy);
-    bDrawSetBuffer (buffer);
+    buffer = bDrawBufferCreate (dx, dy, 1, palette, Npalette);
     for (i = 0; i < NOVERLAYS; i++) {
-      if (image[i].overlay[i].active) bDrawOverlay (image, i);
+      if (image[0].overlay[i].active) bDrawOverlay (buffer, image, i);
     }
 
Index: /trunk/Ohana/src/kapa2/src/PNGit.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/PNGit.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/PNGit.c	(revision 29938)
@@ -7,22 +7,26 @@
 */
 
-bDrawBuffer *bDrawBufferCreate8bitRGB (int Nx, int Ny);
-int bDrawImage (bDrawBuffer *buffer, Graphic *graphic);
+int PNGcommand (int sock) {
 
-int PNGit (int sock) {
+  int status;
+  char filename[1024];
+
+  KiiScanMessage (sock, "%s", filename);
+  status = PNGit (filename);
+  return (status);
+}
+
+int PNGit (char *filename) {
 
   FILE *f;
   png_structp png_ptr;
   png_infop info_ptr;
-  int status, Npalette;
-  char filename[1024];
+  int i, status, Npalette, Nsection;
   bDrawBuffer *buffer = NULL;
   Graphic *graphic = NULL;
   png_color *palette = NULL;
+  Section *section;
 
   graphic = GetGraphic();
-
-  /* expect a line telling the number of bytes and a filename */
-  KiiScanMessage (sock, "%s", filename);
 
   f = fopen (filename, "w");
@@ -62,14 +66,22 @@
   png_init_io (png_ptr, f);
 
+  palette = KapaPNGPalette (&Npalette);
+
+  // do we have an image in any of the sections?
+  Nsection = GetNumberOfSections ();
+  int haveImage = FALSE;
+  for (i = 0; !haveImage && (i < Nsection); i++) {
+    section = GetSectionByNumber (i);
+    haveImage = (section->image != NULL);
+  }
+
   /* see docs for write-row-callback to provide progress info */
-# define PALETTE 1
-  if (PALETTE) {
-    png_set_IHDR (png_ptr, info_ptr, graphic->dx, graphic->dy, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); 
-    palette = KapaPNGPalette (&Npalette);
-    png_set_PLTE (png_ptr, info_ptr, palette, Npalette); 
-  } else {
+  if (haveImage) {
     // png_set_IHDR (png_ptr, info_ptr, graphic->dx, graphic->dy, 16, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
     png_set_IHDR (png_ptr, info_ptr, graphic->dx, graphic->dy, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
     png_set_sRGB (png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE);
+  } else {
+    png_set_IHDR (png_ptr, info_ptr, graphic->dx, graphic->dy, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); 
+    png_set_PLTE (png_ptr, info_ptr, palette, Npalette); 
   }
 
@@ -81,9 +93,9 @@
   png_write_info (png_ptr, info_ptr);
 
-  if (PALETTE) {
-    buffer = bDrawIt ();
+  if (haveImage) {
+    buffer = bDrawIt (palette, Npalette, 3);
+    // bDrawImage (buffer, graphic);
   } else {
-    buffer = bDrawBufferCreate8bitRGB(graphic->dx, graphic->dy);
-    bDrawImage (buffer, graphic);
+    buffer = bDrawIt (palette, Npalette, 1);
   }
 
@@ -93,215 +105,6 @@
   
   bDrawBufferFree (buffer);
-  free (palette);
   fclose (f);
   return (TRUE);
 
 }
-
-/* For color images, I need to define the relationship between the drawing colors and the RGB values? */
-
-
-bDrawBuffer *bDrawBufferCreate8bitRGB (int Nx, int Ny) {
-
-  int i, j;
-  bDrawBuffer *buffer;
-
-  ALLOCATE (buffer, bDrawBuffer, 1);
-  buffer[0].Nx = Nx;
-  buffer[0].Ny = Ny;
-
-  ALLOCATE (buffer[0].pixels, bDrawColor *, Ny);
-  for (i = 0; i < Ny; i++) {
-    ALLOCATE (buffer[0].pixels[i], bDrawColor, 3*Nx);
-    for (j = 0; j < 3*Nx; j+=3) {
-      buffer[0].pixels[i][j+0] = 0xff;
-      buffer[0].pixels[i][j+1] = 0xff;
-      buffer[0].pixels[i][j+2] = 0xff;
-    }
-  }
-  return (buffer);
-}
-
-# define WHITE_R 255
-# define WHITE_G 255
-# define WHITE_B 255
-# define MY_SWAP_INT(A,B) { int tmp; tmp = A; A = B; B = tmp; }
-
-int bDrawImage (bDrawBuffer *buffer, Graphic *graphic) {
-  
-  Section *section;
-  KapaImageWidget *image;
-
-  int ii, i, j;
-  int i_start, i_end, j_start, j_end;
-  int I_start, J_start;
-  int dropback;  /* this is a bit of a kludge... */
-  int dx, dy, DX, DY, inDX, inDY;
-  int expand_in, expand_out;
-  double expand, Ix, Iy;
-  unsigned char *out_pix;
-  unsigned short *in_pix, *in_pix_ref;
-  unsigned char *pixel1, *pixel2, *pixel3;
-
-  bDrawColor *line_buffer;
-
-  section = GetActiveSection();
-  image   = section->image;
-  if (image == NULL) return (TRUE);
-
-  ALLOCATE (pixel1, unsigned char, graphic[0].Npixels);
-  ALLOCATE (pixel2, unsigned char, graphic[0].Npixels);
-  ALLOCATE (pixel3, unsigned char, graphic[0].Npixels);
-
-  /** cmap[i].pixel must be defined even if X is not used **/
-  for (i = 0; i < graphic[0].Npixels; i++) { /* set up pixel array */
-    pixel1[i] = graphic[0].cmap[i].red >> 8;
-    pixel2[i] = graphic[0].cmap[i].green >> 8;
-    pixel3[i] = graphic[0].cmap[i].blue >> 8;
-  }
-
-  assert ((image[0].picture.expand >= 1) || (image[0].picture.expand <= -2));
-  expand = expand_in = expand_out = 1.0;
-  if (image[0].picture.expand > 0) {
-    expand = 1 / (1.0*image[0].picture.expand);
-    expand_out = image[0].picture.expand;
-    expand_in  = 1;
-  }
-  if (image[0].picture.expand < 0) {
-    expand = fabs((double)image[0].picture.expand);
-    expand_out = 1;
-    expand_in  = -image[0].picture.expand;
-  }
-
-  dx = image[0].picture.dx;
-  dy = image[0].picture.dy;
-  DX = image[0].image[0].matrix.Naxis[0];
-  DY = image[0].image[0].matrix.Naxis[1];
-
-  // i_start, j_start are the closest lit screen pixel to 0,0
-  // I_start, J_start are the image pixel corresponding to i_start, j_start
-  Picture_Lower (&i_start, &j_start, &I_start, &J_start, &image[0].image[0].matrix, &image[0].picture);
-
-  // i_end, j_end are the closest lit screen pixel to dx, dy
-  // I_end, J_end are the image pixel corresponding to i_end, j_end
-  Picture_Upper (&i_end, &j_end, i_start, j_start, &image[0].image[0].matrix, &image[0].picture);
-
-  assert (i_start <= i_end);
-  assert (j_start <= j_end);
-
-  Ix = image[0].picture.flipx ? I_start - 1 : I_start;
-  Iy = image[0].picture.flipy ? J_start - 1 : J_start;
-
-  inDX = image[0].picture.flipx ? -1 : +1;
-  inDY = image[0].picture.flipy ? -1 : +1;
-
-  dropback = expand_out - (i_end - i_start) % expand_out;
-  if ((i_end - i_start) % expand_out == 0) dropback = 0;
-
-  ALLOCATE (line_buffer, bDrawColor, 3*dx);
-
-  in_pix_ref  = &image[0].pixmap[DX*(int)MAX(Iy,0) + (int)MAX(Ix,0)];
-
-  /********** below we do the mapping from buffer pixels (in) to picture pixels (out) **********/
-
-  /**** fill in bottom area ****/
-  out_pix = line_buffer;
-  for (i = 0; i < dx; i++, out_pix+=3) {
-    out_pix[0] = WHITE_R;
-    out_pix[1] = WHITE_G;
-    out_pix[2] = WHITE_B;
-  }
-  for (j = 0; j < j_start; j++) {
-    memcpy (buffer[0].pixels[j], line_buffer, 3*dx);
-  }
-  
-  /*** fill in the image data region ***/
-  for (j = j_start; j < j_end; j+= expand_out, in_pix_ref += inDY*expand_in*DX) {
-    
-    /* create one output image line */
-    in_pix = in_pix_ref;
-    out_pix = line_buffer;
-
-    /**** fill in area to the left of the picture ****/
-    for (i = 0; i < i_start; i++, out_pix+=3) {
-      out_pix[0] = WHITE_R;
-      out_pix[1] = WHITE_G;
-      out_pix[2] = WHITE_B;
-    }
-    
-    /*** fill in the picture region ***/
-    for (i = i_start; i < i_end; i+=expand_out, in_pix += inDX*expand_in) {
-      for (ii = 0; ii < expand_out; ii++, out_pix+=3) {
-	out_pix[0] = pixel1[*in_pix];
-	out_pix[1] = pixel2[*in_pix];
-	out_pix[2] = pixel3[*in_pix];
-      }
-    }
-    
-    /**** fill in area to the right of the picture ****/
-    for (i = i_end; i < dx; i++, out_pix+=3) {
-      out_pix[0] = WHITE_R;
-      out_pix[1] = WHITE_G;
-      out_pix[2] = WHITE_B;
-    }
-
-    /* write out the image line expand_out times */
-    for (i = 0; i < expand_out; i++) {
-      memcpy (buffer[0].pixels[j + i], line_buffer, 3*dx);
-    }
-  }
-
-  /**** fill in top area ****/
-  out_pix = line_buffer;
-  for (i = 0; i < dx; i++, out_pix+=3) { 
-    out_pix[0] = WHITE_R;
-    out_pix[1] = WHITE_G;
-    out_pix[2] = WHITE_B;
-  }
-  for (j = j_end; j < dy; j++) {
-    memcpy (buffer[0].pixels[j], line_buffer, 3*dx);
-  }
-
-  free (pixel1);
-  free (pixel2);
-  free (pixel3);
-  free (line_buffer);
-
-  return (TRUE);
-}
-
-# if (0)
-
-  /* I need to write the overlay objects on the jpeg image.
-     if i can write / overwrite data in jpeg buffer, then do it here,
-     otherwise i need to create a temporary image buffer, then write the 
-     scanlines to that buffer */
-
-  {
-    int Npalette;
-    png_color *palette;
-    bDrawColor white, color;
-    bDrawBuffer *buffer;
-
-    palette = KapaPNGPalette (&Npalette);
-
-    buffer = bDrawBufferCreate (dx, dy);
-    bDrawSetBuffer (buffer);
-    for (i = 0; i < NOVERLAYS; i++) {
-      if (image[i].overlay[i].active) bDrawOverlay (image, i);
-    }
-
-    white = KapaColorByName ("white");
-    for (j = 0; j < dy; j++) {
-      for (i = 0; i < dx; i++) {
-	color = buffer[0].pixels[j][i];
-	if (color == white) continue;
-	image_buffer[j*3*dx + 3*i + 0] = palette[color].red;
-	image_buffer[j*3*dx + 3*i + 1] = palette[color].green;
-	image_buffer[j*3*dx + 3*i + 2] = palette[color].blue;
-      }
-    }
-    bDrawBufferFree (buffer);
-  }
-
-# endif
Index: /trunk/Ohana/src/kapa2/src/PPMit.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/PPMit.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/PPMit.c	(revision 29938)
@@ -30,5 +30,5 @@
   fprintf (f, "255\n");
 
-  buffer = bDrawIt ();
+  buffer = bDrawIt (palette, Npalette, 1);
 
   ALLOCATE (line, char, 3*dx);
@@ -47,4 +47,3 @@
   bDrawBufferFree (buffer);
   return (TRUE);
-
 }
Index: /trunk/Ohana/src/kapa2/src/PSFrame.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/PSFrame.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/PSFrame.c	(revision 29938)
@@ -4,6 +4,6 @@
 int PSFrame (KapaGraphWidget *graph, FILE *f) {
   
-  int i, j, Nticks, P;
-  double fx, fy, dfx, dfy, lweight;
+  int i, j, Nticks, P, doffset;
+  double fx, fy, dfx, dfy, dx, dy, lweight;
   Graphic *graphic;
   TickMarkData *ticks;
@@ -15,12 +15,21 @@
   fprintf (f, "1 setlinewidth\n");
   for (i = 0; i < 4; i++) {
-    fx  = graph[0].axis[i].fx;
-    fy  = graphic->dy - graph[0].axis[i].fy;
-    dfx = graph[0].axis[i].dfx;
-    dfy = -graph[0].axis[i].dfy;
-    P = hypot (graph[0].axis[(i+1)%2].dfx, graph[0].axis[(i+1)%2].dfy);
-
     lweight = MAX (0, MIN (10, graph[0].axis[i].lweight));
     color = MAX (0, MIN (15, graph[0].axis[i].color));
+
+    /* temporarily assume rectilinear axes */
+    doffset = ((int)(lweight) % 2) ? 0.5*(lweight - 1) : 0.5*lweight;
+    if (i == 0) { dx = doffset; dy = 0.0; }
+    if (i == 2) { dx = doffset; dy = 0.0; }
+    if (i == 1) { dx = 0.0; dy = doffset; }
+    if (i == 3) { dx = 0.0; dy = doffset; }
+
+    fx  = graph[0].axis[i].fx - dx;
+    fy  = graphic->dy - graph[0].axis[i].fy - dy;
+    dfx = graph[0].axis[i].dfx + 2*dx;
+    dfy = -graph[0].axis[i].dfy + 2*dy;
+
+    P = hypot (graph[0].axis[(i+1)%2].dfx, graph[0].axis[(i+1)%2].dfy);
+    P *= (1 + 0.25*lweight);
 
     fprintf (f, "%.1f setlinewidth\n", lweight);
@@ -95,5 +104,5 @@
     yt = fy + (value-min)*dfy/(max - min) + dy;
 
-    PrintTick (string, value, min, max);
+    PrintTick (string, value, min, max, tick->nsignif);
     PSRotText (f, xt, yt, string, pos, 0.0);
   }
Index: /trunk/Ohana/src/kapa2/src/PSimage.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/PSimage.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/PSimage.c	(revision 29938)
@@ -13,7 +13,11 @@
   graphic = GetGraphic();
 
-  fprintf (f, " newpath 0 0 moveto %d 0 lineto %d %d lineto 0 %d lineto closepath clip\n\n", 
-	   image[0].picture.dx, image[0].picture.dx, image[0].picture.dy, image[0].picture.dy);
+  fprintf (f, " newpath %d %d moveto %d %d lineto %d %d lineto %d %d lineto closepath\n\n", 
+	   (int) image[0].picture.x,                       graphic->dy - (int) image[0].picture.y, 
+	   (int) image[0].picture.x + image[0].picture.dx, graphic->dy - (int) image[0].picture.y, 
+	   (int) image[0].picture.x + image[0].picture.dx, graphic->dy - (int) image[0].picture.y - image[0].picture.dy, 
+	   (int) image[0].picture.x,                       graphic->dy - (int) image[0].picture.y - image[0].picture.dy); 
   fprintf (f, "gsave %% encloses image\n");
+  fprintf (f, "%d %d translate\n", (int) image[0].picture.x, graphic->dy - (int) image[0].picture.y - image[0].picture.dy);
   fprintf (f, "%d %d 8\n", image[0].picture.dx, image[0].picture.dy);
   fprintf (f, "[1 0 0 1 0 0]\n");
Index: /trunk/Ohana/src/kapa2/src/PSit.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/PSit.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/PSit.c	(revision 29938)
@@ -87,4 +87,7 @@
   for (i = 0; i < Nsection; i++) {
     section = GetSectionByNumber (i);
+    if (section->image) {
+      PSimage (section->image, f);
+    }
     if (section->graph) {
       PSFrame (section->graph, f); 
@@ -92,7 +95,4 @@
       PSLabels (section->graph, f);
       PSTextlines (section->graph, f);
-    }
-    if (section->image) {
-      PSimage (section->image, f);
     }
   }
Index: /trunk/Ohana/src/kapa2/src/Resize.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/Resize.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/Resize.c	(revision 29938)
@@ -39,2 +39,66 @@
   return (TRUE);
 }
+
+// resise the window so the image in the currently active window fills its section
+int ResizeByImage (int sock) {
+ 
+  int i, Nsection;
+  unsigned int NX, NY;
+  double dx, dy;
+  int dXm, dXp, dYm, dYp;
+  double x0, y0, x1, y1, expand;
+  Section *section;
+  Graphic *graphic;
+  KapaImageWidget *image;
+
+  graphic = GetGraphic();
+
+  section = GetActiveSection();
+
+  image = section->image;
+  if (!image) {
+    fprintf (stderr, "no image to define size\n");
+    return (TRUE);
+  }
+
+  GetGraphBoundary (section, &x0, &y0, &x1, &y1, &dXm, &dXp, &dYm, &dYp);
+
+  expand = 1.0;
+  if (image[0].picture.expand > 0) {
+    expand = image[0].picture.expand;
+  }
+  if (image[0].picture.expand < 0) {
+    expand = 1.0 / fabs((double)image[0].picture.expand);
+  }
+
+  // pixel dimensions of the imaging region + boundary
+  dx = image[0].image[0].matrix.Naxis[0]*expand + x1;
+  dy = image[0].image[0].matrix.Naxis[1]*expand + y1;
+
+  NX = dx / section[0].dx;
+  NY = dy / section[0].dy;
+
+  NX = MAX(NX, MIN_WIDTH); 
+  NY = MAX(NY, MIN_HEIGHT); 
+
+  // if the new size is the same as the old size, do nothing.
+  if ((graphic->dx == NX) && (graphic->dy == NY)) return (TRUE);
+
+  // set the new window size
+  graphic->dx = NX; 
+  graphic->dy = NY; 
+
+  if (USE_XWINDOW) XResizeWindow (graphic->display, graphic->window, NX, NY);
+
+  // reset the sizes for all sections
+  Nsection = GetNumberOfSections ();
+  for (i = 0; i < Nsection; i++) {
+      section = GetSectionByNumber (i);
+      SetSectionSizes (section);
+  }
+
+  if (USE_XWINDOW) XClearWindow (graphic->display, graphic->window);
+  Refresh ();
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/kapa2/src/SetGraphSize.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/SetGraphSize.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/SetGraphSize.c	(revision 29938)
@@ -5,13 +5,76 @@
 
 void SetGraphSize (Section *section) {
+
+  int dXm, dXp, dYm, dYp;
+  double x0, y0, x1, y1;
+  KapaGraphWidget *graph;
+  Graphic *graphic;
+
+  GetGraphBoundary(section, &x0, &y0, &x1, &y1, &dXm, &dXp, &dYm, &dYp);
+
+  if (section == NULL) return;
+  graph = section->graph;
+  if (graph == NULL) return;
+
+  graphic = GetGraphic ();
+
+  double X0 = graphic[0].dx * section[0].x  + x0;
+  double Y0 = graphic[0].dy * section[0].y  + y0;
+  double dX = graphic[0].dx * section[0].dx - x1;
+  double dY = graphic[0].dy * section[0].dy - y1;
+
+  /* define locations of coordinate axes */
+  graph[0].axis[0].fx  = X0;
+  graph[0].axis[0].fy  = graphic->dy - Y0;
+  graph[0].axis[0].dfx = dX;
+  graph[0].axis[0].dfy = 0;
+
+  graph[0].axis[1].fx  = X0;
+  graph[0].axis[1].fy  = graphic->dy - Y0;
+  graph[0].axis[1].dfx = 0;
+  graph[0].axis[1].dfy = -dY;
+
+  graph[0].axis[2].fx  = X0;
+  graph[0].axis[2].fy  = graphic->dy - Y0 - dY;
+  graph[0].axis[2].dfx = dX;
+  graph[0].axis[2].dfy = 0;
+
+  graph[0].axis[3].fx  = X0 + dX;
+  graph[0].axis[3].fy  = graphic->dy - Y0;
+  graph[0].axis[3].dfx = 0;
+  graph[0].axis[3].dfy = -dY;
+
+  /* define locations of axis labels */
+  graph[0].label[LABELLL].x = graph[0].axis[0].fx;
+  graph[0].label[LABELLL].y = graph[0].axis[0].fy + dXm;
+  graph[0].label[LABELX0].x = graph[0].axis[0].fx + 0.5*graph[0].axis[0].dfx;
+  graph[0].label[LABELX0].y = graph[0].axis[0].fy + dXm;
+  graph[0].label[LABELLR].x = graph[0].axis[0].fx + graph[0].axis[0].dfx;
+  graph[0].label[LABELLR].y = graph[0].axis[0].fy + dXm;
+
+  graph[0].label[LABELUL].x = graph[0].axis[2].fx;
+  graph[0].label[LABELUL].y = graph[0].axis[2].fy - dXp;
+  graph[0].label[LABELX1].x = graph[0].axis[2].fx + 0.5*graph[0].axis[2].dfx;
+  graph[0].label[LABELX1].y = graph[0].axis[2].fy - dXp;
+  graph[0].label[LABELUR].x = graph[0].axis[2].fx + graph[0].axis[2].dfx;
+  graph[0].label[LABELUR].y = graph[0].axis[2].fy - dXp;
+
+  graph[0].label[LABELY0].y = graph[0].axis[1].fy + 0.5*graph[0].axis[1].dfy;
+  graph[0].label[LABELY0].x = graph[0].axis[1].fx - dYm;
+
+  graph[0].label[LABELY1].y = graph[0].axis[3].fy + 0.5*graph[0].axis[3].dfy;
+  graph[0].label[LABELY1].x = graph[0].axis[3].fx + dYp;
+
+  return;
+}
+
+int GetGraphBoundary (Section *section, double *x0, double *y0, double *x1, double *y1, int *dXm, int *dXp, int *dYm, int *dYp) {
 
   int i, Nticks;
   int fontsize, Nc = 0;
   int textpad, textdY, WdY;
-  int dXm, dXp, dYm, dYp;
   double padXm, padXp, padYm, padYp;
   double minPADx, maxPADx, minPADy;
   double minPAD, maxPAD;
-  double X0, Y0, dX, dY;
   char string[64], *fontname;
   KapaGraphWidget *graph;
@@ -19,7 +82,17 @@
   TickMarkData *ticks;
 
-  if (section == NULL) return;
+  *x0 = 0.0;
+  *y0 = 0.0;
+  *x1 = 0.0;
+  *y1 = 0.0;
+
+  *dXm = 0;
+  *dXp = 0;
+  *dYm = 0;
+  *dYp = 0;
+
+  if (section == NULL) return FALSE;
   graph = section->graph;
-  if (graph == NULL) return;
+  if (graph == NULL) return FALSE;
 
   graphic = GetGraphic ();
@@ -40,7 +113,7 @@
   // these depend on (a) existence of tick labels and (b) auto-offset or not
   if (isnan(graph[0].axis[0].labelPad)) {
-    dXm = (graph[0].axis[0].islabel) ? maxPAD  : minPAD;
-  } else {
-    dXm = graph[0].axis[0].labelPad * fontsize;
+    *dXm = (graph[0].axis[0].islabel) ? maxPAD  : minPAD;
+  } else {
+    *dXm = graph[0].axis[0].labelPad * fontsize;
   }
   if (isnan(graph[0].axis[0].pad)) {
@@ -53,7 +126,7 @@
   // these depend on (a) existence of tick labels and (b) auto-offset or not
   if (isnan(graph[0].axis[2].labelPad)) {
-    dXp = (graph[0].axis[2].islabel) ? maxPAD : minPAD;
-  } else {
-    dXp = graph[0].axis[2].labelPad * fontsize;
+    *dXp = (graph[0].axis[2].islabel) ? maxPAD : minPAD;
+  } else {
+    *dXp = graph[0].axis[2].labelPad * fontsize;
   }
   if (isnan(graph[0].axis[2].pad)) {
@@ -73,5 +146,5 @@
       if (!ticks[i].IsMajor) continue;
 
-      int Nchar = PrintTick (string, ticks[i].value, graph[0].axis[1].min, graph[0].axis[1].max);
+      int Nchar = PrintTick (string, ticks[i].value, graph[0].axis[1].min, graph[0].axis[1].max, ticks[i].nsignif);
 
       Nc = MAX (Nc, Nchar);
@@ -80,7 +153,7 @@
   }
   if (isnan(graph[0].axis[1].labelPad)) {
-    dYm = (graph[0].axis[1].islabel) ? (0.7*(Nc + 1.5)*fontsize) : minPAD;
-  } else {
-    dYm = graph[0].axis[1].labelPad * fontsize;
+    *dYm = (graph[0].axis[1].islabel) ? (0.7*(Nc + 1.5)*fontsize) : minPAD;
+  } else {
+    *dYm = graph[0].axis[1].labelPad * fontsize;
   }
   if (isnan(graph[0].axis[1].pad)) {
@@ -98,5 +171,5 @@
     for (i = 0; i < Nticks; i++) {
       if (!ticks[i].IsMajor) continue;
-      int Nchar = PrintTick (string, ticks[i].value, graph[0].axis[3].min, graph[0].axis[3].max);
+      int Nchar = PrintTick (string, ticks[i].value, graph[0].axis[3].min, graph[0].axis[3].max, ticks[i].nsignif);
       Nc = MAX (Nc, Nchar);
     }
@@ -104,7 +177,7 @@
   }
   if (isnan(graph[0].axis[3].labelPad)) {
-    dYp = (graph[0].axis[3].islabel) ? (0.7*(Nc + 1.5)*fontsize) : minPAD;
-  } else {
-    dYp = graph[0].axis[3].labelPad * fontsize;
+    *dYp = (graph[0].axis[3].islabel) ? (0.7*(Nc + 1.5)*fontsize) : minPAD;
+  } else {
+    *dYp = graph[0].axis[3].labelPad * fontsize;
   }
   if (isnan(graph[0].axis[3].pad)) {
@@ -115,8 +188,8 @@
 
   /* basic size of the graph in Xwindow coordinates, but measured from lower-left corner */
-  X0 = graphic[0].dx * section[0].x + padYm;
-  Y0 = graphic[0].dy * section[0].y + padXm;
-  dX = graphic[0].dx * section[0].dx - padYm - padYp;
-  dY = graphic[0].dy * section[0].dy - padXm - padXp;
+  *x0 = padYm;
+  *y0 = padXm;
+  *x1 = padYm + padYp;
+  *y1 = padXm + padXp;
 
   // if we are tied to an image, make mods as needed
@@ -128,70 +201,24 @@
     switch (section->image->location) {
       case 0:
-	// no changes?
 	break;
       case 1:
-        Y0 = graphic[0].dy * section[0].y  + padXm         + 2*PAD1 + WdY + 2;
-        dY = graphic[0].dy * section[0].dy - padXm - padXp - 4*PAD1 - 1 - WdY - COLORPAD;
+        *y0 = padXm + 2*PAD1 + WdY + 2;
+        *y1 = padXm + padXp + 4*PAD1 + 1 + WdY + COLORPAD;
         break;
       case 3:
-        dY = graphic[0].dy * section[0].dy - padXm - padXp - 4*PAD1 - 1 - WdY - COLORPAD;
+        *y1 = padXm + padXp + 4*PAD1 + 1 + WdY + COLORPAD;
         break;
       case 2:
-        X0 = graphic[0].dx * section[0].x  + padYm         + 2*PAD1 + ZOOM_X;
-        dX = graphic[0].dx * section[0].dx - padYm - padYp - 3*PAD1 - ZOOM_X;
-	dY = graphic[0].dy * section[0].dy - padXm - padXp - 2*PAD1 - COLORPAD;
+        *x0 = padYm + 2*PAD1 + ZOOM_X;
+        *x1 = padYm + padYp + 3*PAD1 + ZOOM_X;
+	*y1 = padXm + padXp + 2*PAD1 + COLORPAD;
         break;
       case 4:
-        dX = graphic[0].dx * section[0].dx - padYm - padYp - 3*PAD1 - ZOOM_X;
-	dY = graphic[0].dy * section[0].dy - padXm - padXp - 2*PAD1 - COLORPAD;
+        *x1 = padYm + padYp + 3*PAD1 + ZOOM_X;
+	*y1 = padXm + padXp + 2*PAD1 + COLORPAD;
         break;
     }
   }
 
-  /* define locations of coordinate axes */
-  graph[0].axis[0].fx  = X0;
-  graph[0].axis[0].fy  = graphic->dy - Y0;
-  graph[0].axis[0].dfx = dX;
-  graph[0].axis[0].dfy = 0;
-
-  graph[0].axis[1].fx  = X0;
-  graph[0].axis[1].fy  = graphic->dy - Y0;
-  graph[0].axis[1].dfx = 0;
-  graph[0].axis[1].dfy = -dY;
-
-  graph[0].axis[2].fx  = X0;
-  graph[0].axis[2].fy  = graphic->dy - Y0 - dY;
-  graph[0].axis[2].dfx = dX;
-  graph[0].axis[2].dfy = 0;
-
-  graph[0].axis[3].fx  = X0 + dX;
-  graph[0].axis[3].fy  = graphic->dy - Y0;
-  graph[0].axis[3].dfx = 0;
-  graph[0].axis[3].dfy = -dY;
-
-  /* define locations of axis labels */
-  graph[0].label[LABELLL].x = graph[0].axis[0].fx;
-  graph[0].label[LABELLL].y = graph[0].axis[0].fy + dXm;
-  graph[0].label[LABELX0].x = graph[0].axis[0].fx + 0.5*graph[0].axis[0].dfx;
-  graph[0].label[LABELX0].y = graph[0].axis[0].fy + dXm;
-  graph[0].label[LABELLR].x = graph[0].axis[0].fx + graph[0].axis[0].dfx;
-  graph[0].label[LABELLR].y = graph[0].axis[0].fy + dXm;
-
-  graph[0].label[LABELUL].x = graph[0].axis[2].fx;
-  graph[0].label[LABELUL].y = graph[0].axis[2].fy - dXp;
-  graph[0].label[LABELX1].x = graph[0].axis[2].fx + 0.5*graph[0].axis[2].dfx;
-  graph[0].label[LABELX1].y = graph[0].axis[2].fy - dXp;
-  graph[0].label[LABELUR].x = graph[0].axis[2].fx + graph[0].axis[2].dfx;
-  graph[0].label[LABELUR].y = graph[0].axis[2].fy - dXp;
-
-  graph[0].label[LABELY0].y = graph[0].axis[1].fy + 0.5*graph[0].axis[1].dfy;
-  graph[0].label[LABELY0].x = graph[0].axis[1].fx - dYm;
-
-  graph[0].label[LABELY1].y = graph[0].axis[3].fy + 0.5*graph[0].axis[3].dfy;
-  graph[0].label[LABELY1].x = graph[0].axis[3].fx + dYp;
-
-  // fprintf (stderr, "section %s, %f,%f : %f,%f\n", section->name, X0, Y0, dX, dY);
-
-  /* these are wrong and have to be adjusted to sit in the corners */
-
+  return (TRUE);
 }
Index: /trunk/Ohana/src/kapa2/src/bDrawFrame.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/bDrawFrame.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/bDrawFrame.c	(revision 29938)
@@ -2,8 +2,8 @@
 // bDrawLine is a function, not a macro like DrawLine
 
-int bDrawFrame (KapaGraphWidget *graph) {
+int bDrawFrame (bDrawBuffer *buffer, KapaGraphWidget *graph) {
   
-  int i, j, Nticks, P;
-  double fx, fy, dfx, dfy, lweight;
+  int i, j, Nticks, P, doffset;
+  double fx, fy, dfx, dfy, lweight, dx, dy;
   // Graphic graphic; is not needed
   TickMarkData *ticks;
@@ -14,19 +14,27 @@
   /* each axis is drawn independently */
   for (i = 0; i < 4; i++) {
-    fx  = graph[0].axis[i].fx;
-    fy  = graph[0].axis[i].fy;
-    dfx = graph[0].axis[i].dfx;
-    dfy = graph[0].axis[i].dfy;
-    P = hypot (graph[0].axis[(i+1)%2].dfx, graph[0].axis[(i+1)%2].dfy);
-
     lweight = MAX (0, MIN (10, graph[0].axis[i].lweight));
     color = MAX (0, MIN (15, graph[0].axis[i].color));
 
-    bDrawSetStyle (color, lweight, 0);
-    // function about sets solor and weight
+    /* temporarily assume rectilinear axes */
+    doffset = ((int)(lweight) % 2) ? 0.5*(lweight - 1) : 0.5*lweight;
+    if (i == 0) { dx = doffset; dy = 0.0; }
+    if (i == 2) { dx = doffset; dy = 0.0; }
+    if (i == 1) { dx = 0.0; dy = doffset; }
+    if (i == 3) { dx = 0.0; dy = doffset; }
+
+    fx  = graph[0].axis[i].fx - dx;
+    fy  = graph[0].axis[i].fy - dy;
+    dfx = graph[0].axis[i].dfx + 2*dx;
+    dfy = graph[0].axis[i].dfy + 2*dy;
+    P = hypot (graph[0].axis[(i+1)%2].dfx, graph[0].axis[(i+1)%2].dfy);
+    P *= (1 + 0.25*lweight);
+
+    bDrawSetStyle (buffer, color, lweight, 0);
+    // function about sets color and weight
     // bDrawRotTextInit does not exist
 
     if (graph[0].axis[i].isaxis) { 
-      bDrawLine (fx, fy, fx+dfx, fy+dfy); 
+      bDrawLine (buffer, fx, fy, fx+dfx, fy+dfy); 
     }
     
@@ -34,5 +42,10 @@
       ticks = CreateAxisTicks (&graph[0].axis[i], &Nticks);
       for (j = 0; j < Nticks; j++) {
-	bDrawTick (&graph[0].axis[i], P, &ticks[j], i);
+	bDrawTick (buffer, &graph[0].axis[i], P, &ticks[j], i);
+
+	// XXX DrawTick sets the style to (color, 0, 0) for the font
+	// should the font weight stay the same?
+	// XXX probably not needed now:
+	// bDrawSetStyle (buffer, color, lweight, 0);
       }
       FREE (ticks);
@@ -42,5 +55,5 @@
 }
 
-void bDrawTick (Axis *axis, int P, TickMarkData *tick, int naxis) {
+void bDrawTick (bDrawBuffer *buffer, Axis *axis, int P, TickMarkData *tick, int naxis) {
   
   double x, y, dx, dy;
@@ -75,5 +88,5 @@
   dy = dir*size*dfx*n;
   
-  bDrawLine (x, y, x+dx, y+dy);
+  bDrawLine (buffer, x, y, x+dx, y+dy);
 
   if (tick->IsLabel) {
@@ -94,6 +107,6 @@
     yt = fy + (value-min)*dfy/(max - min) + dy;
 
-    PrintTick (string, value, min, max);
-    bDrawRotText (xt, yt, string, pos, 0.0);
+    PrintTick (string, value, min, max, tick->nsignif);
+    bDrawRotText (buffer, xt, yt, string, pos, 0.0);
   }
 }
Index: /trunk/Ohana/src/kapa2/src/bDrawImage.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/bDrawImage.c	(revision 29938)
+++ /trunk/Ohana/src/kapa2/src/bDrawImage.c	(revision 29938)
@@ -0,0 +1,190 @@
+# include "Ximage.h"
+
+# define WHITE_R 255
+# define WHITE_G 255
+# define WHITE_B 255
+# define MY_SWAP_INT(A,B) { int tmp; tmp = A; A = B; B = tmp; }
+
+int bDrawImage (bDrawBuffer *buffer, KapaImageWidget *image, Graphic *graphic) {
+
+  int ii, i, j;
+  int i_start, i_end, j_start, j_end;
+  int I_start, J_start;
+  int dropback;  /* this is a bit of a kludge... */
+  int dx, dy, DX, DY, inDX, inDY, Xs, Ys;
+  int expand_in, expand_out;
+  double expand, Ix, Iy;
+  unsigned char *out_pix;
+  unsigned short *in_pix, *in_pix_ref;
+  unsigned char *pixel1, *pixel2, *pixel3;
+
+  bDrawColor *line_buffer;
+
+  if (image == NULL) return (TRUE);
+
+  ALLOCATE (pixel1, unsigned char, graphic[0].Npixels);
+  ALLOCATE (pixel2, unsigned char, graphic[0].Npixels);
+  ALLOCATE (pixel3, unsigned char, graphic[0].Npixels);
+
+  /** cmap[i].pixel must be defined even if X is not used **/
+  for (i = 0; i < graphic[0].Npixels; i++) { /* set up pixel array */
+    pixel1[i] = graphic[0].cmap[i].red >> 8;
+    pixel2[i] = graphic[0].cmap[i].green >> 8;
+    pixel3[i] = graphic[0].cmap[i].blue >> 8;
+  }
+
+  assert ((image[0].picture.expand >= 1) || (image[0].picture.expand <= -2));
+  expand = expand_in = expand_out = 1.0;
+  if (image[0].picture.expand > 0) {
+    expand = 1 / (1.0*image[0].picture.expand);
+    expand_out = image[0].picture.expand;
+    expand_in  = 1;
+  }
+  if (image[0].picture.expand < 0) {
+    expand = fabs((double)image[0].picture.expand);
+    expand_out = 1;
+    expand_in  = -image[0].picture.expand;
+  }
+
+  Xs = image[0].picture.x;
+  Ys = image[0].picture.y;
+  dx = image[0].picture.dx;
+  dy = image[0].picture.dy;
+  DX = image[0].image[0].matrix.Naxis[0];
+  DY = image[0].image[0].matrix.Naxis[1];
+
+  if (buffer[0].Nx < Xs + dx) {
+    fprintf (stderr, "invalid condition\n");
+    abort();
+  }
+  if (buffer[0].Ny < Ys + dy) {
+    fprintf (stderr, "invalid condition\n");
+    abort();
+  }
+
+  // i_start, j_start are the closest lit screen pixel to 0,0
+  // I_start, J_start are the image pixel corresponding to i_start, j_start
+  Picture_Lower (&i_start, &j_start, &I_start, &J_start, &image[0].image[0].matrix, &image[0].picture);
+
+  // i_end, j_end are the closest lit screen pixel to dx, dy
+  // I_end, J_end are the image pixel corresponding to i_end, j_end
+  Picture_Upper (&i_end, &j_end, i_start, j_start, &image[0].image[0].matrix, &image[0].picture);
+
+  assert (i_start <= i_end);
+  assert (j_start <= j_end);
+
+  Ix = image[0].picture.flipx ? I_start - 1 : I_start;
+  Iy = image[0].picture.flipy ? J_start - 1 : J_start;
+
+  inDX = image[0].picture.flipx ? -1 : +1;
+  inDY = image[0].picture.flipy ? -1 : +1;
+
+  dropback = expand_out - (i_end - i_start) % expand_out;
+  if ((i_end - i_start) % expand_out == 0) dropback = 0;
+
+  ALLOCATE (line_buffer, bDrawColor, 3*dx);
+
+  in_pix_ref  = &image[0].pixmap[DX*(int)MAX(Iy,0) + (int)MAX(Ix,0)];
+
+  /********** below we do the mapping from buffer pixels (in) to picture pixels (out) **********/
+
+  /**** fill in bottom area ****/
+  out_pix = line_buffer;
+  for (i = 0; i < dx; i++, out_pix+=3) {
+    out_pix[0] = WHITE_R;
+    out_pix[1] = WHITE_G;
+    out_pix[2] = WHITE_B;
+  }
+  for (j = 0; j < j_start; j++) {
+    memcpy (&buffer[0].pixels[j + Ys][3*Xs], line_buffer, 3*dx);
+  }
+  
+  /*** fill in the image data region ***/
+  for (j = j_start; j < j_end; j+= expand_out, in_pix_ref += inDY*expand_in*DX) {
+    
+    /* create one output image line */
+    in_pix = in_pix_ref;
+    out_pix = line_buffer;
+
+    /**** fill in area to the left of the picture ****/
+    for (i = 0; i < i_start; i++, out_pix+=3) {
+      out_pix[0] = WHITE_R;
+      out_pix[1] = WHITE_G;
+      out_pix[2] = WHITE_B;
+    }
+    
+    /*** fill in the picture region ***/
+    for (i = i_start; i < i_end; i+=expand_out, in_pix += inDX*expand_in) {
+      for (ii = 0; ii < expand_out; ii++, out_pix+=3) {
+	out_pix[0] = pixel1[*in_pix];
+	out_pix[1] = pixel2[*in_pix];
+	out_pix[2] = pixel3[*in_pix];
+      }
+    }
+    
+    /**** fill in area to the right of the picture ****/
+    for (i = i_end; i < dx; i++, out_pix+=3) {
+      out_pix[0] = WHITE_R;
+      out_pix[1] = WHITE_G;
+      out_pix[2] = WHITE_B;
+    }
+
+    /* write out the image line expand_out times */
+    for (i = 0; i < expand_out; i++) {
+      memcpy (&buffer[0].pixels[j + i + Ys][3*Xs], line_buffer, 3*dx);
+    }
+  }
+
+  /**** fill in top area ****/
+  out_pix = line_buffer;
+  for (i = 0; i < dx; i++, out_pix+=3) { 
+    out_pix[0] = WHITE_R;
+    out_pix[1] = WHITE_G;
+    out_pix[2] = WHITE_B;
+  }
+  for (j = j_end; j < dy; j++) {
+    memcpy (&buffer[0].pixels[j + Ys][3*Xs], line_buffer, 3*dx);
+  }
+
+  free (pixel1);
+  free (pixel2);
+  free (pixel3);
+  free (line_buffer);
+
+  return (TRUE);
+}
+
+# if (0)
+
+  /* I need to write the overlay objects on the jpeg image.
+     if i can write / overwrite data in jpeg buffer, then do it here,
+     otherwise i need to create a temporary image buffer, then write the 
+     scanlines to that buffer */
+
+  {
+    int Npalette;
+    png_color *palette;
+    bDrawColor white, color;
+    bDrawBuffer *buffer;
+
+    palette = KapaPNGPalette (&Npalette);
+
+    buffer = bDrawBufferCreate (dx, dy, 1);
+    for (i = 0; i < NOVERLAYS; i++) {
+      if (image[i].overlay[i].active) bDrawOverlay (image, i);
+    }
+
+    white = KapaColorByName ("white");
+    for (j = 0; j < dy; j++) {
+      for (i = 0; i < dx; i++) {
+	color = buffer[0].pixels[j][i];
+	if (color == white) continue;
+	image_buffer[j*3*dx + 3*i + 0] = palette[color].red;
+	image_buffer[j*3*dx + 3*i + 1] = palette[color].green;
+	image_buffer[j*3*dx + 3*i + 2] = palette[color].blue;
+      }
+    }
+    bDrawBufferFree (buffer);
+  }
+
+# endif
Index: /trunk/Ohana/src/kapa2/src/bDrawIt.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/bDrawIt.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/bDrawIt.c	(revision 29938)
@@ -1,5 +1,5 @@
 # include "Ximage.h"
 
-bDrawBuffer *bDrawIt () {
+bDrawBuffer *bDrawIt (png_color *palette, int Npalette, int Nbyte) {
 
   int i, Nsection;
@@ -13,7 +13,6 @@
   black = KapaColorByName ("black");
 
-  buffer = bDrawBufferCreate (graphic->dx, graphic->dy);
-  bDrawSetBuffer (buffer);
-  bDrawSetStyle (black, 0, 0);
+  buffer = bDrawBufferCreate (graphic->dx, graphic->dy, Nbyte, palette, Npalette);
+  bDrawSetStyle (buffer, black, 0, 0);
   
   // reset the sizes for all sections
@@ -21,6 +20,6 @@
   for (i = 0; i < Nsection; i++) {
       section = GetSectionByNumber (i);
-      bDrawGraph (section->graph);
-      // bDrawImage (section->image);
+      bDrawImage (buffer, section->image, graphic);
+      bDrawGraph (buffer, section->graph);
   }
 
@@ -28,9 +27,9 @@
 }
 
-void bDrawGraph (KapaGraphWidget *graph) {
+void bDrawGraph (bDrawBuffer *buffer, KapaGraphWidget *graph) {
   if (graph == NULL) return;
-  bDrawFrame (graph); 
-  bDrawObjects (graph);
-  bDrawLabels (graph);
-  bDrawTextlines (graph);
+  bDrawFrame (buffer, graph); 
+  bDrawObjects (buffer, graph);
+  bDrawLabels (buffer, graph);
+  bDrawTextlines (buffer, graph);
 }
Index: /trunk/Ohana/src/kapa2/src/bDrawLabels.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/bDrawLabels.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/bDrawLabels.c	(revision 29938)
@@ -1,5 +1,5 @@
 # include "Ximage.h"
   
-void bDrawLabels (KapaGraphWidget *graph) {
+void bDrawLabels (bDrawBuffer *buffer, KapaGraphWidget *graph) {
   
   int i, pos, x, y, size;
@@ -25,5 +25,5 @@
       y = graph[0].label[i].y;
       SetRotFont (graph[0].label[i].font, graph[0].label[i].size); 
-      bDrawRotText (x, y, graph[0].label[i].text, pos, angle);
+      bDrawRotText (buffer, x, y, graph[0].label[i].text, pos, angle);
     }
   }
@@ -31,5 +31,5 @@
 }
 
-void bDrawTextlines (KapaGraphWidget *graph) {
+void bDrawTextlines (bDrawBuffer *buffer, KapaGraphWidget *graph) {
 
   int i, x, y, size;
@@ -44,5 +44,5 @@
       y = graph[0].textline[i].y;
       SetRotFont (graph[0].textline[i].font, graph[0].textline[i].size);
-      bDrawRotText (x, y, graph[0].textline[i].text, 5, angle);
+      bDrawRotText (buffer, x, y, graph[0].textline[i].text, 5, angle);
     }
   }
Index: /trunk/Ohana/src/kapa2/src/bDrawObjects.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/bDrawObjects.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/bDrawObjects.c	(revision 29938)
@@ -1,10 +1,10 @@
 # include "Ximage.h"
 
-# define DrawLine(X1,Y1,X2,Y2) (bDrawLine ((X1), (Y1), (X2), (Y2)))
-# define DrawCircle(X1,Y1,R) (bDrawCircle ((X1), (Y1), (R)))
-# define DrawRectangle(X,Y,dX,dY) (bDrawRectOpen ((X-0.5*dX), (Y-0.5*dY), (X+0.5*dX), (Y+0.5*dY)))
-# define FillRectangle(X,Y,dX,dY) (bDrawRectFill ((X-0.5*dX), (Y-0.5*dY), (X+0.5*dX), (Y+0.5*dY)))
-# define FillTriangle(X1,Y1,X2,Y2,X3,Y3) (bDrawTriFill ((X1), (Y1), (X2), (Y2), (X3), (Y3)))
-# define OpenTriangle(X1,Y1,X2,Y2,X3,Y3) (bDrawTriOpen ((X1), (Y1), (X2), (Y2), (X3), (Y3)))
+# define DrawLine(BUF,X1,Y1,X2,Y2) (bDrawLine (BUF, (X1), (Y1), (X2), (Y2)))
+# define DrawCircle(BUF,X1,Y1,R) (bDrawCircle (BUF, (X1), (Y1), (R)))
+# define DrawRectangle(BUF,X,Y,dX,dY) (bDrawRectOpen (BUF, (X-0.5*dX), (Y-0.5*dY), (X+0.5*dX), (Y+0.5*dY)))
+# define FillRectangle(BUF,X,Y,dX,dY) (bDrawRectFill (BUF, (X-0.5*dX), (Y-0.5*dY), (X+0.5*dX), (Y+0.5*dY)))
+# define FillTriangle(BUF,X1,Y1,X2,Y2,X3,Y3) (bDrawTriFill (BUF, (X1), (Y1), (X2), (Y2), (X3), (Y3)))
+# define OpenTriangle(BUF,X1,Y1,X2,Y2,X3,Y3) (bDrawTriOpen (BUF, (X1), (Y1), (X2), (Y2), (X3), (Y3)))
 
 # define CONNECT 0
@@ -14,5 +14,5 @@
 static Graphic *graphic;
 
-int bDrawObjects (KapaGraphWidget *graph) {
+int bDrawObjects (bDrawBuffer *buffer, KapaGraphWidget *graph) {
   
   int i;
@@ -30,30 +30,30 @@
     type = graph[0].objects[i].ltype;    
     color = graph[0].objects[i].color;
-    bDrawSetStyle (color, weight, type);
+    bDrawSetStyle (buffer, color, weight, type);
 
     switch (graph[0].objects[i].style) {
       case CONNECT: 
-	bDrawConnect (graph, &graph[0].objects[i]);
+	bDrawConnect (buffer, graph, &graph[0].objects[i]);
 	break;
       case HISTOGRAM:
-	bDrawHistogram (graph, &graph[0].objects[i]);
+	bDrawHistogram (buffer, graph, &graph[0].objects[i]);
 	break;
       case POINTS:
-	bDrawPoints (graph, &graph[0].objects[i]);
+	bDrawPoints (buffer, graph, &graph[0].objects[i]);
 	break;
     }
 
     if (graph[0].objects[i].etype & 0x01) {
-      bDrawYErrors (graph, &graph[0].objects[i]);
+      bDrawYErrors (buffer, graph, &graph[0].objects[i]);
     }
     if (graph[0].objects[i].etype & 0x02) {
-      bDrawXErrors (graph, &graph[0].objects[i]);
-    }
-  }
-  bDrawSetStyle (black, 0, 0);
+      bDrawXErrors (buffer, graph, &graph[0].objects[i]);
+    }
+  }
+  bDrawSetStyle (buffer, black, 0, 0);
   return (TRUE);
 }
 
-void bDrawConnect (KapaGraphWidget *graph, Gobjects *object) {
+void bDrawConnect (bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object) {
   
   int i;
@@ -91,10 +91,10 @@
     sx1 = x[i]*mxi + y[i]*mxj + bx;
     sy1 = x[i]*myi + y[i]*myj + by;
-    bDrawClipLine (sx0, sy0, sx1, sy1, X0, Y0, X1, Y1);
+    bDrawClipLine (buffer, sx0, sy0, sx1, sy1, X0, Y0, X1, Y1);
     sx0 = sx1; sy0 = sy1;
   }
 }
 
-void bDrawClipLine (double x0, double y0, double x1, double y1, double X0, double Y1, double X1, double Y0) {
+void bDrawClipLine (bDrawBuffer *buffer, double x0, double y0, double x1, double y1, double X0, double Y1, double X1, double Y0) {
 
   /* skip line segement if both points are beyond box */
@@ -145,9 +145,9 @@
     y1 = Y1;
   }
-  DrawLine (x0, y0, x1, y1);
+  DrawLine (buffer, x0, y0, x1, y1);
 }
 
 /*******/
-void bDrawHistogram (KapaGraphWidget *graph, Gobjects *object) {
+void bDrawHistogram (bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object) {
 
   int i;
@@ -192,7 +192,7 @@
     sy1 = MAX (MIN (sy1, Y0), Y1);
     sxa = 0.5*(sx0 + sx1);
-    DrawLine (sx0, sy0, sxa, sy0);
-    DrawLine (sxa, sy0, sxa, sy1);
-    DrawLine (sxa, sy1, sx1, sy1);
+    DrawLine (buffer, sx0, sy0, sxa, sy0);
+    DrawLine (buffer, sxa, sy0, sxa, sy1);
+    DrawLine (buffer, sxa, sy1, sx1, sy1);
     sx0 = sx1; sy0 = sy1;
   }
@@ -200,5 +200,5 @@
 
 /*******/
-void bDrawPoints (KapaGraphWidget *graph, Gobjects *object) {
+void bDrawPoints (bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object) {
  
   int i;
@@ -233,5 +233,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  FillRectangle (sx, sy, 2*d*z[i], 2*d*z[i]);
+	  FillRectangle (buffer, sx, sy, 2*d*z[i], 2*d*z[i]);
 	}
       }
@@ -245,5 +245,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawRectangle (sx, sy, 2*d*z[i], 2*d*z[i]);
+	  DrawRectangle (buffer, sx, sy, 2*d*z[i], 2*d*z[i]);
 	}
       }
@@ -257,6 +257,6 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx - d*z[i], sy, sx + d*z[i], sy);
-	  DrawLine (sx, sy - d*z[i], sx, sy + d*z[i]);
+	  DrawLine (buffer, sx - d*z[i], sy, sx + d*z[i], sy);
+	  DrawLine (buffer, sx, sy - d*z[i], sx, sy + d*z[i]);
 	}
       }
@@ -270,6 +270,6 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx + d*z[i], sy - d*z[i], sx - d*z[i], sy + d*z[i]);
-	  DrawLine (sx - d*z[i], sy - d*z[i], sx + d*z[i], sy + d*z[i]);
+	  DrawLine (buffer, sx + d*z[i], sy - d*z[i], sx - d*z[i], sy + d*z[i]);
+	  DrawLine (buffer, sx - d*z[i], sy - d*z[i], sx + d*z[i], sy + d*z[i]);
 	}
       }
@@ -283,5 +283,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  FillTriangle (sx - d*z[i], sy - 0.58*d*z[i], sx + d*z[i], sy - 0.58*d*z[i], sx, sy + 1.15*d*z[i]);
+	  FillTriangle (buffer, sx - d*z[i], sy - 0.58*d*z[i], sx + d*z[i], sy - 0.58*d*z[i], sx, sy + 1.15*d*z[i]);
 	}
       }
@@ -295,5 +295,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  OpenTriangle (sx - d*z[i], sy + 0.58*d*z[i], sx, sy - 1.15*d*z[i], sx + d*z[i], sy + 0.58*d*z[i]);
+	  OpenTriangle (buffer, sx - d*z[i], sy + 0.58*d*z[i], sx, sy - 1.15*d*z[i], sx + d*z[i], sy + 0.58*d*z[i]);
 	}
       }
@@ -307,7 +307,7 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx, sy, sx - d*z[i], sy + 0.58*d*z[i]);
-	  DrawLine (sx, sy, sx + d*z[i], sy + 0.58*d*z[i]);
-	  DrawLine (sx, sy, sx,          sy - 1.15*d*z[i]);
+	  DrawLine (buffer, sx, sy, sx - d*z[i], sy + 0.58*d*z[i]);
+	  DrawLine (buffer, sx, sy, sx + d*z[i], sy + 0.58*d*z[i]);
+	  DrawLine (buffer, sx, sy, sx,          sy - 1.15*d*z[i]);
 	}
       }
@@ -321,5 +321,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawCircle (sx, sy, d*z[i]);
+	  DrawCircle (buffer, sx, sy, d*z[i]);
 	}
       }
@@ -333,9 +333,9 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx + 0.00*d*z[i], sy - 1.00*d*z[i], sx + 0.95*d*z[i], sy - 0.31*d*z[i]);
-	  DrawLine (sx + 0.95*d*z[i], sy - 0.31*d*z[i], sx + 0.58*d*z[i], sy + 0.81*d*z[i]);
-	  DrawLine (sx + 0.58*d*z[i], sy + 0.81*d*z[i], sx - 0.58*d*z[i], sy + 0.81*d*z[i]);
-	  DrawLine (sx - 0.58*d*z[i], sy + 0.81*d*z[i], sx - 0.95*d*z[i], sy - 0.31*d*z[i]);
-	  DrawLine (sx - 0.95*d*z[i], sy - 0.31*d*z[i], sx + 0.00*d*z[i], sy - 1.00*d*z[i]);
+	  DrawLine (buffer, sx + 0.00*d*z[i], sy - 1.00*d*z[i], sx + 0.95*d*z[i], sy - 0.31*d*z[i]);
+	  DrawLine (buffer, sx + 0.95*d*z[i], sy - 0.31*d*z[i], sx + 0.58*d*z[i], sy + 0.81*d*z[i]);
+	  DrawLine (buffer, sx + 0.58*d*z[i], sy + 0.81*d*z[i], sx - 0.58*d*z[i], sy + 0.81*d*z[i]);
+	  DrawLine (buffer, sx - 0.58*d*z[i], sy + 0.81*d*z[i], sx - 0.95*d*z[i], sy - 0.31*d*z[i]);
+	  DrawLine (buffer, sx - 0.95*d*z[i], sy - 0.31*d*z[i], sx + 0.00*d*z[i], sy - 1.00*d*z[i]);
 	}
       }
@@ -349,11 +349,11 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx -      d*z[i], sy,               sx - 0.50*d*z[i], sy + 0.87*d*z[i]);
-	  DrawLine (sx - 0.50*d*z[i], sy + 0.87*d*z[i], sx + 0.50*d*z[i], sy + 0.87*d*z[i]);
-	  DrawLine (sx + 0.50*d*z[i], sy + 0.87*d*z[i], sx +      d*z[i], sy);
-
-	  DrawLine (sx +      d*z[i], sy,               sx + 0.50*d*z[i], sy - 0.87*d*z[i]);
-	  DrawLine (sx + 0.50*d*z[i], sy - 0.87*d*z[i], sx - 0.50*d*z[i], sy - 0.87*d*z[i]);
-	  DrawLine (sx - 0.50*d*z[i], sy - 0.87*d*z[i], sx -      d*z[i], sy);
+	  DrawLine (buffer, sx -      d*z[i], sy,               sx - 0.50*d*z[i], sy + 0.87*d*z[i]);
+	  DrawLine (buffer, sx - 0.50*d*z[i], sy + 0.87*d*z[i], sx + 0.50*d*z[i], sy + 0.87*d*z[i]);
+	  DrawLine (buffer, sx + 0.50*d*z[i], sy + 0.87*d*z[i], sx +      d*z[i], sy);
+
+	  DrawLine (buffer, sx +      d*z[i], sy,               sx + 0.50*d*z[i], sy - 0.87*d*z[i]);
+	  DrawLine (buffer, sx + 0.50*d*z[i], sy - 0.87*d*z[i], sx - 0.50*d*z[i], sy - 0.87*d*z[i]);
+	  DrawLine (buffer, sx - 0.50*d*z[i], sy - 0.87*d*z[i], sx -      d*z[i], sy);
 	}
       }
@@ -366,5 +366,5 @@
 	sx2 = x[i+1]*mxi + y[i+1]*mxj + bx;
 	sy2 = x[i+1]*myi + y[i+1]*myj + by;
-	DrawLine (sx1, sy1, sx2, sy2);
+	DrawLine (buffer, sx1, sy1, sx2, sy2);
       }
     }
@@ -380,5 +380,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  FillRectangle (sx, sy, 2*d, 2*d);
+	  FillRectangle (buffer, sx, sy, 2*d, 2*d);
 	}
       }
@@ -392,5 +392,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawRectangle (sx, sy, 2*d, 2*d);
+	  DrawRectangle (buffer, sx, sy, 2*d, 2*d);
 	}
       }
@@ -404,6 +404,6 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx - d, sy, sx + d, sy);
-	  DrawLine (sx, sy - d, sx, sy + d);
+	  DrawLine (buffer, sx - d, sy, sx + d, sy);
+	  DrawLine (buffer, sx, sy - d, sx, sy + d);
 	}
       }
@@ -417,6 +417,6 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx + d, sy - d, sx - d, sy + d);
-	  DrawLine (sx - d, sy - d, sx + d, sy + d);
+	  DrawLine (buffer, sx + d, sy - d, sx - d, sy + d);
+	  DrawLine (buffer, sx - d, sy - d, sx + d, sy + d);
 	}
       }
@@ -430,5 +430,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  FillTriangle (sx - d, sy - 0.58*d, sx + d, sy - 0.58*d, sx, sy + 1.15*d);
+	  FillTriangle (buffer, sx - d, sy - 0.58*d, sx + d, sy - 0.58*d, sx, sy + 1.15*d);
 	}
       }
@@ -442,5 +442,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  OpenTriangle (sx - d, sy + 0.58*d, sx + d, sy + 0.58*d, sx, sy - 1.15*d);
+	  OpenTriangle (buffer, sx - d, sy + 0.58*d, sx + d, sy + 0.58*d, sx, sy - 1.15*d);
 	}
       }
@@ -454,7 +454,7 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx, sy, sx - d, sy - 0.58*d);
-	  DrawLine (sx, sy, sx + d, sy - 0.58*d);
-	  DrawLine (sx, sy, sx,     sy + 1.15*d);
+	  DrawLine (buffer, sx, sy, sx - d, sy - 0.58*d);
+	  DrawLine (buffer, sx, sy, sx + d, sy - 0.58*d);
+	  DrawLine (buffer, sx, sy, sx,     sy + 1.15*d);
 	}
       }
@@ -468,5 +468,5 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawCircle (sx, sy, d);
+	  DrawCircle (buffer, sx, sy, d);
 	}
       }
@@ -480,9 +480,9 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx + 0.00*d, sy - 1.00*d, sx + 0.95*d, sy - 0.31*d);
-	  DrawLine (sx + 0.95*d, sy - 0.31*d, sx + 0.58*d, sy + 0.81*d);
-	  DrawLine (sx + 0.58*d, sy + 0.81*d, sx - 0.58*d, sy + 0.81*d);
-	  DrawLine (sx - 0.58*d, sy + 0.81*d, sx - 0.95*d, sy - 0.31*d);
-	  DrawLine (sx - 0.95*d, sy - 0.31*d, sx + 0.00*d, sy - 1.00*d);
+	  DrawLine (buffer, sx + 0.00*d, sy - 1.00*d, sx + 0.95*d, sy - 0.31*d);
+	  DrawLine (buffer, sx + 0.95*d, sy - 0.31*d, sx + 0.58*d, sy + 0.81*d);
+	  DrawLine (buffer, sx + 0.58*d, sy + 0.81*d, sx - 0.58*d, sy + 0.81*d);
+	  DrawLine (buffer, sx - 0.58*d, sy + 0.81*d, sx - 0.95*d, sy - 0.31*d);
+	  DrawLine (buffer, sx - 0.95*d, sy - 0.31*d, sx + 0.00*d, sy - 1.00*d);
 	}
       }
@@ -496,10 +496,10 @@
 	    (sy < graph[0].axis[1].fy) && (sy > graph[0].axis[1].fy + graph[0].axis[1].dfy))
 	{
-	  DrawLine (sx - 1.00*d, sy + 0.00*d, sx - 0.50*d, sy + 0.87*d);
-	  DrawLine (sx - 0.50*d, sy + 0.87*d, sx + 0.50*d, sy + 0.87*d);
-	  DrawLine (sx + 0.50*d, sy + 0.87*d, sx + 1.00*d, sy + 0.00*d);
-	  DrawLine (sx + 1.00*d, sy + 0.00*d, sx + 0.50*d, sy - 0.87*d);
-	  DrawLine (sx + 0.50*d, sy - 0.87*d, sx - 0.50*d, sy - 0.87*d);
-	  DrawLine (sx - 0.50*d, sy - 0.87*d, sx - 1.00*d, sy + 0.00*d);
+	  DrawLine (buffer, sx - 1.00*d, sy + 0.00*d, sx - 0.50*d, sy + 0.87*d);
+	  DrawLine (buffer, sx - 0.50*d, sy + 0.87*d, sx + 0.50*d, sy + 0.87*d);
+	  DrawLine (buffer, sx + 0.50*d, sy + 0.87*d, sx + 1.00*d, sy + 0.00*d);
+	  DrawLine (buffer, sx + 1.00*d, sy + 0.00*d, sx + 0.50*d, sy - 0.87*d);
+	  DrawLine (buffer, sx + 0.50*d, sy - 0.87*d, sx - 0.50*d, sy - 0.87*d);
+	  DrawLine (buffer, sx - 0.50*d, sy - 0.87*d, sx - 1.00*d, sy + 0.00*d);
 	}
       }
@@ -512,5 +512,5 @@
 	sx2 = x[i+1]*mxi + y[i+1]*mxj + bx;
 	sy2 = x[i+1]*myi + y[i+1]*myj + by;
-	DrawLine (sx1, sy1, sx2, sy2);
+	DrawLine (buffer, sx1, sy1, sx2, sy2);
       }
     }
@@ -519,5 +519,5 @@
     
 /*******/
-void bDrawXErrors (KapaGraphWidget *graph, Gobjects *object) {
+void bDrawXErrors (bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object) {
   
   int i, bar;
@@ -553,9 +553,9 @@
 	 (sy1 < graph[0].axis[1].fy) && (sy1 > graph[0].axis[1].fy + graph[0].axis[1].dfy)))
     {
-      DrawLine (sx0, sy0, sx1, sy1);
+      DrawLine (buffer, sx0, sy0, sx1, sy1);
       if (bar) {
 	sx10 = sy1 - sz;
 	sx11 = sy1 + sz;
-	DrawLine (sx1, sx10, sx1, sx11);
+	DrawLine (buffer, sx1, sx10, sx1, sx11);
       }
     }
@@ -570,9 +570,9 @@
 	 (sy1 < graph[0].axis[1].fy) && (sy1 > graph[0].axis[1].fy + graph[0].axis[1].dfy)))
     {
-      DrawLine (sx0, sy0, sx1, sy1);
+      DrawLine (buffer, sx0, sy0, sx1, sy1);
       if (bar) {
 	sx10 = sy1 - sz;
 	sx11 = sy1 + sz;
-	DrawLine (sx1, sx10, sx1, sx11);
+	DrawLine (buffer, sx1, sx10, sx1, sx11);
       }
     }
@@ -581,5 +581,5 @@
     
 /*******/
-void bDrawYErrors (KapaGraphWidget *graph, Gobjects *object) {
+void bDrawYErrors (bDrawBuffer *buffer, KapaGraphWidget *graph, Gobjects *object) {
   
   int i, bar;
@@ -615,9 +615,9 @@
 	 (sy1 < graph[0].axis[1].fy) && (sy1 > graph[0].axis[1].fy + graph[0].axis[1].dfy)))
     {
-      DrawLine (sx0, sy0, sx1, sy1);
+      DrawLine (buffer, sx0, sy0, sx1, sy1);
       if (bar) {
 	sx10 = sx1 - sz;
 	sx11 = sx1 + sz;
-	DrawLine (sx10, sy1, sx11, sy1);
+	DrawLine (buffer, sx10, sy1, sx11, sy1);
       }
     }
@@ -632,9 +632,9 @@
 	 (sy1 < graph[0].axis[1].fy) && (sy1 > graph[0].axis[1].fy + graph[0].axis[1].dfy)))
     {
-      DrawLine (sx0, sy0, sx1, sy1);
+      DrawLine (buffer, sx0, sy0, sx1, sy1);
       if (bar) {
 	sx10 = sx1 - sz;
 	sx11 = sx1 + sz;
-	DrawLine (sx10, sy1, sx11, sy1);
+	DrawLine (buffer, sx10, sy1, sx11, sy1);
       }
     }
Index: /trunk/Ohana/src/kapa2/src/bDrawOverlay.c
===================================================================
--- /trunk/Ohana/src/kapa2/src/bDrawOverlay.c	(revision 29937)
+++ /trunk/Ohana/src/kapa2/src/bDrawOverlay.c	(revision 29938)
@@ -4,5 +4,5 @@
 static char name[4][16] = {"red", "green", "blue", "yellow"};
 
-void bDrawOverlay (KapaImageWidget *image, int N) {
+void bDrawOverlay (bDrawBuffer *buffer, KapaImageWidget *image, int N) {
 
   int i;
@@ -14,5 +14,5 @@
   /* translate color to bDrawColors : image[0].overlay[N].color */
   color = KapaColorByName (name[N]);
-  bDrawSetStyle (color, 0, 0);
+  bDrawSetStyle (buffer, color, 0, 0);
   
   expand = 1.0;
@@ -57,14 +57,14 @@
     switch (image[0].overlay[N].objects[i].type) {
       case KII_OVERLAY_LINE:
-	bDrawLine (X, Y, (X+dX), (Y+dY));
+	bDrawLine (buffer, X, Y, (X+dX), (Y+dY));
 	break;
       case KII_OVERLAY_TEXT:
-	bDrawRotText (X, Y, image[0].overlay[N].objects[i].text, 8, 0.0);
+	bDrawRotText (buffer, X, Y, image[0].overlay[N].objects[i].text, 8, 0.0);
 	break;
       case KII_OVERLAY_BOX:
 	dx = MAX (abs(dX),2) / 2;
 	dy = MAX (abs(dY),2) / 2;
-	bDrawRectOpen ((X-dx), (Y-dy), (X+dx), (Y+dy));
-	// bDrawRectOpen ((X-dx), (Y-dy), (X), (Y));
+	bDrawRectOpen (buffer, (X-dx), (Y-dy), (X+dx), (Y+dy));
+	// bDrawRectOpen (buffer, (X-dx), (Y-dy), (X), (Y));
 	break;
       case KII_OVERLAY_CIRCLE:
@@ -72,5 +72,5 @@
 	dy = MAX (abs(dY),2);
 	if (image[0].overlay[N].objects[i].angle == 0.0) {
-	  bDrawArc (X, Y, dx, dy, 0, 360);
+	  bDrawArc (buffer, X, Y, dx, dy, 0, 360);
 	} else {
 	  // moderately-stupid rotated ellipse drawing:
@@ -88,5 +88,5 @@
 	    x1 = X + pX*dx*cos(t)*cs - pX*dy*sin(t)*sn;
 	    y1 = Y + pY*dx*cos(t)*sn + pY*dy*sin(t)*cs;
-	    bDrawLine (x0, y0, x1, y1);
+	    bDrawLine (buffer, x0, y0, x1, y1);
 	    x0 = x1;
 	    y0 = y1;
@@ -101,5 +101,5 @@
   
   /* translate color to bDrawColors : image[0].overlay[N].color */
-  bDrawSetStyle (color, 0, 0);
+  bDrawSetStyle (buffer, color, 0, 0);
 }
 
Index: /trunk/Ohana/src/libdvo/include/dvo.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/dvo.h	(revision 29937)
+++ /trunk/Ohana/src/libdvo/include/dvo.h	(revision 29938)
@@ -58,10 +58,10 @@
 // these are used as NAN for types of int values
 typedef enum {
-    NAN_S_CHAR  = 0x7f,
-    NAN_U_CHAR  = 0xff,   // was NO_ERR
-    NAN_S_SHORT = 0x7fff, // was NO_MAG
-    NAN_U_SHORT = 0xffff, 
-    NAN_S_INT   = 0x7fffffff,
-    NAN_U_INT   = 0xffffffff,
+  NAN_S_CHAR  = 0x7f,
+  NAN_U_CHAR  = 0xff,   // was NO_ERR
+  NAN_S_SHORT = 0x7fff, // was NO_MAG
+  NAN_U_SHORT = 0xffff, 
+  NAN_S_INT   = 0x7fffffff,
+  NAN_U_INT   = (signed int) 0xffffffff,
 } DVO_INT_NAN;
 
@@ -135,17 +135,17 @@
 /* Average.code 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_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_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
@@ -156,6 +156,6 @@
   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     = 0x04000000, // good-quality measurement in our data (eg,PS)
+  ID_OBJ_GOOD_ALT = 0x08000000, // good-quality measurement in  external data (eg, 2MASS)
 } DVOAverageFlags;
 
@@ -233,5 +233,5 @@
      *** that is just silly, and bad: convert to using Nsec_mem, Nsec_disk, Nsec_off.
      *** unless we always require the secfilt and average entries to be loaded sychronously.
-   */
+     */
 
   /* pointers to split data files */
@@ -293,4 +293,5 @@
 int isRegisteredMosaic (void);
 off_t GetRegisteredMosaic (void);
+off_t *GetChipMatch (void);
 int GetMosaicCoords (Coords *coords);
 int FindMosaicForImage (Image *images, off_t Nimages, off_t entry);
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog.c	(revision 29937)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog.c	(revision 29938)
@@ -302,4 +302,6 @@
 int dvo_catalog_save (Catalog *catalog, char VERBOSE) {
 
+  int status = FALSE;
+
   // set the 'sorted' header keyword
   gfits_modify_alt (&catalog[0].header, "SORTED",  "%t", 1, catalog[0].sorted);
@@ -308,11 +310,11 @@
   switch (catalog[0].catmode) {
     case DVO_MODE_RAW:
-      dvo_catalog_save_raw (catalog, VERBOSE);
+      status = dvo_catalog_save_raw (catalog, VERBOSE);
       break;
     case DVO_MODE_MEF:
-      dvo_catalog_save_mef (catalog, VERBOSE);
+      status = dvo_catalog_save_mef (catalog, VERBOSE);
       break;
     case DVO_MODE_SPLIT:
-      dvo_catalog_save_split (catalog, VERBOSE);
+      status = dvo_catalog_save_split (catalog, VERBOSE);
       break;
     default:
@@ -320,5 +322,5 @@
       exit (2);
   }
-  return (TRUE);
+  return (status);
 }
 
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_mef.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_mef.c	(revision 29937)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_mef.c	(revision 29938)
@@ -66,5 +66,5 @@
   /* read Average table data (or skip) */
   if (catalog[0].catflags & LOAD_AVES) {
-    if (!gfits_fread_ftable_data (f, &ftable)) {
+    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table average data");
       return (FALSE);
@@ -94,5 +94,5 @@
   /* read Measure table data */
   if (catalog[0].catflags & LOAD_MEAS) {
-    if (!gfits_fread_ftable_data (f, &ftable)) {
+    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table measure data");
       return (FALSE);
@@ -119,5 +119,5 @@
   /* read Missing table data */
   if (catalog[0].catflags & LOAD_MISS) {
-    if (!gfits_fread_ftable_data (f, &ftable)) {
+    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table missing data");
       return (FALSE);
@@ -149,5 +149,5 @@
   /* read secfilt table data */
   if (catalog[0].catflags & LOAD_SECF) {
-    if (!gfits_fread_ftable_data (f, &ftable)) {
+    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table secfilt data");
       return (FALSE);
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c	(revision 29937)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c	(revision 29938)
@@ -218,5 +218,5 @@
     }
     /* read Average table data : format is irrelevant here */
-    if (!gfits_fread_ftable_data (catalog[0].f, &ftable)) { 
+    if (!gfits_fread_ftable_data (catalog[0].f, &ftable, FALSE)) { 
       if (VERBOSE) fprintf (stderr, "can't read table average data");
       return (FALSE);
@@ -249,5 +249,5 @@
     // XXX this allows an empty Measure catalog with non-empty Average catalog : is that OK?
     /* read Measure table data */
-    if (!gfits_fread_ftable_data (catalog[0].measure_catalog[0].f, &ftable)) {
+    if (!gfits_fread_ftable_data (catalog[0].measure_catalog[0].f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table measure data\n");
       return (FALSE);
@@ -279,5 +279,5 @@
   if ((status != DVO_CAT_OPEN_EMPTY) && (catalog[0].catflags & LOAD_MISS)) {
     /* read Missing table data */
-    if (!gfits_fread_ftable_data (catalog[0].missing_catalog[0].f, &ftable)) {
+    if (!gfits_fread_ftable_data (catalog[0].missing_catalog[0].f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table missing data\n");
       return (FALSE);
@@ -313,5 +313,5 @@
   if ((status != DVO_CAT_OPEN_EMPTY) && (catalog[0].catflags & LOAD_SECF)) {
     /* read secfilt table data */
-    if (!gfits_fread_ftable_data (catalog[0].secfilt_catalog[0].f, &ftable)) {
+    if (!gfits_fread_ftable_data (catalog[0].secfilt_catalog[0].f, &ftable, FALSE)) {
       if (VERBOSE) fprintf (stderr, "can't read table secfilt data\n");
       return (FALSE);
Index: /trunk/Ohana/src/libdvo/src/dvo_photcode_ops.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_photcode_ops.c	(revision 29937)
+++ /trunk/Ohana/src/libdvo/src/dvo_photcode_ops.c	(revision 29938)
@@ -557,9 +557,11 @@
       }
     }
-    if (entry == -1) {
-      // entry is missing fail (no printfs in this file)
-      free(map);
-      return(FALSE);
-    }
+    // if (entry == -1) {
+    //   // entry is missing fail (no printfs in this file)
+    //   free(map);
+    //   return(FALSE);
+    // }
+
+    // if entry is still -1, we will skip this one (not map into the output db)
     map[i] = entry;
   }
Index: /trunk/Ohana/src/libdvo/src/fits_db.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/fits_db.c	(revision 29937)
+++ /trunk/Ohana/src/libdvo/src/fits_db.c	(revision 29938)
@@ -83,5 +83,5 @@
     return (FALSE);
   }
-  if (!gfits_fread_ftable_data (db[0].f, &db[0].ftable)) {
+  if (!gfits_fread_ftable_data (db[0].f, &db[0].ftable, FALSE)) {
     fprintf (stderr, "can't read table data");
     gfits_db_free (db);
Index: /trunk/Ohana/src/libdvo/src/mosaic_astrom.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/mosaic_astrom.c	(revision 29937)
+++ /trunk/Ohana/src/libdvo/src/mosaic_astrom.c	(revision 29938)
@@ -17,4 +17,9 @@
 }
 
+/* what is the currently registered mosaic image? */
+off_t *GetChipMatch () {
+  return (ChipMatch);
+}
+
 /* given an image array and a current entry of type WRP, find matching DIS & Register it */
 int FindMosaicForImage (Image *images, off_t Nimages, off_t entry) {
@@ -40,5 +45,5 @@
   if (strcmp(&images[entry].coords.ctype[4], "-WRP")) {
     /* not a wrp image, do nothing */
-    return (TRUE); /* error or not */
+    return (entry + 1); /* error or not */
   }
 
@@ -52,5 +57,5 @@
     RegisterMosaic (&images[i].coords);
     iDIS = i;
-    return (TRUE);
+    return (i + 1);
   }
 
@@ -62,5 +67,5 @@
     RegisterMosaic (&images[i].coords);
     iDIS = i;
-    return (TRUE);
+    return (i + 1);
   }
   return (FALSE);
@@ -74,5 +79,5 @@
   if (strcmp(&images[entry].coords.ctype[4], "-WRP")) {
     /* not a wrp image, do nothing */
-    return (TRUE); /* error or not? */
+    return (entry + 1); /* error or not? */
   }
 
@@ -85,5 +90,5 @@
   RegisterMosaic (&images[N].coords);
   iDIS = N;
-  return (TRUE);
+  return (N + 1);
 }
 
@@ -165,8 +170,18 @@
   SortDISindex (DIStzero, DISentry, Ndis);
 
+  // ChipMatch has a few possible values: 
+  // -3 : image is a mosaic (DIS)
+  // -2 : image is an unassigned WRP image
+  // -1 : image is not a WRP or DIS images
+  // >= 0 : value is the mosaic seq number for this WRP image 
+
   /* find all matched WRP images */
   ALLOCATE (ChipMatch, off_t, Nimages);
   for (i = 0; i < Nimages; i++) {
     ChipMatch[i] = -1;
+    if (!strcmp(&images[i].coords.ctype[4], "-DIS")) {
+      ChipMatch[i] = -3;
+      continue;
+    }
     if (strcmp(&images[i].coords.ctype[4], "-WRP")) continue;
 
Index: /trunk/Ohana/src/libfits/include/gfitsio.h
===================================================================
--- /trunk/Ohana/src/libfits/include/gfitsio.h	(revision 29937)
+++ /trunk/Ohana/src/libfits/include/gfitsio.h	(revision 29938)
@@ -64,4 +64,5 @@
   char                   *buffer;
   off_t                   datasize;
+  off_t                   validsize;
 } FTable;
 
@@ -172,5 +173,5 @@
 int     gfits_define_table_column      PROTO((Header *header, char *format, char *label, char *comment, char *unit));
 int   	gfits_fread_ftable             PROTO((FILE *f, FTable *ftable, char *extname)); 
-int   	gfits_fread_ftable_data        PROTO((FILE *f, FTable *ftable));
+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_vtable             PROTO((FILE *f, VTable *vtable, char *extname, off_t Nrow, off_t *row));
Index: /trunk/Ohana/src/libfits/table/F_read_T.c
===================================================================
--- /trunk/Ohana/src/libfits/table/F_read_T.c	(revision 29937)
+++ /trunk/Ohana/src/libfits/table/F_read_T.c	(revision 29938)
@@ -36,5 +36,5 @@
     gfits_scan (header, "EXTNAME", "%s", 1, tname);
     if (!strcmp (tname, extname)) {
-      if (gfits_fread_ftable_data (f, table)) return (TRUE);
+      if (gfits_fread_ftable_data (f, table, FALSE)) return (TRUE);
       gfits_free_header (header);
       return (FALSE);
@@ -50,5 +50,5 @@
 
 /*********************** fits read ftable data ***********************************/
-int gfits_fread_ftable_data (FILE *f, FTable *table) {
+int gfits_fread_ftable_data (FILE *f, FTable *table, int padIfShort) {
 
   off_t Nbytes, Nread;
@@ -65,9 +65,18 @@
     if (Nread < gfits_data_min_size (table[0].header)) {
       fprintf (stderr, "error: fits read error in %s, read "OFF_T_FMT", need "OFF_T_FMT"\n", __func__,  Nread,  gfits_data_min_size (table[0].header));
-      gfits_free_table  (table);
-      return (FALSE);
+      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;
+	return (TRUE);
+      } else {
+	gfits_free_table  (table);
+	return (FALSE);
+      }
     }
     fprintf (stderr, "warning: file missing pad\n");
   }
+  table[0].validsize = Nread;
   table[0].datasize = Nbytes;
   return (TRUE);
Index: /trunk/Ohana/src/libkapa/include/kapa.h
===================================================================
--- /trunk/Ohana/src/libkapa/include/kapa.h	(revision 29937)
+++ /trunk/Ohana/src/libkapa/include/kapa.h	(revision 29938)
@@ -115,6 +115,15 @@
 
 typedef struct {
-  int Nx, Ny;
+  int Nx, Ny, Nbyte;
   bDrawColor **pixels;
+  png_color *palette;
+  int Npalette;
+  // current drawing values:
+  int bWeight;
+  int bType;
+  bDrawColor bColor;
+  bDrawColor bColor_R;
+  bDrawColor bColor_G;
+  bDrawColor bColor_B;
 } bDrawBuffer;
 
@@ -165,4 +174,5 @@
 /* KapaWindow.c */
 int KiiResize (int fd, int Nx, int Ny);
+int KiiResizeByImage (int fd);
 int KiiRelocate (int fd, int x, int y);
 int KiiCenter (int fd, double x, double y, int zoom);
@@ -182,4 +192,5 @@
 int KapaGetLimits (int fd, float *dx, float *dy);
 int KapaSetSection (int fd, KapaSection *section);
+int KapaSetSectionByImage (int fd, KapaSection *section);
 int KapaSelectSection (int fd, char *name);
 int KapaGetSection (int fd, char *name);
@@ -221,29 +232,29 @@
 
 /* bDrawFuncs.c */
-bDrawBuffer *bDrawBufferCreate (int Nx, int Ny);
+bDrawBuffer *bDrawBufferCreate (int Nx, int Ny, int Nbyte, png_color *palette, int Npalette);
 void bDrawBufferFree (bDrawBuffer *buffer);
 void bDrawSetBuffer (bDrawBuffer *buffer);
-void bDrawSetStyle (bDrawColor color, int lw, int lt);
-void bDrawPoint (int x, int y);
-void bDrawPointf (float x, float y);
-
-void bDrawArc (double Xc, double Yc, double Xr, double Yr, double Ts, double Te);
-void bDrawCircle (double Xc, double Yc, double radius);
-void bDrawCircleFill (double xc, double yc, double radius);
-
-void bDrawLine (double x1, double y1, double x2, double y2);
-void bDrawLineWeight (int X1, int Y1, int X2, int Y2, int swapcoords);
-void bDrawLineBresen (int X1, int Y1, int X2, int Y2, int swapcoords);
-void bDrawLineHorizontal (int X1, int X2, int Y);
-void bDrawLineVertical (int X, int Y1, int Y2);
-
-void bDrawRectOpen (double x1, double y1, double x2, double y2);
-void bDrawRectFill (double x1, double y1, double x2, double y2);
-void bDrawTriOpen (double x1, double y1, double x2, double y2, double x3, double y3);
-void bDrawTriFill (double x1, double y1, double x2, double y2, double x3, double y3);
+void bDrawSetStyle (bDrawBuffer *buffer, bDrawColor color, int lw, int lt);
+void bDrawPoint (bDrawBuffer *buffer, int x, int y);
+void bDrawPointf (bDrawBuffer *buffer, float x, float y);
+
+void bDrawArc (bDrawBuffer *buffer, double Xc, double Yc, double Xr, double Yr, double Ts, double Te);
+void bDrawCircle (bDrawBuffer *buffer, double Xc, double Yc, double radius);
+void bDrawCircleFill (bDrawBuffer *buffer, double xc, double yc, double radius);
+
+void bDrawLine (bDrawBuffer *buffer, double x1, double y1, double x2, double y2);
+void bDrawLineWeight (bDrawBuffer *buffer, int X1, int Y1, int X2, int Y2, int swapcoords);
+void bDrawLineBresen (bDrawBuffer *buffer, int X1, int Y1, int X2, int Y2, int swapcoords);
+void bDrawLineHorizontal (bDrawBuffer *buffer, int X1, int X2, int Y);
+void bDrawLineVertical (bDrawBuffer *buffer, int X, int Y1, int Y2);
+
+void bDrawRectOpen (bDrawBuffer *buffer, double x1, double y1, double x2, double y2);
+void bDrawRectFill (bDrawBuffer *buffer, double x1, double y1, double x2, double y2);
+void bDrawTriOpen  (bDrawBuffer *buffer, double x1, double y1, double x2, double y2, double x3, double y3);
+void bDrawTriFill  (bDrawBuffer *buffer, double x1, double y1, double x2, double y2, double x3, double y3);
 
 /* bDrawRotFont.c */
-int bDrawRotText (int x, int y, char *string, int pos, double angle);
-int bDrawRotBitmap (int x, int y, int dx, int dy, unsigned char *bitmap, int mode, double angle, double scale);
+int bDrawRotText (bDrawBuffer *buffer, int x, int y, char *string, int pos, double angle);
+int bDrawRotBitmap (bDrawBuffer *buffer, int x, int y, int dx, int dy, unsigned char *bitmap, int mode, double angle, double scale);
 
 /* Kapa Socket functions */
Index: /trunk/Ohana/src/libkapa/src/KapaWindow.c
===================================================================
--- /trunk/Ohana/src/libkapa/src/KapaWindow.c	(revision 29937)
+++ /trunk/Ohana/src/libkapa/src/KapaWindow.c	(revision 29938)
@@ -23,4 +23,11 @@
   KiiSendCommand (fd, 4, "RSIZ");
   KiiSendMessage (fd, "%d %d", Nx, Ny); 
+  KiiWaitAnswer (fd, "DONE");
+  return (TRUE);
+}
+
+int KiiResizeByImage (int fd) {
+
+  KiiSendCommand (fd, 4, "ISIZ");
   KiiWaitAnswer (fd, "DONE");
   return (TRUE);
@@ -370,4 +377,16 @@
 }
 
+int KapaSetSectionByImage (int fd, KapaSection *section) {
+
+  KiiSendCommand (fd, 4, "ISEC");
+  KiiSendMessage (fd, "%s %6.3f %6.3f %3d", 
+		  section[0].name, 
+		  section[0].x,
+		  section[0].y,
+		  section[0].bg);
+  KiiWaitAnswer (fd, "DONE");
+  return (TRUE);
+}
+
 int KapaSectionBG (int fd, char *name, int bg) {
 
Index: /trunk/Ohana/src/libkapa/src/bDrawFuncs.c
===================================================================
--- /trunk/Ohana/src/libkapa/src/bDrawFuncs.c	(revision 29937)
+++ /trunk/Ohana/src/libkapa/src/bDrawFuncs.c	(revision 29938)
@@ -1,33 +1,58 @@
 # include <kapa_internal.h>
-
-// XXX we can get rid of these static vars by making them elements of the bDrawBuffer
-// 
-
-
-static int bWeight;
-static int bType;
-static bDrawColor bColor;
-static bDrawBuffer *bBuffer;
-void bDrawCircleSingle (double xc, double yc, double radius);
-
-bDrawBuffer *bDrawBufferCreate (int Nx, int Ny) {
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort (); } }
+
+// move these to the bDrawBuffer type
+// static int bWeight;
+// static int bType;
+// static bDrawColor bColor;
+// static bDrawColor bColor_R;
+// static bDrawColor bColor_G;
+// static bDrawColor bColor_B;
+// static bDrawBuffer *bBuffer;
+
+void bDrawCircleSingle (bDrawBuffer *buffer, double xc, double yc, double radius);
+
+// create a drawing buffer with either 1 or 3 byte colors
+bDrawBuffer *bDrawBufferCreate (int Nx, int Ny, int Nbyte, png_color *palette, int Npalette) {
 
   int i, j;
-  bDrawColor white;
+  bDrawColor white, white_R, white_G, white_B;
   bDrawBuffer *buffer;
 
+  myAssert((Nbyte == 1) || (Nbyte == 3), "invalid depth");
+  myAssert(palette, "missing palette");
+
   white = KapaColorByName ("white");
+  white_R = palette[white].red;
+  white_G = palette[white].green;
+  white_B = palette[white].blue;
 
   ALLOCATE (buffer, bDrawBuffer, 1);
   buffer[0].Nx = Nx;
   buffer[0].Ny = Ny;
+  buffer[0].Nbyte = Nbyte;
+  buffer[0].palette = palette;
+  buffer[0].Npalette = Npalette;
 
   ALLOCATE (buffer[0].pixels, bDrawColor *, Ny);
   for (i = 0; i < Ny; i++) {
-    ALLOCATE (buffer[0].pixels[i], bDrawColor, Nx);
+    ALLOCATE (buffer[0].pixels[i], bDrawColor, Nbyte*Nx);
     for (j = 0; j < Nx; j++) {
-      buffer[0].pixels[i][j] = white;
-    }
-  }
+      if (Nbyte == 1) {
+	buffer[0].pixels[i][j] = white;
+      } else {
+	buffer[0].pixels[i][3*j+0] = white_R;
+	buffer[0].pixels[i][3*j+1] = white_G;
+	buffer[0].pixels[i][3*j+2] = white_B;
+      }
+    }
+  }
+
+  buffer[0].bWeight = 0;
+  buffer[0].bType = 0;
+  buffer[0].bColor = white;
+  buffer[0].bColor_R = white_R;
+  buffer[0].bColor_G = white_G;
+  buffer[0].bColor_B = white_B;
   return (buffer);
 }
@@ -41,48 +66,60 @@
   }
   free (buffer[0].pixels);
+  free (buffer[0].palette);
   free (buffer);
   return;
 }
 
-void bDrawSetBuffer (bDrawBuffer *buffer) {
-  bBuffer = buffer;
-  return;
-}
-
-void bDrawSetStyle (bDrawColor color, int lw, int lt) {
-  bColor = color;
-  bWeight = lw;
-  bType = lt;
+// void bDrawSetBuffer (bDrawBuffer *buffer) {
+//   myAssert(buffer[0].Nbyte == 1, "invalid depth");
+//   bBuffer = buffer;
+//   return;
+// }
+
+void bDrawSetStyle (bDrawBuffer *buffer, bDrawColor color, int lw, int lt) {
+  buffer->bColor = color;
+  buffer->bColor_R = buffer->palette[color].red;
+  buffer->bColor_G = buffer->palette[color].green;
+  buffer->bColor_B = buffer->palette[color].blue;
+  buffer->bWeight = lw;
+  buffer->bType = lt;
   return;
 }
 
 // draw a point in the current color 
-void bDrawPoint (int x, int y) {
-
+void bDrawPoint (bDrawBuffer *buffer, int x, int y) {
+
+  // myAssert(buffer[0].Nbyte == 1, "invalid depth");
   if (x < 0) return;
   if (y < 0) return;
-  if (x >= bBuffer[0].Nx) return;
-  if (y >= bBuffer[0].Ny) return;
-  bBuffer[0].pixels[y][x] = bColor;
+  if (x >= buffer[0].Nx) return;
+  if (y >= buffer[0].Ny) return;
+  if (buffer[0].Nbyte == 1) {
+    buffer[0].pixels[y][x] = buffer->bColor;
+  } else {
+    buffer[0].pixels[y][3*x+0] = buffer->bColor_R;
+    buffer[0].pixels[y][3*x+1] = buffer->bColor_G;
+    buffer[0].pixels[y][3*x+2] = buffer->bColor_B;
+  }
   return;
 }
 
 // draw a point in the current color 
-void bDrawPointf (float x, float y) {
-
-  bDrawPoint (ROUND(x), ROUND(y));
-  return;
-}
-
-void bDrawTriOpen (double x1, double y1, double x2, double y2, double x3, double y3) {
-
-  bDrawLine (x1, y1, x2, y2);
-  bDrawLine (x2, y2, x3, y3);
-  bDrawLine (x3, y3, x1, y1);
-
-  return;
-}
-
-void bDrawRectOpen (double x1, double y1, double x2, double y2) {
+void bDrawPointf (bDrawBuffer *buffer, float x, float y) {
+
+  bDrawPoint (buffer, ROUND(x), ROUND(y));
+  return;
+}
+
+void bDrawTriOpen (bDrawBuffer *buffer, double x1, double y1, double x2, double y2, double x3, double y3) {
+
+  bDrawLine (buffer, x1, y1, x2, y2);
+  bDrawLine (buffer, x2, y2, x3, y3);
+  bDrawLine (buffer, x3, y3, x1, y1);
+
+  return;
+}
+
+void bDrawRectOpen (bDrawBuffer *buffer, double x1, double y1, double x2, double y2) {
 
   int X1, Y1, X2, Y2;
@@ -91,18 +128,18 @@
   if (y1 > y2) SWAP (y1, y2);
 
-  X1 = MIN (MAX (ROUND (x1), 0), bBuffer[0].Nx - 1);
-  X2 = MIN (MAX (ROUND (x2), 1), bBuffer[0].Nx);
-
-  Y1 = MIN (MAX (ROUND (y1), 0), bBuffer[0].Ny - 1);
-  Y2 = MIN (MAX (ROUND (y2), 1), bBuffer[0].Ny);
-
-  bDrawLineHorizontal (X1, X2, Y1);
-  bDrawLineHorizontal (X1, X2, Y2);
-  bDrawLineVertical (X1, Y1, Y2);
-  bDrawLineVertical (X2, Y1, Y2);
-  return;
-}
-
-void bDrawRectFill (double x1, double y1, double x2, double y2) {
+  X1 = MIN (MAX (ROUND (x1), 0), buffer[0].Nx - 1);
+  X2 = MIN (MAX (ROUND (x2), 1), buffer[0].Nx - 1);
+
+  Y1 = MIN (MAX (ROUND (y1), 0), buffer[0].Ny - 1);
+  Y2 = MIN (MAX (ROUND (y2), 1), buffer[0].Ny - 1);
+
+  bDrawLineHorizontal (buffer, X1, X2, Y1);
+  bDrawLineHorizontal (buffer, X1, X2, Y2);
+  bDrawLineVertical   (buffer, X1, Y1, Y2);
+  bDrawLineVertical   (buffer, X2, Y1, Y2);
+  return;
+}
+
+void bDrawRectFill (bDrawBuffer *buffer, double x1, double y1, double x2, double y2) {
 
   int i;
@@ -112,12 +149,12 @@
   if (y1 > y2) SWAP (y1, y2);
 
-  X1 = MIN (MAX (ROUND (x1), 0), bBuffer[0].Nx - 1);
-  X2 = MIN (MAX (ROUND (x2), 1), bBuffer[0].Nx);
-
-  Y1 = MIN (MAX (ROUND (y1), 0), bBuffer[0].Ny - 1);
-  Y2 = MIN (MAX (ROUND (y2), 0), bBuffer[0].Ny);
+  X1 = MIN (MAX (ROUND (x1), 0), buffer[0].Nx - 1);
+  X2 = MIN (MAX (ROUND (x2), 1), buffer[0].Nx - 1);
+
+  Y1 = MIN (MAX (ROUND (y1), 0), buffer[0].Ny - 1);
+  Y2 = MIN (MAX (ROUND (y2), 0), buffer[0].Ny - 1);
 
   for (i = Y1; i < Y2; i++) {
-    bDrawLineHorizontal (X1, X2, i);
+    bDrawLineHorizontal (buffer, X1, X2, i);
   } 
   return;
@@ -125,5 +162,5 @@
 
 // identify the quadrant and draw the correct line
-void bDrawLine (double x1, double y1, double x2, double y2) {
+void bDrawLine (bDrawBuffer *buffer, double x1, double y1, double x2, double y2) {
 
   int FlipDirect, FlipCoords;
@@ -144,8 +181,8 @@
   FlipDirect = FlipCoords ? (y1 > y2) : (x1 > x2);
 
-  if (!FlipDirect && !FlipCoords) bDrawLineWeight (X1, Y1, X2, Y2, FALSE);
-  if ( FlipDirect && !FlipCoords) bDrawLineWeight (X2, Y2, X1, Y1, FALSE);
-  if (!FlipDirect &&  FlipCoords) bDrawLineWeight (Y1, X1, Y2, X2, TRUE);
-  if ( FlipDirect &&  FlipCoords) bDrawLineWeight (Y2, X2, Y1, X1, TRUE);
+  if (!FlipDirect && !FlipCoords) bDrawLineWeight (buffer, X1, Y1, X2, Y2, FALSE);
+  if ( FlipDirect && !FlipCoords) bDrawLineWeight (buffer, X2, Y2, X1, Y1, FALSE);
+  if (!FlipDirect &&  FlipCoords) bDrawLineWeight (buffer, Y1, X1, Y2, X2, TRUE);
+  if ( FlipDirect &&  FlipCoords) bDrawLineWeight (buffer, Y2, X2, Y1, X1, TRUE);
 
   return;
@@ -153,16 +190,16 @@
 
 // draw a series of lines to give the line weight
-void bDrawLineWeight (int X1, int Y1, int X2, int Y2, int swapcoords) {
+void bDrawLineWeight (bDrawBuffer *buffer, int X1, int Y1, int X2, int Y2, int swapcoords) {
 
   int dN, dNs, dNe;
 
-  dNs = -0.5*(bWeight - 1); 
+  dNs = -0.5*(buffer->bWeight - 1); 
   /* 0, 0, 0, -1, -1, -2, -2 */
 
-  dNe = +0.5*bWeight + 1; 
+  dNe = +0.5*buffer->bWeight + 1; 
   /* 1, 1, 2, 2, 2, 3, 3 */
 
   for (dN = dNs; dN < dNe; dN++) {
-    bDrawLineBresen (X1, Y1 + dN, X2, Y2 + dN, swapcoords);
+    bDrawLineBresen (buffer, X1, Y1 + dN, X2, Y2 + dN, swapcoords);
   }
   return;
@@ -171,5 +208,5 @@
 // use the Bresenham line drawing technique
 // integer-only Bresenham line-draw version which is fast
-void bDrawLineBresen (int X1, int Y1, int X2, int Y2, int swapcoords) {
+void bDrawLineBresen (bDrawBuffer *buffer, int X1, int Y1, int X2, int Y2, int swapcoords) {
 
   int X, Y, dX, dY;
@@ -185,10 +222,10 @@
   e = 0;
   for (X = X1, N = 0; X <= X2; X++, N++) {
-    if (bType == 1) { DashOn = (N % 10) < 5; }
-    if (bType == 2) { DashOn = (N % 6) < 3; }
+    if (buffer->bType == 1) { DashOn = (N % 10) < 5; }
+    if (buffer->bType == 2) { DashOn = (N % 6) < 3; }
     if (swapcoords) {
-      if (DashOn) bDrawPoint (Y,X);
+      if (DashOn) bDrawPoint (buffer, Y,X);
     } else {
-      if (DashOn) bDrawPoint (X,Y);
+      if (DashOn) bDrawPoint (buffer, X,Y);
     }
     e += dY;
@@ -206,31 +243,43 @@
 }
 
-void bDrawLineHorizontal (int X1, int X2, int Y) {
+void bDrawLineHorizontal (bDrawBuffer *buffer, int X1, int X2, int Y) {
   
   int i;
 
   for (i = X1; i < X2; i++) {
-    bBuffer[0].pixels[Y][i] = bColor;
-  }
-  return;
-}
-
-void bDrawLineVertical (int X, int Y1, int Y2) {
+    if (buffer[0].Nbyte == 1) {
+      buffer[0].pixels[Y][i] = buffer->bColor;
+    } else {
+      buffer[0].pixels[Y][3*i+0] = buffer->bColor_R;
+      buffer[0].pixels[Y][3*i+1] = buffer->bColor_G;
+      buffer[0].pixels[Y][3*i+2] = buffer->bColor_B;
+    }
+  }
+  return;
+}
+
+void bDrawLineVertical (bDrawBuffer *buffer, int X, int Y1, int Y2) {
   
   int i;
 
   for (i = Y1; i < Y2; i++) {
-    bBuffer[0].pixels[i][X] = bColor;
-  }
-  return;
-}
-
-void bDrawTriFill (double x1, double y1, double x2, double y2, double x3, double y3) {
-
-  bDrawTriOpen (x1, y1, x2, y2, x3, y3);
-  return;
-}
-
-void bDrawArc (double Xc, double Yc, double Xr, double Yr, double Ts, double Te) {
+    if (buffer[0].Nbyte == 1) {
+      buffer[0].pixels[i][X] = buffer->bColor;
+    } else {
+      buffer[0].pixels[i][3*X+0] = buffer->bColor_R;
+      buffer[0].pixels[i][3*X+1] = buffer->bColor_G;
+      buffer[0].pixels[i][3*X+2] = buffer->bColor_B;
+    }
+  }
+  return;
+}
+
+void bDrawTriFill (bDrawBuffer *buffer, double x1, double y1, double x2, double y2, double x3, double y3) {
+
+  bDrawTriOpen (buffer, x1, y1, x2, y2, x3, y3);
+  return;
+}
+
+void bDrawArc (bDrawBuffer *buffer, double Xc, double Yc, double Xr, double Yr, double Ts, double Te) {
 
   float t, dt;
@@ -258,5 +307,5 @@
 
     /* we could use the value of MAX(dy/dt,dx/dt) to set dt */
-    bDrawPoint (x,y);
+    bDrawPoint (buffer, x,y);
 
     dt = MAX (fabs(Xr * sin(t)), fabs(Yr * cos(t)));
@@ -267,16 +316,16 @@
 
 // draw a series of circles to give line weight
-void bDrawCircle (double xc, double yc, double radius) {
+void bDrawCircle (bDrawBuffer *buffer, double xc, double yc, double radius) {
 
   int dN, dNs, dNe;
 
-  dNs = -0.5*(bWeight - 1); 
+  dNs = -0.5*(buffer->bWeight - 1); 
   /* 0, 0, 0, -1, -1, -2, -2 */
 
-  dNe = +0.5*bWeight + 1; 
+  dNe = +0.5*buffer->bWeight + 1; 
   /* 1, 1, 2, 2, 2, 3, 3 */
 
   for (dN = dNs; dN < dNe; dN++) {
-    bDrawCircleSingle (xc, yc, radius + dN);
+    bDrawCircleSingle (buffer, xc, yc, radius + dN);
   }
   return;
@@ -284,5 +333,5 @@
 
 // draw a pure circle  
-void bDrawCircleSingle (double xc, double yc, double radius) {
+void bDrawCircleSingle (bDrawBuffer *buffer, double xc, double yc, double radius) {
 
   int Xc, Yc, Radius;
@@ -300,12 +349,12 @@
 
   while (x <= y) {
-    bDrawPoint (Xc+x, Yc+y);
-    bDrawPoint (Xc+x, Yc-y);
-    bDrawPoint (Xc-x, Yc+y);
-    bDrawPoint (Xc-x, Yc-y);
-    bDrawPoint (Xc+y, Yc+x);
-    bDrawPoint (Xc+y, Yc-x);
-    bDrawPoint (Xc-y, Yc+x);
-    bDrawPoint (Xc-y, Yc-x);
+    bDrawPoint (buffer, Xc+x, Yc+y);
+    bDrawPoint (buffer, Xc+x, Yc-y);
+    bDrawPoint (buffer, Xc-x, Yc+y);
+    bDrawPoint (buffer, Xc-x, Yc-y);
+    bDrawPoint (buffer, Xc+y, Yc+x);
+    bDrawPoint (buffer, Xc+y, Yc-x);
+    bDrawPoint (buffer, Xc-y, Yc+x);
+    bDrawPoint (buffer, Xc-y, Yc-x);
 
     if (d < 0) {
@@ -322,5 +371,5 @@
 
 // draw a pure circle  
-void bDrawCircleFill (double xc, double yc, double radius) {
+void bDrawCircleFill (bDrawBuffer *buffer, double xc, double yc, double radius) {
 
   int Xc, Yc, Radius;
@@ -338,8 +387,8 @@
 
   while (x <= y) {
-    bDrawLineHorizontal (Xc-x, Xc+x, Yc+y);
-    bDrawLineHorizontal (Xc-x, Xc+x, Yc-y);
-    bDrawLineHorizontal (Xc-y, Xc+y, Yc+x);
-    bDrawLineHorizontal (Xc-y, Xc+y, Yc-x);
+    bDrawLineHorizontal (buffer, Xc-x, Xc+x, Yc+y);
+    bDrawLineHorizontal (buffer, Xc-x, Xc+x, Yc-y);
+    bDrawLineHorizontal (buffer, Xc-y, Xc+y, Yc+x);
+    bDrawLineHorizontal (buffer, Xc-y, Xc+y, Yc-x);
 
     if (d < 0) {
Index: /trunk/Ohana/src/libkapa/src/bDrawRotFont.c
===================================================================
--- /trunk/Ohana/src/libkapa/src/bDrawRotFont.c	(revision 29937)
+++ /trunk/Ohana/src/libkapa/src/bDrawRotFont.c	(revision 29938)
@@ -6,8 +6,9 @@
 # define NEARINT(x) ((x < 0) ? ((int)(x - 0.5)) : ((int)(x + 0.5)))
   
-static bDrawColor black;
-static bDrawColor white;
-
-int bDrawRotText (int x, int y, char *string, int pos, double angle) {
+// XXX need to pass the rot text color here
+// static bDrawColor black;
+// static bDrawColor white;
+
+int bDrawRotText (bDrawBuffer *buffer, int x, int y, char *string, int pos, double angle) {
 
   unsigned char *bitmap;
@@ -19,6 +20,6 @@
   RotFont *currentfont;
 
-  white = KapaColorByName ("white");
-  black = KapaColorByName ("black");
+  // white = KapaColorByName ("white");
+  // black = KapaColorByName ("black");
 
   currentname = GetRotFont (&currentsize);
@@ -111,5 +112,5 @@
     X = x + (int)(Xoff*cs - Yoff*sn) + (int)(currentscale*currentfont[N].ascent*sn);
     Y = y + (int)(Xoff*sn + Yoff*cs) - (int)(currentscale*currentfont[N].ascent*cs);
-    bDrawRotBitmap (X, Y, dx, dy, bitmap, TRUE, angle, currentscale);
+    bDrawRotBitmap (buffer, X, Y, dx, dy, bitmap, TRUE, angle, currentscale);
     Xoff += 1 + (int)(currentscale*dx + 0.5);
   }
@@ -118,17 +119,16 @@
 }
 
-int bDrawRotBitmap (int x, int y, int dx, int dy, unsigned char *bitmap, int mode, double angle, double scale) {
+int bDrawRotBitmap (bDrawBuffer *buffer, int x, int y, int dx, int dy, unsigned char *bitmap, int mode, double angle, double scale) {
 
   int ii, jj, byte_line, byte, bit, flag;
-  bDrawColor color;
   double i, j, cs, sn, rscale, tmp;
   int X, Y, X0, X1, X2, Y0, Y1, Y2, x0, y0;
 
   /* this mode option is nort actually used... */
-  if (mode) {
-    color = black;
-  } else {
-    color = white;
-  } 
+  // if (mode) {
+  //   color = black;
+  // } else {
+  //   color = white;
+  // } 
     
   byte_line = (int) ((dx + 7) / 8);
@@ -148,5 +148,5 @@
   Y = YPROC (dx,0);
 # ifdef DRAWBOXES
-  bDrawLine (x+X, y+Y, x+X1, y+Y1);
+  bDrawLine (buffer, x+X, y+Y, x+X1, y+Y1);
   Xt = X;
   Yt = Y;
@@ -160,5 +160,5 @@
   Y = YPROC (dx,dy);
 # ifdef DRAWBOXES
-  bDrawLine (x+X, y+Y, x+Xt, y+Yt);
+  bDrawLine (buffer, x+X, y+Y, x+Xt, y+Yt);
   Xt = X;
   Yt = Y;
@@ -172,5 +172,5 @@
   Y = YPROC (0,dy);
 # ifdef DRAWBOXES
-  bDrawLine (x+X, y+Y, x+Xt, y+Yt);
+  bDrawLine (buffer, x+X, y+Y, x+Xt, y+Yt);
   Xt = X;
   Yt = Y;
@@ -181,5 +181,5 @@
   X2 = MAX (X, X2);
 
-  bDrawSetStyle (color, 0, 0);
+  // bDrawSetStyle (color, 0, 0);
   if (scale > 1) {
     for (i = X1; i <= X2; i+=1) {
@@ -191,5 +191,5 @@
 	bit = ii % 8;
 	flag = 0x01 & (bitmap[byte] >> bit);
-	if (flag) bDrawPointf (x + i, y + j);
+	if (flag) bDrawPointf (buffer, x + i, y + j);
       }
     }
@@ -203,9 +203,9 @@
 	bit = ii % 8;
 	flag = 0x01 & (bitmap[byte] >> bit);
-	if (flag) bDrawPointf (x + i, y + j);
+	if (flag) bDrawPointf (buffer, x + i, y + j);
       }
     }
   }
-  bDrawSetStyle (black, 0, 0);
+  // bDrawSetStyle (black, 0, 0);
   return (TRUE);
 }
Index: /trunk/Ohana/src/opihi/cmd.astro/region.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.astro/region.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.astro/region.c	(revision 29938)
@@ -55,4 +55,5 @@
   if ((argc != 4) && (argc != 5)) {
     gprint (GP_ERR, "USAGE: region Ra Dec Radius [projection] [orientation]\n");
+    gprint (GP_ERR, "  [-image] [-ew] [+ew] [-ns] [+ns] [-no-clear]\n");
     gprint (GP_ERR, " current: %f %f (%f x %f) (%s)\n", 
 	     graphmode.coords.crval1, graphmode.coords.crval2, 
Index: /trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 29938)
@@ -36,4 +36,5 @@
 $(SRC)/cut.$(ARCH).o		\
 $(SRC)/delete.$(ARCH).o	\
+$(SRC)/densify.$(ARCH).o	\
 $(SRC)/device.$(ARCH).o	\
 $(SRC)/dimendown.$(ARCH).o	\
Index: /trunk/Ohana/src/opihi/cmd.data/densify.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/densify.c	(revision 29938)
+++ /trunk/Ohana/src/opihi/cmd.data/densify.c	(revision 29938)
@@ -0,0 +1,61 @@
+# include "data.h"
+
+int densify (int argc, char **argv) {
+
+  int i, Nx, Ny, Xb, Yb, Normalize, N;
+  float Xmin, Xmax, dX, Ymin, Ymax, dY;
+  float *val;
+  Buffer *bf;
+  Vector *vx, *vy;
+  opihi_flt *x, *y;
+
+  Normalize = TRUE;
+  if ((N = get_argument (argc, argv, "-raw"))) {
+    remove_argument (N, &argc, argv);
+    Normalize = FALSE;
+  }
+
+  if (argc != 10) {
+    gprint (GP_ERR, "USAGE: densify buffer x y Xmin Xmax dX Ymin Ymax dY\n");
+    return (FALSE);
+  }
+  
+  if ((bf = SelectBuffer (argv[1], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((vx = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((vy = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  if (vx[0].Nelements != vy[0].Nelements) return (FALSE);
+
+  REQUIRE_VECTOR_FLT (vx, FALSE); 
+  REQUIRE_VECTOR_FLT (vy, FALSE); 
+
+  Xmin = atof (argv[4]);
+  Xmax = atof (argv[5]);
+  dX   = atof (argv[6]);
+
+  Ymin = atof (argv[7]);
+  Ymax = atof (argv[8]);
+  dY   = atof (argv[9]);
+
+  Nx = (Xmax - Xmin) / dX + 1;
+  Ny = (Ymax - Ymin) / dY + 1;
+  
+  gfits_free_matrix (&bf[0].matrix);
+  gfits_free_header (&bf[0].header);
+  CreateBuffer (bf, Nx, Ny, -32, 0.0, 1.0);
+  strcpy (bf[0].file, "(empty)");
+
+  x = vx[0].elements.Flt;
+  y = vy[0].elements.Flt;
+  val = (float *)bf[0].matrix.buffer;
+  for (i = 0; i < vx[0].Nelements; i++, x++, y++) {
+    Xb = (*x - Xmin) / dX;
+    Yb = (*y - Ymin) / dY;
+    if (Xb >= Nx) continue;
+    if (Yb >= Ny) continue;
+    if (Xb < 0) continue;
+    if (Yb < 0) continue;
+    val[Xb + Yb*Nx] ++;
+  }
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 29938)
@@ -25,4 +25,5 @@
 int dbselect         PROTO((int, char **));
 int delete           PROTO((int, char **));
+int densify          PROTO((int, char **));
 int device           PROTO((int, char **));
 int dimendown        PROTO((int, char **));
@@ -161,4 +162,5 @@
   {1, "dbselect",     dbselect,         "extract vectors from mysql database table"},
   {1, "delete",       delete,           "delete vectors or images"},
+  {1, "densify",      densify,          "create an image histogram from a set of vectors"},
   {1, "device",       device,           "set / get current graphics device"},
   {1, "dimendown",    dimendown,        "convert image to vector"},
Index: /trunk/Ohana/src/opihi/cmd.data/rd.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/rd.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/rd.c	(revision 29938)
@@ -184,5 +184,5 @@
     ftable.header[0].buffer = NULL;
     gfits_copy_header (&buf[0].header, ftable.header);
-    status = gfits_fread_ftable_data (f, &ftable);  // this just reads the bytes (not even a SWAP)
+    status = gfits_fread_ftable_data (f, &ftable, FALSE);  // this just reads the bytes (not even a SWAP)
     status = gfits_uncompress_image (&buf[0].header, &buf[0].matrix, &ftable);
     // uncompressing the image leaves the format as an extension
Index: /trunk/Ohana/src/opihi/cmd.data/read_vectors.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 29938)
@@ -160,5 +160,5 @@
 
   off_t Nbytes;
-  int i, j, k, N, Nextend, Ny, Binary, vecType;
+  int i, j, k, N, Nextend, Ny, Binary, vecType, padIfShort;
   char type[16], ID[80], *CCDKeyword;
   FTable table;
@@ -174,4 +174,10 @@
     CCDKeyword = strcreate (argv[N]);
     remove_argument (N, &argc, argv);
+  }
+
+  padIfShort = FALSE;
+  if ((N = get_argument (argc, argv, "-pad-if-short"))) {
+    remove_argument (N, &argc, argv);
+    padIfShort = TRUE;
   }
 
@@ -205,5 +211,5 @@
     }
     if (!gfits_load_header (f, &header)) ESCAPE ("error reading header for extension");
-    if (!gfits_fread_ftable_data (f, &table)) ESCAPE ("error reading table for extension");
+    if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension");
 
   } else {
@@ -236,5 +242,5 @@
 	continue;
       }
-      if (!gfits_fread_ftable_data (f, &table)) ESCAPE ("error reading table for extension");
+      if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension");
       break;
     }
Index: /trunk/Ohana/src/opihi/cmd.data/resize.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/resize.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/resize.c	(revision 29938)
@@ -17,4 +17,10 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  if ((N = get_argument (argc, argv, "-by-image"))) {
+    remove_argument (N, &argc, argv);
+    KiiResizeByImage (kapa);
+    return (TRUE);
+  }
 
   if (argc != 3) {
Index: /trunk/Ohana/src/opihi/cmd.data/section.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/section.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/section.c	(revision 29938)
@@ -1,5 +1,5 @@
 # include "data.h"
 
-enum {NONE, LIST, UP, DOWN, TOP, BOTTOM, TOOL, BG};
+enum {NONE, LIST, UP, DOWN, TOP, BOTTOM, TOOL, BG, IMAGE};
 
 int section (int argc, char **argv) {
@@ -49,4 +49,9 @@
     remove_argument (N, &argc, argv);
     action = BG;
+  }
+
+  if ((N = get_argument (argc, argv, "-image"))) {
+    remove_argument (N, &argc, argv);
+    action = IMAGE;
   }
 
@@ -130,4 +135,14 @@
   } 
   
+  if (argc == 4) {
+    /* set section */
+    section.name = argv[1];
+    section.x = atof (argv[2]);
+    section.y = atof (argv[3]);
+    section.bg = background;
+    KapaSetSectionByImage (kapa, &section);
+    return (TRUE);
+  }
+
   if (argc == 6) {
     /* set section */
@@ -142,4 +157,5 @@
   }
   gprint (GP_ERR, "USAGE: section name [x y dx dy]\n");
+  gprint (GP_ERR, "USAGE: section name [-image x y] : width based on current image\n");
   gprint (GP_ERR, "USAGE: section name [-list] [-up] [-down] [-top] [-bottom]\n");
   return (FALSE);
Index: /trunk/Ohana/src/opihi/cmd.data/vgauss.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/vgauss.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/cmd.data/vgauss.c	(revision 29938)
@@ -45,4 +45,6 @@
   CastVector (yvec, OPIHI_FLT);
   CastVector (svec, OPIHI_FLT);
+  // XXX Cast is failing.
+    
 
   Npts = xvec[0].Nelements;
Index: /trunk/Ohana/src/opihi/dvo/images.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/images.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/dvo/images.c	(revision 29938)
@@ -11,9 +11,9 @@
 int images (int argc, char **argv) {
 
-  off_t i, Nimage;
+  off_t i, Nimage, Nmosaic;
   int j, status, InPic, leftside, *plist, TimeSelect, ByName;
   int WITH_MOSAIC, SOLO_MOSAIC, HIDDEN;
   time_t tzero, tend;
-  int N, NPTS, n, npts, Npts, kapa;
+  int N, NPTS, n, npts, Npts, kapa, *foundMosaic;
   Vector Xvec, Yvec;
   double r[8], d[8], x[8], y[8], Rmin, Rmax, Rmid, trange, Radius;
@@ -35,4 +35,5 @@
 
   SOLO_MOSAIC = FALSE;
+  foundMosaic = NULL;
   if ((N = get_argument (argc, argv, "-mosaic"))) {
     remove_argument (N, &argc, argv);
@@ -132,4 +133,9 @@
   BuildChipMatch (image, Nimage);
 
+  if (SOLO_MOSAIC && photcode) {
+    ALLOCATE(foundMosaic, int, Nimage);
+    memset(foundMosaic, 0, Nimage*sizeof(int));
+  }
+
   Rmin = graphmode.coords.crval1 - 180.0;
   Rmax = graphmode.coords.crval1 + 180.0;
@@ -137,4 +143,5 @@
   
   int DistortImage = wordhash ("-DIS");
+  int ChipImage    = wordhash ("-WRP");
   int TriangleUp   = wordhash ("TRP-");
   int TriangleDn   = wordhash ("TRM-");
@@ -150,5 +157,6 @@
     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 (!FindMosaicForImage (image, Nimage, i)) continue;
+    if (!(Nmosaic = FindMosaicForImage (image, Nimage, i))) continue;
+    Nmosaic --; // XXX kind of a hack: FindMosaicForImage returns 0 or the mosaic seq number + 1
     if (photcode) {
       if ( photcodeEquiv && (photcode[0].code != GetPhotcodeEquivCodebyCode(image[i].photcode))) continue;
@@ -161,4 +169,31 @@
 
     typehash = wordhash (&image[i].coords.ctype[4]);
+
+    if (photcode && SOLO_MOSAIC) {
+      // 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;
+      for (j = 0; j < Npts; j++) {
+	status = XY_to_RD (&r[j], &d[j], x[j], y[j], &image[Nmosaic].coords);
+	if (!status) break;
+	r[j] = ohana_normalize_angle (r[j]);
+	while (r[j] < Rmin) { r[j] += 360.0; }
+	while (r[j] > Rmax) { r[j] -= 360.0; }
+	if (j == 0) {
+	  leftside = (r[j] < Rmid);
+	} 
+	if (j > 0) { 
+	  if ( leftside && (r[j] > Rmid + 90)) { r[j] -= 360.0; }
+	  if (!leftside && (r[j] < Rmid - 90)) { r[j] += 360.0; }
+	}
+      }
+      foundMosaic[Nmosaic] = TRUE;
+      goto plot_points;
+    }
 
     /* DIS images represent a field, not a chip */
@@ -270,4 +305,6 @@
     if (Npts == 0) continue;
 
+  plot_points:
+
     status = FALSE;
     for (j = 0; j < Npts; j++) {
@@ -320,4 +357,5 @@
   free (Yvec.elements.Flt);
   FreeImages (image);
+  FREE (foundMosaic);
   return (TRUE);
 
Index: /trunk/Ohana/src/opihi/dvo/imlist.c
===================================================================
--- /trunk/Ohana/src/opihi/dvo/imlist.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/dvo/imlist.c	(revision 29938)
@@ -104,6 +104,6 @@
       XY_to_RD (&r, &d, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
     }
-    gprint (GP_LOG, "%3lld %s %8.4f %8.4f %f %5d %2d %4.2f %5.3f %5.3f\n", 
-	    (long long) i, image[i].name, r, d, t, image[i].nstar, image[i].photcode, image[i].secz, image[i].Mcal, image[i].dMcal);
+    gprint (GP_LOG, "%3lld %s %8lld %8.4f %8.4f %f %5d %2d %4.2f %5.3f %5.3f\n", 
+	    (long long) i, image[i].name, (long long) image[i].imageID, r, d, t, image[i].nstar, image[i].photcode, image[i].secz, image[i].Mcal, image[i].dMcal);
   }
 
Index: /trunk/Ohana/src/opihi/lib.data/starfuncs.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/starfuncs.c	(revision 29937)
+++ /trunk/Ohana/src/opihi/lib.data/starfuncs.c	(revision 29938)
@@ -5,5 +5,5 @@
   double *ring;
   double x, y, x2, y2, xy, I, sky, FWHMx, FWHMy, value, mag, Sxy;
-  int i, j, n, Npix2, Nring, Nmax;
+  int i, j, n, Radius, Nring, Nmax;
   double Npts, gain, dsky2, dmag, peak, offset;
   char *string;
@@ -19,6 +19,6 @@
   Nborder = MIN (1000, Nborder);
   
-  Npix2 = (int)(0.5*Npix);
-  Npix = 2 * Npix2 + 1;
+  Radius = (int)(0.5*Npix);
+  Npix = 2 * Radius + 1;
   Nring = 4*Nborder*(Nborder + Npix);
   ALLOCATE (ring, double, Nring);
@@ -27,11 +27,11 @@
   n = 0;  
   for (j = 0; j < Nborder; j++) {
-    for (i = X - Npix2 - Nborder; i < X + Npix2 + Nborder + 1; i++, n+=2) {
-      ring[n]   = gfits_get_matrix_value (matrix, i, (int)(Y - Npix2 - j));
-      ring[n+1] = gfits_get_matrix_value (matrix, i, (int)(Y + Npix2 + j));
-    }
-    for (i = Y - Npix2; i < Y + Npix2 + 1; i++, n+=2) {
-      ring[n]   = gfits_get_matrix_value (matrix, (int)(X - Npix2 - j), i);
-      ring[n+1] = gfits_get_matrix_value (matrix, (int)(X + Npix2 + j), i);
+    for (i = X - Radius - Nborder; i < X + Radius + Nborder + 1; i++, n+=2) {
+      ring[n]   = gfits_get_matrix_value (matrix, i, (int)(Y - Radius - j));
+      ring[n+1] = gfits_get_matrix_value (matrix, i, (int)(Y + Radius + j));
+    }
+    for (i = Y - Radius; i < Y + Radius + 1; i++, n+=2) {
+      ring[n]   = gfits_get_matrix_value (matrix, (int)(X - Radius - j), i);
+      ring[n+1] = gfits_get_matrix_value (matrix, (int)(X + Radius + j), i);
     }
   }
@@ -50,6 +50,7 @@
   Npts = Nmax = 0;
   x = y = x2 = y2 = xy = I = 0;
-  for (i = X - Npix2; i < X + Npix2 + 1; i++) {
-    for (j = Y - Npix2; j < Y + Npix2 + 1; j++) {
+  for (i = X - Radius; i < X + Radius + 1; i++) {
+    for (j = Y - Radius; j < Y + Radius + 1; j++) {
+      if (hypot((i-X), (j-Y)) > Radius) continue;
       value = gfits_get_matrix_value (matrix, i, j);
       offset = value - sky;
@@ -92,4 +93,5 @@
   set_variable ("Zpk", peak);
   set_int_variable ("Nsat", Nmax);
+  set_int_variable ("Npts", Npts);
   
   gprint (GP_LOG, "%f %f %f %f %f %f %f %f\n", x, y, FWHMx, FWHMy, sky, I, mag, dmag);
Index: /trunk/Ohana/src/photdbc/include/photdbc.h
===================================================================
--- /trunk/Ohana/src/photdbc/include/photdbc.h	(revision 29937)
+++ /trunk/Ohana/src/photdbc/include/photdbc.h	(revision 29938)
@@ -114,2 +114,3 @@
 int SetSignals (void);
 int copy_images (char *outdir);
+void usage();
Index: /trunk/Ohana/src/photdbc/src/args.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/args.c	(revision 29937)
+++ /trunk/Ohana/src/photdbc/src/args.c	(revision 29938)
@@ -61,8 +61,5 @@
   }
 
-  if (argc != 2) {
-    fprintf (stderr, "USAGE: photdbc (output)\n");
-    exit (2);
-  } 
+  if (argc != 2) usage();
 
   if ((REGION.Rmin == 0) && (REGION.Rmax == 360) && (REGION.Dmin == -90) && (REGION.Dmax == +90)) {
@@ -80,23 +77,24 @@
 }
 
-/**
+void usage() {
 
- this program takes an existing DVO database and makes a copy, applying a number of optional
- filters in the process.  
-
- photdbc (output)
-
- allowed filters / restrictions include:
-
- -region ra dec ra dec : limit operation to the specified region 
- -join                 : join measurements between stars using JOIN_RADIUS
- -ccdregion X Y X Y    : only keep detections within the specified detector region
-                         (can this be limited to specific photcodes? cameras? detectors?)
- -photcode_limits code Mmin Mmax : allow multiples of these
-
- SIGMA_MAX
- NMEAS_MIN
- INST_MAG_MAX
- INST_MAG_MIN
-
-**/
+  fprintf (stderr, "USAGE: photdbc (output)\n\n");
+  fprintf (stderr, " this program takes an existing DVO database and makes a copy, applying a number of optional\n");
+  fprintf (stderr, " filters in the process.  \n");
+  fprintf (stderr, "\n");
+  fprintf (stderr, " photdbc (output)\n");
+  fprintf (stderr, "\n");
+  fprintf (stderr, " allowed filters / restrictions include:\n");
+  fprintf (stderr, "\n");
+  fprintf (stderr, " -region Rmin Rmax Dmin Dmax : limit operation to the specified region \n");
+  fprintf (stderr, " -join                 : join measurements between stars using JOIN_RADIUS\n");
+  fprintf (stderr, " -ccdregion X Y X Y    : only keep detections within the specified detector region\n");
+  fprintf (stderr, "                         (can this be limited to specific photcodes? cameras? detectors?)\n");
+  fprintf (stderr, " -photcode_limits code Mmin Mmax : allow multiples of these\n");
+  fprintf (stderr, "\n");
+  fprintf (stderr, " SIGMA_MAX\n");
+  fprintf (stderr, " NMEAS_MIN\n");
+  fprintf (stderr, " INST_MAG_MAX\n");
+  fprintf (stderr, " INST_MAG_MIN\n");
+  exit (2);
+}
Index: /trunk/Ohana/src/photdbc/src/initialize.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/initialize.c	(revision 29937)
+++ /trunk/Ohana/src/photdbc/src/initialize.c	(revision 29938)
@@ -4,4 +4,9 @@
 
   /* are these set correctly? */
+  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();
+
   ConfigInit (&argc, argv);
   args (argc, argv);
Index: /trunk/Ohana/src/photdbc/src/join_stars.c
===================================================================
--- /trunk/Ohana/src/photdbc/src/join_stars.c	(revision 29937)
+++ /trunk/Ohana/src/photdbc/src/join_stars.c	(revision 29938)
@@ -32,4 +32,5 @@
 
   /* reference for coords is this region */
+  Ncurr = 0;
   Naves = 0;
   Rmid = Dmid = 0;
Index: /trunk/Ohana/src/relastro/src/GetAstromError.c
===================================================================
--- /trunk/Ohana/src/relastro/src/GetAstromError.c	(revision 29937)
+++ /trunk/Ohana/src/relastro/src/GetAstromError.c	(revision 29938)
@@ -2,8 +2,8 @@
 
 float GetAstromError (Measure *measure, int mode) {
-
+  //BIG HACKXXXXXXXX
+  return 0.1;
   PhotCode *code;
   float dPobs, dPsys, dPtotal, dM, AS, MS;
-
   switch (mode) {
     case ERROR_MODE_RA:
@@ -19,5 +19,4 @@
       abort();
   }
-
   /* the astrometric errors are not being carried yet (but should be!) */
   /* we use the photometric mag error as a weighting term */
@@ -28,5 +27,4 @@
   dPsys = code[0].astromErrSys;
   dM    = measure[0].dM;
-  
   dPtotal = sqrt(SQ(dPsys) + AS*SQ(dPobs) + MS*SQ(dM));
 
@@ -35,5 +33,4 @@
 
   dPtotal = MAX (dPtotal, MIN_ERROR);
-
   return (dPtotal);
 }
Index: /trunk/Ohana/src/relastro/src/UpdateObjects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/UpdateObjects.c	(revision 29937)
+++ /trunk/Ohana/src/relastro/src/UpdateObjects.c	(revision 29938)
@@ -54,5 +54,4 @@
   memset (&fitPM,  0, sizeof(fitPM));
   memset (&fitPAR, 0, sizeof(fitPAR));
-  
   initObjectData (catalog, Ncatalog);
 
@@ -72,5 +71,4 @@
   // use J2000 as a reference time
   T2000 = ohana_date_to_sec ("2000/01/01");
-
   // XXX in the future, use catalog[0].Nsecfilt only?  allow catalogs to have variable Nsecfilt?
   Nsecfilt = GetPhotcodeNsecfilt ();
@@ -97,5 +95,4 @@
       N = 0;
       m = catalog[i].average[j].measureOffset;
-
       Tmin = Tmax = (catalog[i].measure[m].t - T2000) / (86400*365.25);
       mode = FIT_MODE;
@@ -137,6 +134,9 @@
 
 	// dX, dY : error in arcsec -- 
-	dX[N] = GetAstromError (&catalog[i].measure[m], ERROR_MODE_RA);
-	dY[N] = GetAstromError (&catalog[i].measure[m], ERROR_MODE_DEC);
+	// dX[N] = GetAstromError (&catalog[i].measure[m], ERROR_MODE_RA);
+	// dY[N] = GetAstromError (&catalog[i].measure[m], ERROR_MODE_DEC);
+
+	dX[N] = 0.1;
+	dY[N] = 0.1;
 	dT[N] = catalog[i].measure[m].dt;
 
@@ -145,6 +145,6 @@
 	// non-circular errors (different values for X and Y), then dR and dD will be
 	// incorrect: they would need to be rotated to take out the position angle
-	dR[k] = dX[k] / 3600.0;
-	dD[k] = dY[k] / 3600.0;
+	dR[N] = dX[N] / 3600.0;
+	dD[N] = dY[N] / 3600.0;
 
 	N++;
@@ -194,4 +194,5 @@
 
 	FitPM (&fitPM, X, dX, Y, dY, T, N);
+
 	if (XVERB) fprintf (stderr, "fitted:  %f - %f : %f %f : %f %f : %f vs %f\n", Tmin, Tmax, fitPM.Ro, fitPM.Do, fitPM.uR, fitPM.uD, fitPM.chisq, fitAve.chisq);
 
@@ -244,27 +245,29 @@
 
       switch (result) {
-	case FIT_AVERAGE:
-	  catalog[i].average[j].flags |= ID_STAR_USE_AVE;
-	  fit = fitAve;
-	  break;
-	case FIT_PM_ONLY:
-	  catalog[i].average[j].flags |= ID_STAR_USE_PM;
-	  fit = fitPM;
-	  break;
-	case FIT_PM_AND_PAR:
-	  catalog[i].average[j].flags |= ID_STAR_USE_PAR;
-	  fit = fitPAR;
-	  break;
-      }
-
-      if (XVERB) fprintf (stderr, "%f %f -> %f %f (%f,%f)\n",
+      case FIT_AVERAGE:
+	catalog[i].average[j].flags |= ID_STAR_USE_AVE;
+	fit = fitAve;
+	break;
+      case FIT_PM_ONLY:
+	catalog[i].average[j].flags |= ID_STAR_USE_PM;
+	fit = fitPM;
+	break;
+      case FIT_PM_AND_PAR:
+	catalog[i].average[j].flags |= ID_STAR_USE_PAR;
+	fit = fitPAR;
+	break;
+      }
+      if (XVERB) fprintf (stderr, "A%f %f -> %f %f (%f,%f) pm=(%f %f)\n",
 			  catalog[i].average[j].R, 
 			  catalog[i].average[j].D, 
 			  fit.Ro, fit.Do, 
 			  3600*(catalog[i].average[j].R - fit.Ro), 
-			  3600*(catalog[i].average[j].D - fit.Do));
+			  3600*(catalog[i].average[j].D - fit.Do),
+			  fit.uR,
+			  fit.uD);
 
       //make sure that the fit succeeded
-      status  = finite(fit.Ro);
+      status = TRUE;
+      status &= finite(fit.Ro);
       status &= finite(fit.Do);
       status &= finite(fit.dRo);
@@ -310,4 +313,13 @@
       catalog[i].average[j].Trange = (Trange * 86400 * 365.26);
       catalog[i].average[j].Npos = fit.Nfit;
+      if (XVERB) fprintf (stderr, "%f %f -> %f %f (%f,%f) pm=(%f %f)\n",
+                          catalog[i].average[j].R,
+                          catalog[i].average[j].D,
+                          fit.Ro, fit.Do,
+                          3600*(catalog[i].average[j].R - fit.Ro),
+                          3600*(catalog[i].average[j].D - fit.Do),
+                          catalog[i].average[j].uR,
+                          catalog[i].average[j].uD);
+
     }
 
Index: /trunk/Ohana/src/relastro/src/high_speed_objects.c
===================================================================
--- /trunk/Ohana/src/relastro/src/high_speed_objects.c	(revision 29937)
+++ /trunk/Ohana/src/relastro/src/high_speed_objects.c	(revision 29938)
@@ -13,13 +13,46 @@
   // XXX seems silly to require region here just to find the catalog bounds / center
 
-  off_t i, j, m, J, ni, nj, *N1, Nslow, Ninvalid, NgroupA, NgroupB, NgroupAbad, NgroupBbad;
-  int *slowMoving, *groupA, *groupB, status, foundA, foundB, Nmatch, valid;
+  off_t i, j, m, J, ni, nj, *N1, Nslow, Ninvalid, NgroupA, NgroupB, NgroupAbad, NgroupBbad,l,i1,Noff,Nmeasure,Naverage, NAVERAGE, NMEASURE;
+  int *slowMoving, *groupA, *groupB, status, foundA, foundB, Nmatch, valid,Nmatchmeas,Nepoch,nv[2],Nmatchmeasobj;
   double *X1, *Y1;
-  double dX, dY, dR, RADIUS2, yJ;
+  double dX, dY, dR, RADIUS2;
   Coords tcoords;
+  Catalog catalog1;
 
   int zcode, zNsec, ycode, yNsec, jcode, jNsec, hcode, hNsec, kcode, kNsec, USNO_R, USNO_N, Nsecfilt;
+  char filename[1024];
+  char outdir[]="/data/ipp022.0/ndeacon/hispeedzy";
+  Noff = strlen(CATDIR);
+  sprintf (filename, "%s/%s", outdir, &catalog[0].filename[Noff]);
+  dvo_catalog_init(&catalog1, TRUE); /*initialise new catalogue*/
+  catalog1.filename = strcreate(filename);
+
+  SIGMA_LIM = 0.0;
 
   Nsecfilt = GetPhotcodeNsecfilt();
+  Naverage=catalog[0].Naverage;
+  Nmeasure=catalog[0].Nmeasure;
+  // ALLOCATE (catalog1.average, Average,Naverage);
+  // ALLOCATE (catalog1.measure, Measure,Nmeasure);
+  // ALLOCATE(catalog1.secfilt, SecFilt,catalog[0].Naverage*Nsecfilt);
+  catalog1.filename = filename; // based on the input name, need to keep everything below the catdir portion
+  catalog1.Nsecfilt  = Nsecfilt;
+  // always load all of the data (if any exists)
+  catalog1.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF;
+  
+  catalog1.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+  catalog1.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+
+  if (!dvo_catalog_open (&catalog1, region, VERBOSE,"w")) {
+    fprintf (stderr, "ERROR: failure to open catalog file %s\n",
+	     filename);
+    exit (2);
+  }
+
+  NAVERAGE = 1000;
+  NMEASURE = 10000;
+  REALLOCATE (catalog1.average, Average, NAVERAGE);
+  REALLOCATE (catalog1.measure, Measure, NMEASURE);
+  REALLOCATE(catalog1.secfilt, SecFilt, NAVERAGE*Nsecfilt);
 
   ycode = GetPhotcodeCodebyName("y");
@@ -67,5 +100,5 @@
   NgroupBbad = 0;
 
-  fprintf (stdout, "#      RA_A  DEC_A              RA_B  DEC_B      :     pmx     pmy      dR  :    z      dz       y      dy       J      dJ       H      dH       K      dK\n");
+  fprintf (stdout, "#      RA_A  DEC_A              RA_B  DEC_B      :     pmx     pmy      dR  dt:    z      dz       y      dy       J      dJ       H      dH       K      dK\n");
   //................270.2346670  20.2471540  270.2170434  20.2717396 :  -59.11   88.78   106.66 :   0.000  0.000   19.582  0.112   16.041  0.110   15.388  0.098   14.858  0.001
 
@@ -88,4 +121,9 @@
     foundA = FALSE;
     for (j = 0; !foundA && (j < catalog[0].average[i].Nmeasure); j++, m++) {
+      if((catalog[0].average[i].R>204.1923)&&(catalog[0].average[i].R<204.1924)&&(catalog[0].average[i].D>11.376)&&(catalog[0].average[i].D<11.377))
+	{
+	  printf("Hello");
+	}
+
       if (MeasMatchesPhotcode(&catalog[0].measure[m], photcodesGroupA, NphotcodesGroupA)) {
 	foundA = TRUE;
@@ -97,4 +135,10 @@
     foundB = FALSE;
     for (j = 0; !foundB && (j < catalog[0].average[i].Nmeasure); j++, m++) {
+                                                   
+      if((catalog[0].average[i].R>204.192)&&(catalog[0].average[i].R<204.1925)&&(catalog[0].average[i].D>11.376)&&(catalog[0].average[i].D<11.377))
+        {
+          printf("Hello");
+        }
+
       if (MeasMatchesPhotcode(&catalog[0].measure[m], photcodesGroupB, NphotcodesGroupB)) {
 	foundB = TRUE;
@@ -114,13 +158,19 @@
     if (foundA && !foundB) {
       // average-based tests:
+                                                   
+      if((catalog[0].average[i].R>204.1923)&&(catalog[0].average[i].R<204.1924)&&(catalog[0].average[i].D>11.376)&&(catalog[0].average[i].D<11.377))
+	{
+          printf("Hello");
+        }
+
       valid = TRUE;
-      valid &= ((catalog[0].average[i].flags & 0x40000) == 0);
-      valid &= ((catalog[0].average[i].flags & 0x80000)  > 0);
-      valid &= (catalog[0].secfilt[i*Nsecfilt + yNsec].M < 1.0);
-      valid &= (catalog[0].secfilt[i*Nsecfilt + zNsec].M < 1.0);
+      valid &= ((catalog[0].average[i].flags & 0x02000000) == 0);
+      valid &= ((catalog[0].average[i].flags & 0x08000000)  > 0);
+      valid &= ((catalog[0].secfilt[i*Nsecfilt + yNsec].M < 1.0)||(isnan(catalog[0].secfilt[i*Nsecfilt + yNsec].M)));
+      valid &= (((catalog[0].secfilt[i*Nsecfilt + zNsec].M < 1.0))||(isnan(catalog[0].secfilt[i*Nsecfilt + zNsec].M)));
       // XXX color restrictions are applied in the pair-wise matching below
       
       // measure-based tests:
-      m = catalog[0].average[i].measureOffset;
+      /*      m = catalog[0].average[i].measureOffset;
       for (j = 0; valid && (j < catalog[0].average[i].Nmeasure); j++, m++) {
 	if (catalog[0].measure[m].photcode == USNO_R) {
@@ -130,5 +180,5 @@
 	  valid &= ((catalog[0].measure[m].M > 18.0) || isnan(catalog[0].measure[m].M));
 	}
-      }
+	}*/
 
       // ((objflags & 524288) == 524288) && 
@@ -154,19 +204,25 @@
 
       // average-based tests:
+      if((catalog[0].average[i].R>204.192)&&(catalog[0].average[i].R<204.193)&&(catalog[0].average[i].D>11.372)&&(catalog[0].average[i].D<11.373))
+	{
+	  printf("Hello");
+	}
       valid = TRUE;
-      valid &= ((catalog[0].average[i].flags & 0x10000) == 0);
-      valid &= ((catalog[0].average[i].flags & 0x20000)  > 0);
-      valid &= (catalog[0].secfilt[i*Nsecfilt + jNsec].M < 1.0);
+      valid &= ((catalog[0].average[i].flags & 0x01000000) == 0);
+
+      valid &= ((catalog[0].average[i].flags & 0x04000000)  > 0);
+
+      valid &= ((catalog[0].secfilt[i*Nsecfilt + jNsec].M < 1.0)||(isnan(catalog[0].secfilt[i*Nsecfilt + jNsec].M)));
       valid &= (catalog[0].secfilt[i*Nsecfilt + yNsec].Nused > 1);
       valid &= (catalog[0].secfilt[i*Nsecfilt + yNsec].dM < 0.2);
 
-      if ((catalog[0].secfilt[i*Nsecfilt + zNsec].M < 1.0) || (catalog[0].secfilt[i*Nsecfilt + zNsec].Nused == 1)) {
+      /*if ((catalog[0].secfilt[i*Nsecfilt + zNsec].M < 1.0) || (catalog[0].secfilt[i*Nsecfilt + zNsec].Nused == 1)) {
 	valid &= TRUE;
       } else {
 	valid &= ((catalog[0].secfilt[i*Nsecfilt + zNsec].M - catalog[0].secfilt[i*Nsecfilt + yNsec].M) > 0.2);
-      }
+	}*/
 
       // measure-based tests:
-      m = catalog[0].average[i].measureOffset;
+      /*m = catalog[0].average[i].measureOffset;
       for (j = 0; valid && (j < catalog[0].average[i].Nmeasure); j++, m++) {
 	if (catalog[0].measure[m].photcode == USNO_R) {
@@ -176,5 +232,5 @@
 	  valid &= ((catalog[0].measure[m].M > 18.5) || isnan(catalog[0].measure[m].M));
 	}
-      }
+	}*/
 
       // (abs(glat)>15.0) && 
@@ -236,4 +292,5 @@
 
   Nmatch = 0;
+  Nmatchmeas = 0;
   RADIUS2 = SQ(RADIUS);
 
@@ -243,5 +300,4 @@
     ni = N1[i];
     nj = N1[j];
-
     // if (ni == 131030) {
     //   fprintf (stderr, "got 2mass\n");
@@ -268,5 +324,5 @@
 
     // within match range; look for valid matches
-    for (J = j; (dX > -1.02*RADIUS) && (J < catalog[0].Naverage); J++) {
+    for (J = j; (dX > -1.02*RADIUS) && (J < catalog[0].Naverage); J++) {     
       if (J == i) continue;  // avoid auto-matches
 
@@ -281,18 +337,53 @@
 
       // y_groupA - J_groupB 
-      yJ = catalog[0].secfilt[nj*Nsecfilt + yNsec].M - catalog[0].secfilt[ni*Nsecfilt + jNsec].M;
+
+      /*      yJ = catalog[0].secfilt[nj*Nsecfilt + yNsec].M - catalog[0].secfilt[ni*Nsecfilt + jNsec].M;
       if (yJ < 1.25) continue;
-      if (yJ > 5.0) continue;
+      if (yJ > 5.0) continue;*/
 
       /*** a match is found (just print it for the moment) ***/
+      /*Define a vector of matched indices and set averages in new catalogue*/
+      Nepoch=2;
+      FIT_MODE = FIT_PM_ONLY;
+/*nv=(int *)malloc(Nepoch*sizeof(int));*/
+      nv[0]=ni; /*THESE SHOULD BE CHANGED FOR MULTIPLE EPOCHS AS SHOULD nv*/
+      nv[1]=nj;
+
+      catalog1.average[Nmatch]=catalog[0].average[nv[0]];
+      /*Loop over index values and set measurements in new catalogue*/
+      Nmatchmeasobj=0;
+      catalog1.average[Nmatch].measureOffset=Nmatchmeas;
+      for(l=0;l<Nepoch;l++) {
+	  m = catalog[0].average[nv[l]].measureOffset;
+	  for(i1=0;i1<catalog[0].average[nv[l]].Nmeasure;i1++) {
+	      catalog1.measure[Nmatchmeas]=catalog[0].measure[m+i1];
+	      /*Set offset RA and Dec wrt correct average value*/
+	      catalog1.measure[Nmatchmeas].dR=catalog[0].measure[m+i1].dR+3600.0*(catalog[0].average[nv[0]].R-catalog[0].average[nv[l]].R);
+	      catalog1.measure[Nmatchmeas].dD=catalog[0].measure[m+i1].dD+3600.0*(catalog[0].average[nv[0]].D-catalog[0].average[nv[l]].D);
+	      catalog1.measure[Nmatchmeas].averef = Nmatch;
+	      Nmatchmeasobj++;
+	      Nmatchmeas++;
+
+	      if (Nmatchmeas == NMEASURE - 1) {
+		NMEASURE += 10000;
+		REALLOCATE (catalog1.measure, Measure, NMEASURE);
+	      }
+	  }
+      }
+      catalog1.average[Nmatch].Nmeasure=Nmatchmeasobj;
       Nmatch ++;
 
+      if (Nmatch == NAVERAGE - 1) {
+	NAVERAGE += 1000;
+	REALLOCATE (catalog1.average, Average, NAVERAGE);
+	REALLOCATE(catalog1.secfilt, SecFilt, NAVERAGE*Nsecfilt);
+      }
       // for the moment, just write the matches to stdout:
-      float pmx = -dX; // proper-motion displacement in ra  direction (B - A) [arcsec]
-      float pmy = -dY; // proper-motion displacement in dec direction (B - A) [arcsec]
-      fprintf (stdout, "%11.7f %11.7f  %11.7f %11.7f : %7.2f %7.2f  %7.2f : %7.3f %6.3f  %7.3f %6.3f  %7.3f %6.3f  %7.3f %6.3f  %7.3f %6.3f\n", 
+      //float pmx = -dX; // proper-motion displacement in ra  direction (B - A) [arcsec]
+      //float pmy = -dY; // proper-motion displacement in dec direction (B - A) [arcsec]
+      /*           fprintf (stdout, "%11.7f %11.7f  %11.7f %11.7f : %7.2f %7.2f  %7.2f: %7.3f %6.3f  %7.3f %6.3f  %7.3f %6.3f  %7.3f %6.3f  %7.3f %6.3f\n", 
 	       catalog[0].average[ni].R, catalog[0].average[ni].D,
 	       catalog[0].average[nj].R, catalog[0].average[nj].D,
-	       pmx, pmy, sqrt(dR),
+	       dX, dY, sqrt(dR),
 	       catalog[0].secfilt[nj*Nsecfilt + zNsec].M,
 	       catalog[0].secfilt[nj*Nsecfilt + zNsec].dM,
@@ -304,10 +395,19 @@
 	       catalog[0].secfilt[ni*Nsecfilt + hNsec].dM,
 	       catalog[0].secfilt[ni*Nsecfilt + kNsec].M,
-	       catalog[0].secfilt[ni*Nsecfilt + kNsec].dM);
+	       catalog[0].secfilt[ni*Nsecfilt + kNsec].dM);*/
     }
     i++;
   }
+  catalog1.Naverage=Nmatch;
+  catalog1.Nmeasure=Nmatchmeas;
+  catalog1.Nsecfilt=Nsecfilt;
+  catalog1.Nsecf_mem=Nmatch*Nsecfilt;
+
+  UpdateObjects(&catalog1, 1);
+
   fprintf (stderr, "found %d matches\n", Nmatch);
-  
+  dvo_catalog_save (&catalog1, VERBOSE);
+  dvo_catalog_unlock (&catalog1);
+  dvo_catalog_free (&catalog1);
   free (slowMoving);
   free (groupA);
Index: /trunk/Ohana/src/relphot/doc/config.txt
===================================================================
--- /trunk/Ohana/src/relphot/doc/config.txt	(revision 29938)
+++ /trunk/Ohana/src/relphot/doc/config.txt	(revision 29938)
@@ -0,0 +1,28 @@
+
+for choosing the bright (tied-down) stars, these are used:
+
+MAG_LIM                 &MAG_LIM        -- faintest stars used
+SIGMA_LIM               &SIGMA_LIM      -- lowest S/N used (sigma = 1/(S/N))
+STAR_SCATTER            &STAR_SCATTER   -- max scatter for a star to be include in the fits 
+IMAGE_SCATTER           &IMAGE_SCATTER  -- max scatter for an image to be kept in the fit
+IMAGE_OFFSET            &IMAGE_OFFSET   -- max zero point offset of an image to be kept in the fit
+STAR_CHISQ              &STAR_CHISQ   -- max chisq for a star  to be include in the fits 
+STAR_TOOFEW             &STAR_TOOFEW   -- star must have more than this good measurements to be used
+IMAGE_TOOFEW            &IMAGE_TOOFEW   -- image must have more than this good measurements to be used
+IMAGE_GOOD_FRACTION     &IMAGE_GOOD_FRACTION   -- image must have more than this (good measurements/all meas) to be used
+
+
+GSCFILE                 GSCFILE   -- source for database sky table if missing
+CATDIR                  CATDIR   -- catdir
+CATMODE                CATMODE   -- output format for DVO tables (should not be used by relphot?)
+CATFORMAT              CATFORMAT   -- output format for DVO tables (should not be used by relphot?)
+PHOTCODE_FILE          MasterPhotcodeFile   -- source for photcode table if missing
+SKY_DEPTH              SKY_DEPTH)) { SKY_DEPTH = 2; } -- output sky partition depth (default is 2, should not be used by relphot)
+ZERO_PT                &ZERO_POINT   -- reference internal zero point, should not be needed (defined by DB)
+
+CAMERA_CONFIG          CameraConfig   -- source of camera description, used for generating correction grid
+MOSAICNAME             MOSAICNAME   -- name to define relationship between chip and camera 
+GRID_TOOFEW            &GRID_TOOFEW   -- correction grid cell must have more than this stars (or is nan)
+RELPHOT_GRID_X         &RELPHOT_GRID_X  -- number of grid cells per chip (x)
+RELPHOT_GRID_Y         &RELPHOT_GRID_Y  -- number of grid cells per chip (y)
+RELPHOT_GRID_BINNING   &RELPHOT_GRID_BINNING  -- ???
Index: /trunk/Ohana/src/tools/Makefile
===================================================================
--- /trunk/Ohana/src/tools/Makefile	(revision 29937)
+++ /trunk/Ohana/src/tools/Makefile	(revision 29938)
@@ -17,5 +17,5 @@
 PROGRAMS = gconfig fhead ftable fields list_astro glockfile \
 radec mktemp precess csystem fits_insert \
-medianfilter mefhead ckfits
+medianfilter mefhead ckfits roc
 
 all tools: $(PROGRAMS)
Index: /trunk/Ohana/src/tools/src/roc.c
===================================================================
--- /trunk/Ohana/src/tools/src/roc.c	(revision 29938)
+++ /trunk/Ohana/src/tools/src/roc.c	(revision 29938)
@@ -0,0 +1,353 @@
+# include <ohana.h>
+# include <gfitsio.h>
+# include <sys/types.h>
+# include <regex.h>
+
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
+# define ROC_HEADER_SIZE  0x1000
+# define ROC_HEADER_BLOCK 0x1000
+# define ROC_BLOCKSIZE    0x8000
+
+int roc_create (int argc, char **argv);
+int roc_repair (int argc, char **argv);
+int roc_insert (int argc, char **argv);
+int roc_delete (int argc, char **argv);
+int usage (void);
+int print_block (char *header, int *header_size, int *Noff, char *format,...);
+
+int main (int argc, char **argv) {
+
+  if (get_argument (argc, argv, "-h")) usage();
+  if (get_argument (argc, argv, "-help")) usage();
+  if (get_argument (argc, argv, "--help")) usage();
+  if (argc < 3) usage();
+
+  if (!strcasecmp(argv[1], "-create")) roc_create (argc, argv);
+  if (!strcasecmp(argv[1], "-repair")) roc_repair (argc, argv);
+  if (!strcasecmp(argv[1], "-insert")) roc_insert (argc, argv);
+  if (!strcasecmp(argv[1], "-delete")) roc_delete (argc, argv);
+
+  usage();
+  exit (1);
+}
+
+int roc_create (int argc, char **argv) {
+
+  int i, j, n, Ninput, result, Nblocks, header_size, size_off, Noff, Nbytes, Nread;
+  off_t maxSize, *sizes, *bytes_read;
+  char value;
+  char *header;
+  char **inputName, *base;
+  char **inputData;
+  char *outputData;
+  FILE **input, *target;
+  struct stat stats;
+
+  if (argc < 4) usage();
+
+  /* find the md5 sum for each input file (ADD LATER)
+   * find the size of the input files
+   * define the output file size = MAX(sizes)
+   * create the target file header
+   * open & read the input files, write the output
+   */
+
+  // the output file
+  char *targetName = argv[2];
+  Ninput = argc - 3;
+
+  // the input files
+  ALLOCATE (inputName, char *, Ninput);
+  for (i = 0; i < Ninput; i++) {
+    inputName[i] = strcreate (argv[i + 3]);
+  }
+
+  // check on the input files (validity & sizes)
+  maxSize = 0;
+  ALLOCATE (sizes, off_t, Ninput);
+  ALLOCATE (bytes_read, off_t, Ninput);
+  for (i = 0; i < Ninput; i++) {
+    result = stat (inputName[i], &stats);
+    if (result) {
+      perror("problem with input file");
+      exit (1);
+    }
+    if (!S_ISREG(stats.st_mode)) {
+      fprintf (stderr, "%s is not a regular file\n", inputName[i]);
+      exit (1);
+    }
+    sizes[i] = stats.st_size;
+    maxSize = MAX(maxSize, sizes[i]);
+    bytes_read[i] = 0;
+  }
+
+  ALLOCATE (input, FILE *, Ninput);
+  for (i = 0; i < Ninput; i++) {
+    input[i] = fopen(inputName[i], "r");
+    myAssert (input[i], "failed to open file");
+  }
+
+  Nblocks = maxSize / ROC_BLOCKSIZE;
+  if (maxSize % ROC_BLOCKSIZE) {
+    Nblocks = 1 + (int) (maxSize / ROC_BLOCKSIZE);
+  }
+
+  // create the output header
+  header_size = ROC_HEADER_SIZE;
+  ALLOCATE (header, char, header_size);
+
+  Noff = 0;
+  print_block (header, &header_size, &Noff, "ROC Version %d\n", 0);
+
+  size_off = Noff;
+  print_block (header, &header_size, &Noff, "HEADER SIZE 0x%08x\n", header_size); // this is a dummy: re-write below
+  print_block (header, &header_size, &Noff, "N_FILES %d\n", Ninput);
+  print_block (header, &header_size, &Noff, "N_BLOCKS %d\n", Nblocks);
+
+  // write the header information
+  for (i = 0; i < Ninput; i++) {
+    print_block (header, &header_size, &Noff, "FILE_%03d : %s\n", i, inputName[i]);
+    
+    base = filerootname(inputName[i]);
+    print_block (header, &header_size, &Noff, "BASE_%03d : %s\n", i, base);
+    print_block (header, &header_size, &Noff, "SIZE_%03d : %d\n", i, sizes[i]);
+    print_block (header, &header_size, &Noff, "MD5_%03d -- Not Yet Implemented\n", i);
+  }
+  print_block (header, &header_size, &size_off, "HEADER SIZE 0x%08x", header_size);
+  header[size_off] = '\n'; // kind of a hack: print_block writes a NULL char at the end of the string; replace with <RETURN>
+
+  target = fopen(targetName, "w");
+  myAssert (target, "failed to open output");
+
+  fwrite (header, 1, header_size, target);
+
+  ALLOCATE (inputData, char *, Ninput);
+  ALLOCATE (outputData, char, ROC_BLOCKSIZE);
+  for (i = 0; i < Ninput; i++) {
+    ALLOCATE (inputData[i], char, ROC_BLOCKSIZE);
+  }
+
+  for (n = 0; n < Nblocks; n++) {
+    for (i = 0; i < Ninput; i++) {
+      Nread = MIN (ROC_BLOCKSIZE, sizes[i] - bytes_read[i]);
+      Nbytes = fread (inputData[i], 1, Nread, input[i]);
+      myAssert (Nbytes == Nread, "failed to read data");
+      if (Nread < ROC_BLOCKSIZE) {
+	// if we have reached the end of the file, fill in the rest with NULLs
+	memset (&inputData[i][Nread], 0, ROC_BLOCKSIZE - Nread);
+      }
+      bytes_read[i] += Nread;
+    }
+    
+    for (j = 0; j < ROC_BLOCKSIZE; j++) {
+      value = 0;
+      for (i = 0; i < Ninput; i++) {
+	value = value ^ inputData[i][j];
+      }
+      outputData[j] = value;
+    }
+
+    fwrite (outputData, 1, ROC_BLOCKSIZE, target);
+  }
+
+  exit (0);
+}
+
+int roc_repair (int argc, char **argv) {
+
+  int i, j, n, Ninput, Nblocks, header_size, Nbytes, Nread, Nfile, Nwrite;
+  off_t *sizes, *bytes_read;
+  char value;
+  char *header, *line, *ptr;
+  char **inputName, *targetName, *outputName;
+  char **inputData;
+  char *targetData;
+  char *outputData;
+  FILE **input, *target, *output;
+
+  if (argc < 4) usage();
+
+  /* find the md5 sum for each input file (ADD LATER)
+   * find the size of the input files
+   * define the output file size = MAX(sizes)
+   * create the target file header
+   * open & read the input files, write the output
+   */
+
+  // the output file
+  targetName = argv[2];
+  Nfile = atoi(argv[3]);
+  outputName = argv[4];
+
+  if (Nfile < 0) {
+    fprintf (stderr, "invalid file number %d\n", Nfile);
+    exit (3);
+  }
+
+  target = fopen(targetName, "r");
+  myAssert (target, "failed to open output");
+  
+  ALLOCATE (line, char, 1024);
+
+  scan_line (target, line);
+  myAssert (!strncmp(line, "ROC Version 0", strlen("ROC Verion 0")), "invalid ROC file or format");
+
+  scan_line (target, line);
+  sscanf (line, "HEADER SIZE %x", &header_size);
+
+  ALLOCATE (header, char, header_size);
+
+  // read the full header
+  fseeko (target, 0, SEEK_SET);
+  fread (header, 1, header_size, target);
+
+  ptr = header;
+  ptr = strchr (ptr, '\n'); ptr ++; // ROC Version
+  ptr = strchr (ptr, '\n'); ptr ++; // HEADER SIZE
+  
+  sscanf (ptr, "N_FILES %d", &Ninput); ptr = strchr (ptr, '\n'); ptr ++; 
+  sscanf (ptr, "N_BLOCKS %d", &Nblocks); ptr = strchr (ptr, '\n'); ptr ++; 
+
+  if (Nfile >= Ninput) {
+    fprintf (stderr, "file number %d not in ROC\n", Nfile);
+    exit (3);
+  }
+
+  // the input files
+  ALLOCATE (inputName, char *, Ninput);
+  ALLOCATE (sizes, off_t, Ninput);
+  ALLOCATE (bytes_read, off_t, Ninput);
+  for (i = 0; i < Ninput; i++) {
+    ALLOCATE (inputName[i], char, 1024);
+    sscanf (ptr, "%*s : %s", inputName[i]); ptr = strchr (ptr, '\n'); ptr ++; 
+    ptr = strchr (ptr, '\n'); ptr ++; // BASE
+    sscanf (ptr, "%*s : "OFF_T_FMT, &sizes[i]); ptr = strchr (ptr, '\n'); ptr ++; 
+    ptr = strchr (ptr, '\n'); ptr ++; // MD5
+    bytes_read[i] = 0;
+  }
+
+  ALLOCATE (input, FILE *, Ninput);
+  for (i = 0; i < Ninput; i++) {
+    if (i == Nfile) continue;
+    input[i] = fopen(inputName[i], "r");
+    myAssert (input[i], "failed to open file");
+  }
+
+  output = fopen (outputName, "w");
+  myAssert (output, "failed to open output");
+
+  ALLOCATE (inputData, char *, Ninput);
+  ALLOCATE (targetData, char, ROC_BLOCKSIZE);
+  ALLOCATE (outputData, char, ROC_BLOCKSIZE);
+  for (i = 0; i < Ninput; i++) {
+    if (i == Nfile) continue;
+    ALLOCATE (inputData[i], char, ROC_BLOCKSIZE);
+  }
+
+  for (n = 0; n < Nblocks; n++) {
+    for (i = 0; i < Ninput; i++) {
+      if (i == Nfile) continue;
+      Nread = MIN (ROC_BLOCKSIZE, sizes[i] - bytes_read[i]);
+      Nbytes = fread (inputData[i], 1, Nread, input[i]);
+      myAssert (Nbytes == Nread, "failed to read data");
+      if (Nread < ROC_BLOCKSIZE) {
+	// if we have reached the end of the file, fill in the rest with NULLs
+	memset (&inputData[i][Nread], 0, ROC_BLOCKSIZE - Nread);
+      }
+      bytes_read[i] += Nread;
+    }
+    fread (targetData, 1, ROC_BLOCKSIZE, target);
+    
+    for (j = 0; j < ROC_BLOCKSIZE; j++) {
+      value = targetData[j];
+      for (i = 0; i < Ninput; i++) {
+	if (i == Nfile) continue;
+	value = value ^ inputData[i][j];
+      }
+      outputData[j] = value;
+    }
+
+    Nwrite = MIN (ROC_BLOCKSIZE, sizes[Nfile] - bytes_read[Nfile]);
+    if (Nwrite == 0) break;
+    Nbytes = fwrite (outputData, 1, Nwrite, output);
+    myAssert (Nbytes == Nwrite, "failed to write");
+    bytes_read[Nfile] += Nbytes;
+  }
+
+  for (i = 0; i < Ninput; i++) {
+    if (i == Nfile) continue;
+    fclose(input[i]);
+  }
+  fclose (output);
+  fclose (target);
+
+  exit (0);
+}
+
+int roc_insert (int argc, char **argv) {
+
+  exit (0);
+}
+
+int roc_delete (int argc, char **argv) {
+
+  exit (0);
+}
+
+/* roc file description:
+
+   header block + data block
+
+   header min length = ROC_HEADER_SIZE (padded with NULLs)
+
+   ROC Version %d
+   NFILES %d
+   FILE_NNN : full path
+   BASE_NNN : basename
+   SIZE_NNN : file size
+   MD5_NNN  : MD5 checksum of file
+   END
+
+   MAX(NNN) = 32?
+   32*1024
+*/
+
+int usage () {
+  fprintf (stderr, "USAGE: roc [mode]\n");
+  fprintf (stderr, " -create (target) (input) (input) (input) ... [options]\n");
+  fprintf (stderr, " -repair (target) (file #) (output\n");
+  fprintf (stderr, " -insert (target) (input)\n");
+  fprintf (stderr, " -delete (target) (file)\n");
+  exit (2);
+}
+
+int print_block (char *header, int *header_size, int *Noff, char *format,...) {
+
+  char tmp;
+  int Nbyte, Nleft;
+  va_list argp;  
+
+  va_start (argp, format);
+  Nbyte = vsnprintf (&tmp, 0, format, argp);
+  va_end (argp);
+
+  Nleft = *header_size - *Noff;
+
+  if (Nbyte >= Nleft) {
+    *header_size += ROC_HEADER_BLOCK + Nbyte;
+    REALLOCATE (header, char, *header_size);
+  }
+  
+  va_start (argp, format);
+  Nbyte = vsnprintf (&header[*Noff], Nleft, format, argp);
+  va_end (argp);
+
+  if (Nbyte >= Nleft) {
+    return (FALSE);
+  }
+
+  *Noff += Nbyte;
+  return (Nbyte);
+}
+
