Index: /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/Makefile
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/Makefile	(revision 29784)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/Makefile	(revision 29785)
@@ -87,5 +87,12 @@
 $(BIN)/dvorepair.$(ARCH) : $(DVOREPAIR)
 
-INSTALL = dvomerge dvoconvert dvosecfilt 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: /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvorepair.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvorepair.c	(revision 29784)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvorepair.c	(revision 29785)
@@ -6,4 +6,5 @@
   
   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);
Index: /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvorepairFixTables.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvorepairFixTables.c	(revision 29785)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvorepairFixTables.c	(revision 29785)
@@ -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: /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvoverify.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvoverify.c	(revision 29784)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/dvomerge/src/dvoverify.c	(revision 29785)
@@ -8,170 +8,204 @@
 */
 
+int VerifyTableFile (char *filename);
+
 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;
+
+    sprintf (filename, "%s/%s.cpm", 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 ++;
+    }
+
+    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:
+	// 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;
+  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 (Nbytes > fileStats.st_size) {
+      fprintf (stderr, "file is short ("OFF_T_FMT" vs "OFF_T_FMT")\n", Nbytes, fileStats.st_size);
+      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;
+      }
+    }
+  }
+  // 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);
+    
+  
