Index: /trunk/Ohana/src/relastro/Makefile
===================================================================
--- /trunk/Ohana/src/relastro/Makefile	(revision 39470)
+++ /trunk/Ohana/src/relastro/Makefile	(revision 39471)
@@ -25,4 +25,8 @@
 
 RELASTRO = \
+$(SRC)/RepairWarps.$(ARCH).o \
+$(SRC)/RepairWarpMeasures.$(ARCH).o \
+$(SRC)/WarpImageMaps.$(ARCH).o \
+$(SRC)/myIndex.$(ARCH).o \
 $(SRC)/ConfigInit.$(ARCH).o	     \
 $(SRC)/FitChip.$(ARCH).o             \
@@ -105,4 +109,8 @@
 
 RELASTRO_CLIENT = \
+$(SRC)/RepairWarps.$(ARCH).o \
+$(SRC)/RepairWarpMeasures.$(ARCH).o \
+$(SRC)/WarpImageMaps.$(ARCH).o \
+$(SRC)/myIndex.$(ARCH).o \
 $(SRC)/ConfigInit.$(ARCH).o	     \
 $(SRC)/FitSimple.$(ARCH).o           \
Index: /trunk/Ohana/src/relastro/include/relastro.h
===================================================================
--- /trunk/Ohana/src/relastro/include/relastro.h	(revision 39470)
+++ /trunk/Ohana/src/relastro/include/relastro.h	(revision 39471)
@@ -10,4 +10,8 @@
 # define IDX_T int 
 
+# ifndef MAX_INT
+# define MAX_INT 2147483647
+# endif
+
 # define LOGRTIME(MSG,...) {				\
     gettimeofday (&stopTimer, (void *) NULL);		\
@@ -25,5 +29,5 @@
 typedef enum {FIT_NONE, FIT_AVERAGE, FIT_PM_ONLY, FIT_PAR_ONLY, FIT_PM_AND_PAR} FitMode;
 
-typedef enum {OP_NONE, OP_IMAGES, OP_HIGH_SPEED, OP_MERGE_SOURCE, OP_UPDATE_OBJECTS, OP_UPDATE_OFFSETS, OP_LOAD_OBJECTS, OP_HPM, OP_PARALLEL_REGIONS, OP_PARALLEL_IMAGES} RelastroOp;
+typedef enum {OP_NONE, OP_IMAGES, OP_HIGH_SPEED, OP_MERGE_SOURCE, OP_UPDATE_OBJECTS, OP_UPDATE_OFFSETS, OP_LOAD_OBJECTS, OP_HPM, OP_PARALLEL_REGIONS, OP_PARALLEL_IMAGES, OP_REPAIR_WARPS} RelastroOp;
 
 typedef enum {TARGET_NONE, TARGET_SIMPLE, TARGET_CHIPS, TARGET_MOSAICS} FitTarget;
@@ -45,4 +49,12 @@
   MARK_BIG_OFFSET    = 0x0010,
 } MeasurementMask;
+
+typedef struct {
+  int minID;
+  int maxID;
+  int *index;
+  int Nindex;
+  int NINDEX;
+} myIndexType;
 
 typedef struct {
@@ -787,2 +799,16 @@
 double dpsi_cauchy (double x);
 void my_memdump (char *message);
+
+int RepairWarps (SkyList *skylist, int hostID, char *hostpath);
+int RepairWarpMeasures (Catalog *catalog);
+
+int FindWarpGroups (void);
+void FreeWarpGroups (void);
+int GetWarpID (Image *image, int obstime, unsigned short photcode, double Rave, double Dave, float X, float Y);
+
+myIndexType *myIndexInit ();
+int myIndexFree (myIndexType *myIndex);
+int myIndexUpdateLimits (myIndexType *myIndex, int value);
+int myIndexSetRange (myIndexType *myIndex);
+int myIndexSetEntry (myIndexType *myIndex, int value, int entry);
+int myIndexGetEntry (myIndexType *myIndex, int value);
Index: /trunk/Ohana/src/relastro/src/RepairWarpMeasures.c
===================================================================
--- /trunk/Ohana/src/relastro/src/RepairWarpMeasures.c	(revision 39470)
+++ /trunk/Ohana/src/relastro/src/RepairWarpMeasures.c	(revision 39471)
@@ -2,81 +2,78 @@
 
 // XXX need to load_images first and generate some lookup tables
-int RepairWarpMeasures (Catalog *catalog, int Ncatalog) {
+int RepairWarpMeasures (Catalog *catalog) {
 
-  off_t i, j, Nimage;
-  Image *image;
-  double DPOS_MAX_ASEC;
+  off_t Nimage;
+  Image *image = getimages (&Nimage, NULL);
 
-  image = getimages (&Nimage, NULL);
+  myAssert (!catalog->Nmeasure || catalog->measure, "programming error");
 
-  for (i = 0; i < Ncatalog; i++) {
-    myAssert (!catalog[i].Nmeasure || catalog[i].measureT, "programming error");
+  for (off_t j = 0; j < catalog->Nmeasure; j++) {
+    Measure *measure = &catalog->measure[j];
 
-    for (j = 0; j < catalog[i].Nmeasure; j++) {
-      Measure *measure = &catalog[i].measure[j];
-
-      // we are only going to repair warp detections
-      if (!isGPC1warp(measure->photcode)) 
-	// XXX fix extID
-	continue;
+    // we are only going to repair warp detections
+    if (!isGPC1warp(measure->photcode))  {
+      // XXX fix extID
+      continue;
     }
 
-      // get the associated average value:
-      int averef = measure->averef;
-      Average *average = &catalog[i].average[averef];
+    // get the associated average value:
+    int averef = measure->averef;
+    Average *average = &catalog->average[averef];
 
-      // double-check for consistency
-      myAssert (average->objID == measure->objID, "mismatch");
+    // double-check for consistency
+    myAssert (average->objID == measure->objID, "mismatch");
 
-      // check if this detection is far from it correct location
-      double Rwrp = measure->R;
-      double Dwrp = measure->D;
+    // check if this detection is far from its correct location
+    double Rwrp = measure->R;
+    double Dwrp = measure->D;
 
-      double Rave = average->R;
-      double Dave = average->D;
+    double Rave = average->R;
+    double Dave = average->D;
       
-      float csdec = cos(Dave * RAD_DEG);
+    float csdec = cos(Dave * RAD_DEG);
 
-      // find the ra,dec displacement in arcsec:
-      double dR = 3600.0*(Rwrp - Rave)*csdec;
-      double dD = 3600.0*(Dwrp - Dave);
+    // find the ra,dec displacement in arcsec:
+    double dR = 3600.0*(Rwrp - Rave)*csdec;
+    double dD = 3600.0*(Dwrp - Dave);
 
-      // skip detections which are within a small distance of the expected location
-      double dPos = hypot(dR,dD);
-      if (dPos < 2.0) continue;
+    // skip detections which are within a small distance of the expected location
+    double dPos = hypot(dR,dD);
+    if (dPos < 2.0) continue;
 
-      // find the corrected warp ID:
-      int warpID = getWarpGroup (measure->t, measure->photcode, Rstk, Dstk);
+    // warp coordinates to confirm warp
+    double X = measure->Xccd;
+    double Y = measure->Yccd;
 
-      // assert on coords being TAN?
-      Coords *imcoords = &image[warpID].coords;
+    // find the corrected warp ID:
+    int warpID = GetWarpID (image, measure->t, measure->photcode, Rave, Dave, X, Y);
+    myAssert (warpID >= 0, "error finding match");
 
-      double X = measure->Xccd;
-      double Y = measure->Yccd;
+    // assert on coords being TAN?
+    Coords *imcoords = &image[warpID].coords;
 
-      double Rnew, Dnew;
-      XY_to_RD (&Rnew, &Dnew, X, Y, imcoords);
+    double Rnew, Dnew;
+    XY_to_RD (&Rnew, &Dnew, X, Y, imcoords);
 
-      Rnew = ohana_normalize_angle_to_midpoint(Rnew, 180.0);
-      Dnew = ohana_normalize_angle_to_midpoint(Dnew,   0.0);
+    Rnew = ohana_normalize_angle_to_midpoint(Rnew, 180.0);
+    Dnew = ohana_normalize_angle_to_midpoint(Dnew,   0.0);
 
-      // find the ra,dec displacement in arcsec:
-      double dRnew = 3600.0*(Rnew - Rave)*csdec;
-      double dDnew = 3600.0*(Dnew - Dave);
+    // find the ra,dec displacement in arcsec:
+    double dRnew = 3600.0*(Rnew - Rave)*csdec;
+    double dDnew = 3600.0*(Dnew - Dave);
 
-      // skip detections which are within a small distance of the expected location
-      double dPos = hypot(dRnew,dDnew);
-      if (dPos > 2.0) {
-	// complain. abort?
-	fprintf (stderr, "measurement still far from average location\n");
-	abort();
-      }
+    // skip detections which are within a small distance of the expected location
+    dPos = hypot(dRnew,dDnew);
+    if (dPos > 2.0) {
+      // complain. abort?
+      fprintf (stderr, "measurement still far from average location\n");
+      abort();
+    }
 
-      measure->R = Rnew;
-      measure->D = Dnew;
-    }
+    measure->R = Rnew;
+    measure->D = Dnew;
+
+    // XXX fix extID
   }
   return (TRUE);
 }
-
-
Index: /trunk/Ohana/src/relastro/src/RepairWarps.c
===================================================================
--- /trunk/Ohana/src/relastro/src/RepairWarps.c	(revision 39471)
+++ /trunk/Ohana/src/relastro/src/RepairWarps.c	(revision 39471)
@@ -0,0 +1,152 @@
+# include "relastro.h"
+
+int RepairWarps_parallel (SkyList *sky);
+
+int RepairWarps (SkyList *skylist, int hostID, char *hostpath) {
+
+  // XXX need to decide how to determine PARALLEL mode...
+  if (PARALLEL && !hostID) {
+    RepairWarps_parallel (skylist);
+    return TRUE;
+  }
+
+  FindWarpGroups ();
+
+  // load data from each region file, only use bright stars
+  for (int i = 0; i < skylist[0].Nregions; i++) {
+
+    // does this host ID match the desired location for the table?
+    if (!HostTableTestHost(skylist[0].regions[i], hostID)) continue;
+
+    // define the catalog file name
+    char hostfile[1024];
+    snprintf (hostfile, 1024, "%s/%s.cpt", hostpath, skylist[0].regions[i]->name);
+
+    Catalog catalog;
+    dvo_catalog_init (&catalog, TRUE);
+    catalog.filename = hostID ? hostfile : skylist[0].filename[i];
+
+    // set up the basic catalog info
+    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  = DVO_LOAD_AVERAGE | DVO_LOAD_MEASURE | DVO_LOAD_MISSING | DVO_LOAD_SECFILT;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+
+    if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE2, "w")) {
+      fprintf (stderr, "ERROR: failure reading catalog %s\n", catalog.filename);
+      exit (1);
+    }
+    if (!catalog.Naverage_disk) {
+      if (VERBOSE2) fprintf (stderr, "no data in %s, skipping\n", catalog.filename);
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+
+    // update the detection coordinates using the new image parameters
+    RepairWarpMeasures (&catalog);
+
+    char history[128];
+    struct timeval now;
+    gettimeofday (&now, (void *) NULL);
+    char *moddate = ohana_sec_to_date (now.tv_sec);
+    snprintf (history, 128, "repair warp measure.imageID: %s", moddate);
+    gfits_modify_alt (&catalog.header, "HISTORY", "%S", 0, history); // adds a new entry
+    free (moddate);
+
+    if (!UPDATE) {
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+    
+    // write the updated detections to disk
+    save_catalogs (&catalog, 1);
+  }
+
+  FreeWarpGroups ();
+  
+  return (TRUE);
+}
+
+// launch the setphot_client jobs to the parallel hosts
+int RepairWarps_parallel (SkyList *sky) {
+
+
+  // load the list of hosts
+  HostTable *table = HostTableLoad (CATDIR, sky->hosts);
+  if (!table) {
+    fprintf (stderr, "ERROR: failure reading Host Table %s for database %s\n", sky->hosts, CATDIR);
+    exit (1);
+  }    
+
+  // launch the relastro_client jobs to the parallel hosts
+  for (int i = 0; i < table->Nhosts; i++) {
+
+    if (sky->Nregions < table->Nhosts) {
+      // do any of the regions want this host?
+      int wantThisHost = FALSE;
+      for (int j = 0; j < sky->Nregions; j++) {
+	if (HostTableTestHost (sky->regions[j], table->hosts[i].hostID)) {
+	  wantThisHost = TRUE;
+	  break;
+	}
+      }
+      if (!wantThisHost) continue;
+    }
+
+    // ensure that the paths are absolute path names
+    char *tmppath = abspath (table->hosts[i].pathname, DVO_MAX_PATH);
+    free (table->hosts[i].pathname);
+    table->hosts[i].pathname = tmppath;
+
+    // options / arguments that can affect relastro_client -repair-warps:
+    // VERBOSE, VERBOSE2, UPDATE
+
+    char *command = NULL;
+    strextend (&command, "relastro_client -repair-warps -hostID %d -D CATDIR %s -hostdir %s -region %f %f %f %f", 
+	      table->hosts[i].hostID, CATDIR, table->hosts[i].pathname, 
+	       UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax);
+
+    if (VERBOSE)       strextend (&command, "-v");
+    if (VERBOSE2)      strextend (&command, "-vv");
+    if (UPDATE)        strextend (&command, "-update");
+
+    fprintf (stderr, "command: %s\n", command);
+
+    if (PARALLEL_MANUAL) {
+      free (command);
+      continue;
+    }
+
+    if (PARALLEL_SERIAL) {
+      int status = system (command);
+      if (status) {
+	fprintf (stderr, "ERROR running relastro_client\n");
+	exit (2);
+      }
+    } else {
+      // launch the job on the remote machine (no handshake)
+      int errorInfo = 0;
+      int pid = rconnect ("ssh", table->hosts[i].hostname, command, table->hosts[i].stdio, &errorInfo, FALSE);
+      if (!pid) {
+	fprintf (stderr, "failure to start %s (error %d)\n", table->hosts[i].hostname, errorInfo);
+	exit (1);
+      }
+      table->hosts[i].pid = pid; // save for future reference
+    }
+    free (command);
+  }
+
+  if (PARALLEL_MANUAL) {
+    fprintf (stderr, "run the relastro_client commands above.  when these are done, hit return\n");
+    getchar();
+  }
+  if (!PARALLEL_MANUAL && !PARALLEL_SERIAL) {
+    HostTableWaitJobsGetIO (table, __FILE__, __LINE__, VERBOSE);
+  }
+
+  FreeHostTable (table);
+
+  return TRUE;
+}      
Index: /trunk/Ohana/src/relastro/src/WarpImageMaps.c
===================================================================
--- /trunk/Ohana/src/relastro/src/WarpImageMaps.c	(revision 39470)
+++ /trunk/Ohana/src/relastro/src/WarpImageMaps.c	(revision 39471)
@@ -1,4 +1,213 @@
-
-int GetWarpID () {
-
+# include "relastro.h"
+
+// we have a list of warp images, each with obstime & photcode.  I need to make a list of the obstime groups
+
+# define D_NOBSTIMES 10000
+
+typedef struct {
+  int obstime;
+  unsigned short photcode;
+  int Nwarps;
+  int NWARPS;
+  off_t *warps;
+  double *Rmin;
+  double *Rmax;
+  double *Dmin;
+  double *Dmax;
+} WarpGroup;
+
+WarpGroup *warpgroup = NULL;
+int Nwarpgroup = 0;
+int NWARPGROUP = 0;
+
+myIndexType *warpObstimeIndex = NULL;
+
+int FindWarpGroups (void) {
+
+  off_t Nimage;
+  Image *image = getimages (&Nimage, NULL);
+
+  int *obstimes = NULL;
+  int Nobstimes = 0;
+  int NOBSTIMES = D_NOBSTIMES;
+  ALLOCATE (obstimes, int, NOBSTIMES);
+
+  // first generate a list of obstime values
+  for (off_t i = 0; i < Nimage; i++) {
+    if (!isGPC1warp(image[i].photcode)) continue;
+
+    obstimes[Nobstimes] = image[i].tzero;
+    Nobstimes ++;
+
+    CHECK_REALLOCATE (obstimes, int, NOBSTIMES, Nobstimes, D_NOBSTIMES);
+  }
+
+  // sort the list:
+  isort (obstimes, Nobstimes);
+
+  // find the unique obstimes
+  int *uniqtimes = NULL;
+  int *uniqcount = NULL;
+  int Nuniqtimes = 0;
+  ALLOCATE (uniqtimes, int, Nobstimes);
+  ALLOCATE (uniqcount, int, Nobstimes);
+
+  // generate a uniq set of obstimes
+  for (int i = 0; i < Nobstimes; Nuniqtimes ++) {
+    uniqtimes[Nuniqtimes] = obstimes[i];
+    int Ndup = 0;
+    int lastValue = uniqtimes[Nuniqtimes];
+    while ((i < Nobstimes) && (obstimes[i] == lastValue)) {
+      i++;
+      Ndup ++;
+      uniqcount[Nuniqtimes] = Ndup;
+    }
+  }
+  REALLOCATE (uniqtimes, int, Nuniqtimes);
+  REALLOCATE (uniqcount, int, Nuniqtimes);
+  
+  // now I need to assign all warp images to a warpgroup
+  // I have 2 options to go from warp obstime to warpgroup:
+  // a) use a bisection lookup [may be slow]
+  // b) use an index on obstime [require ~5*3.14*1e7*4 bytes ~ 600MB for index]
+
+  // generate an index on obstime
+  warpObstimeIndex = myIndexInit ();
+  warpObstimeIndex->minID = obstimes[0];
+  warpObstimeIndex->maxID = obstimes[Nobstimes-1];
+
+  myIndexSetRange (warpObstimeIndex);
+
+  // generate the warp groups (create the index as we go)
+  Nwarpgroup = Nuniqtimes;
+  ALLOCATE (warpgroup, WarpGroup, Nwarpgroup);
+  
+  for (int i = 0; i < Nuniqtimes; i++) {
+    warpgroup[i].obstime = uniqtimes[i];
+    warpgroup[i].photcode = 0; // not yet set
+    warpgroup[i].Nwarps = 0;
+    warpgroup[i].NWARPS = 100;
+    ALLOCATE (warpgroup[i].warps, off_t, warpgroup[i].NWARPS);
+    myIndexSetEntry (warpObstimeIndex, uniqtimes[i], i);
+  }
+
+  // assign all warps to one of the warpgroups
+  for (off_t i = 0; i < Nimage; i++) {
+    if (!isGPC1warp(image[i].photcode)) continue;
+
+    int seq = myIndexGetEntry (warpObstimeIndex, image[i].tzero);
+    myAssert (warpgroup[seq].obstime == image[i].tzero, "oops");
+
+    if (!warpgroup[seq].photcode) {
+      warpgroup[seq].photcode = image[i].photcode;
+    } else {
+      myAssert (warpgroup[seq].photcode == image[i].photcode, "oops");
+    }
+
+    int N = warpgroup[seq].Nwarps;
+    warpgroup[seq].warps[N] = i;
+    warpgroup[seq].Nwarps ++;
+    
+    CHECK_REALLOCATE (warpgroup[seq].warps, off_t, warpgroup[seq].NWARPS, warpgroup[seq].Nwarps, 100);
+  }
+
+  // we now have the warps assigned to groups.  now we need to generate the grid data to find the warp assignments
+  for (int i = 0; i < Nwarpgroup; i++) {
+
+    myAssert (warpgroup[i].Nwarps == uniqcount[i], "failure");
+    ALLOCATE (warpgroup[i].Rmin, double, warpgroup[i].Nwarps);
+    ALLOCATE (warpgroup[i].Rmax, double, warpgroup[i].Nwarps);
+    ALLOCATE (warpgroup[i].Dmin, double, warpgroup[i].Nwarps);
+    ALLOCATE (warpgroup[i].Dmax, double, warpgroup[i].Nwarps);
+
+    for (int j = 0; j < warpgroup[i].Nwarps; j++) {
+
+      off_t N = warpgroup[i].warps[j];
+      
+      int Nx = image[N].NX;
+      int Ny = image[N].NY;
+
+      double R, D;
+      XY_to_RD (&R, &D, 0.0, 0.0, &image[N].coords);
+      warpgroup[i].Rmin[j] = R;
+      warpgroup[i].Rmax[j] = R;
+      warpgroup[i].Dmin[j] = D;
+      warpgroup[i].Dmax[j] = D;
+
+      // XXX need to worry about 0,360 boundary
+      XY_to_RD (&R, &D, Nx, 0.0, &image[N].coords);
+      warpgroup[i].Rmin[j] = MIN (warpgroup[i].Rmin[j], R);
+      warpgroup[i].Rmax[j] = MAX (warpgroup[i].Rmax[j], R);
+      warpgroup[i].Dmin[j] = MIN (warpgroup[i].Dmin[j], D);
+      warpgroup[i].Dmax[j] = MAX (warpgroup[i].Dmax[j], D);
+
+      // XXX need to worry about 0,360 boundary
+      XY_to_RD (&R, &D, 0.0, Ny, &image[N].coords);
+      warpgroup[i].Rmin[j] = MIN (warpgroup[i].Rmin[j], R);
+      warpgroup[i].Rmax[j] = MAX (warpgroup[i].Rmax[j], R);
+      warpgroup[i].Dmin[j] = MIN (warpgroup[i].Dmin[j], D);
+      warpgroup[i].Dmax[j] = MAX (warpgroup[i].Dmax[j], D);
+
+      // XXX need to worry about 0,360 boundary
+      XY_to_RD (&R, &D, Nx, Ny, &image[N].coords);
+      warpgroup[i].Rmin[j] = MIN (warpgroup[i].Rmin[j], R);
+      warpgroup[i].Rmax[j] = MAX (warpgroup[i].Rmax[j], R);
+      warpgroup[i].Dmin[j] = MIN (warpgroup[i].Dmin[j], D);
+      warpgroup[i].Dmax[j] = MAX (warpgroup[i].Dmax[j], D);
+      // bump Rmin,Rmax,Dmin,Dmax by some padding?
+    }
+  }
+
+  free (uniqtimes); 
+  free (uniqcount); 
+  free (obstimes);
+  return TRUE;
 }
+
+int GetWarpID (Image *image, int obstime, unsigned short photcode, double Rave, double Dave, float X, float Y) {
+
+  int seq = myIndexGetEntry (warpObstimeIndex, obstime);
+  myAssert (seq > -1, "ooops");
+  myAssert (warpgroup[seq].photcode == photcode, "oops");
+  
+  // we now have the warp group, but which is the correct warp?
+  for (int i = 0; i < warpgroup[seq].Nwarps; i++) {
+    if (Rave < warpgroup[seq].Rmin[i]) continue;
+    if (Rave > warpgroup[seq].Rmax[i]) continue;
+    if (Dave < warpgroup[seq].Dmin[i]) continue;
+    if (Dave > warpgroup[seq].Dmax[i]) continue;
+
+    // this is a possible image: check the coordinates:
+    int N = warpgroup[seq].warps[i];
+
+    double Rwrp, Dwrp;
+    XY_to_RD (&Rwrp, &Dwrp, X, Y, &image[N].coords);
+
+    float csdec = cos(Dave * RAD_DEG);
+    
+    // find the ra,dec displacement in arcsec:
+    double dR = 3600.0*(Rwrp - Rave)*csdec;
+    double dD = 3600.0*(Dwrp - Dave);
+    
+    // skip detections which are within a small distance of the expected location
+    double dPos = hypot(dR,dD);
+    if (dPos < 2.0) return N;
+
+    // check for multiple possible matches??
+  }
+  return -1;
+}
+
+void FreeWarpGroups (void) {
+
+  for (int i = 0; i < Nwarpgroup; i++) {
+    free (warpgroup[i].warps);
+    free (warpgroup[i].Rmin);
+    free (warpgroup[i].Rmax);
+    free (warpgroup[i].Dmin);
+    free (warpgroup[i].Dmax);
+  }    
+
+  free (warpgroup);
+  myIndexFree (warpObstimeIndex);
+}
Index: /trunk/Ohana/src/relastro/src/args.c
===================================================================
--- /trunk/Ohana/src/relastro/src/args.c	(revision 39470)
+++ /trunk/Ohana/src/relastro/src/args.c	(revision 39471)
@@ -51,4 +51,9 @@
     remove_argument (N, &argc, argv);
     RELASTRO_OP = OP_UPDATE_OFFSETS;
+  }
+
+  if ((N = get_argument (argc, argv, "-repair-warps"))) {
+    remove_argument (N, &argc, argv);
+    RELASTRO_OP = OP_REPAIR_WARPS;
   }
 
@@ -666,4 +671,9 @@
     remove_argument (N, &argc, argv);
     RELASTRO_OP = OP_UPDATE_OFFSETS;
+  }
+
+  if ((N = get_argument (argc, argv, "-repair-warps"))) {
+    remove_argument (N, &argc, argv);
+    RELASTRO_OP = OP_REPAIR_WARPS;
   }
 
Index: /trunk/Ohana/src/relastro/src/myIndex.c
===================================================================
--- /trunk/Ohana/src/relastro/src/myIndex.c	(revision 39471)
+++ /trunk/Ohana/src/relastro/src/myIndex.c	(revision 39471)
@@ -0,0 +1,76 @@
+# include "relastro.h"
+
+myIndexType *myIndexInit () {
+
+  myIndexType *myIndex;
+  ALLOCATE (myIndex, myIndexType, 1);
+
+  myIndex->minID = MAX_INT;
+  myIndex->maxID = 0;
+
+  myIndex->NINDEX = 1000;
+  myIndex->Nindex = 0;
+
+  ALLOCATE (myIndex->index, int, myIndex->NINDEX);
+
+  return myIndex;
+}
+
+int myIndexFree (myIndexType *myIndex) {
+
+  free (myIndex->index);
+  free (myIndex);
+  return TRUE;
+}
+
+// set minID and maxID externally
+int myIndexUpdateLimits (myIndexType *myIndex, int value) {
+
+  myIndex->maxID = MAX(value, myIndex->maxID);
+  myIndex->minID = MIN(value, myIndex->minID);
+
+  return TRUE;
+}
+
+// once minID and maxID are set, this function defines the range and inits
+int myIndexSetRange (myIndexType *myIndex) {
+
+  myIndex->Nindex = myIndex->maxID - myIndex->minID + 1;
+  myIndex->NINDEX = myIndex->Nindex;
+  
+  REALLOCATE (myIndex->index, int, myIndex->NINDEX);
+
+  int i;
+  for (i = 0; i < myIndex->Nindex; i++) {
+    myIndex->index[i] = -1;
+  }
+
+  return TRUE;
+}
+
+// once minID and maxID are set, this function defines the range and inits
+int myIndexSetEntry (myIndexType *myIndex, int value, int entry) {
+
+  int n = value - myIndex->minID;
+
+  myAssert (n >= 0, "impossible!");
+
+  if (myIndex->index[n] != -1) {
+    fprintf (stderr, "skipping duplicate ID value: %d\n", value);
+    return -1;
+  }
+
+  myIndex->index[n] = entry;
+  return n;
+}
+
+// once minID and maxID are set, this function defines the range and inits
+int myIndexGetEntry (myIndexType *myIndex, int value) {
+
+  if (value < myIndex->minID) return -1; // not in this index
+  if (value > myIndex->maxID) return -1; // not in this index
+
+  int n = value - myIndex->minID;
+
+  return myIndex->index[n];
+}
Index: /trunk/Ohana/src/relastro/src/relastro.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro.c	(revision 39470)
+++ /trunk/Ohana/src/relastro/src/relastro.c	(revision 39471)
@@ -69,4 +69,35 @@
       exit (0);
 
+    case OP_REPAIR_WARPS: {
+      FITS_DB db;
+      if (!PARALLEL) {
+      
+	set_db (&db);
+	gfits_db_init (&db);
+
+	/* lock and load the image db table */
+	int status = dvo_image_lock (&db, ImageCat, 60.0, LCK_SOFT);
+	if (!status) Shutdown ("ERROR: failure to lock image catalog %s", db.filename);
+	if (db.dbstate == LCK_EMPTY) Shutdown ("ERROR: No images in catalog %s (1)", db.filename);
+	if (!dvo_image_load (&db, VERBOSE, FALSE)) Shutdown ("can't read image catalog %s", db.filename);
+	// the raw FITS data is freed by dvo_image_load, leaving only the Image structure data
+
+	/* load regions and images based on specified sky patch (default depth) */
+	load_images (&db, skylist, FALSE, USE_ALL_IMAGES);
+      }
+
+      // iterate over catalogs to make detection coordinates consistant
+      RepairWarps (skylist, 0, NULL);
+
+      if (!PARALLEL) {
+	gfits_db_free (&db);
+	freeImages();
+	freeMosaics ();
+      }	
+
+      relastro_free (sky, skylist);
+      exit (0);
+    }
+
     case OP_HIGH_SPEED:
       /* high-speed is a 2pt cross-correlation process for linking moving objects (high PM) */
Index: /trunk/Ohana/src/relastro/src/relastro_client.c
===================================================================
--- /trunk/Ohana/src/relastro/src/relastro_client.c	(revision 39470)
+++ /trunk/Ohana/src/relastro/src/relastro_client.c	(revision 39471)
@@ -100,4 +100,31 @@
     }
 
+      // XXX loading the images is fairly costly -- see if we can do an image subset?
+    case OP_REPAIR_WARPS: {
+      FITS_DB db;
+      
+      set_db (&db);
+      gfits_db_init (&db);
+
+      /* lock and load the image db table */
+      int status = dvo_image_lock (&db, ImageCat, 60.0, LCK_SOFT);
+      if (!status) Shutdown ("ERROR: failure to lock image catalog %s", db.filename);
+      if (db.dbstate == LCK_EMPTY) Shutdown ("ERROR: No images in catalog %s (1)", db.filename);
+      if (!dvo_image_load (&db, VERBOSE, FALSE)) Shutdown ("can't read image catalog %s", db.filename);
+      // the raw FITS data is freed by dvo_image_load, leaving only the Image structure data
+
+      /* load regions and images based on specified sky patch (default depth) */
+      load_images (&db, skylist, FALSE, USE_ALL_IMAGES);
+
+      RepairWarps (skylist, HOST_ID, HOSTDIR);
+      
+      gfits_db_free (&db);
+      freeImages();
+      freeMosaics ();
+
+      relastro_client_free (sky, skylist);
+      break;
+    }
+
     case OP_HIGH_SPEED: {
       // USAGE: relastro_client -high-speed
