Index: trunk/Ohana/src/relphot/src/StackImageMaps.c
===================================================================
--- trunk/Ohana/src/relphot/src/StackImageMaps.c	(revision 39512)
+++ trunk/Ohana/src/relphot/src/StackImageMaps.c	(revision 39512)
@@ -0,0 +1,301 @@
+# include "relphot.h"
+
+// generate an index of stack image ra,dec centers sorted indexes to the image IDs
+
+# define D_NSTACKS 1000
+
+typedef struct {
+  unsigned short photcode;
+  double *Rcenter;
+  double *Dcenter;
+  int    *imageID;
+  int    *imageSeq;
+  int Nstacks;
+  int NSTACKS;
+} StackGroupType;
+
+// one group for each photcode
+StackGroupType stackgroup[5];
+
+// myIndexType *warpObstimeIndex = NULL;
+
+int MakeStackIndex (void) {
+
+  off_t Nimage;
+  Image *image = getimages (&Nimage, NULL);
+
+  for (int i = 0; i < 5; i++) {
+    stackgroup[i].photcode = 11000 + i*100;
+
+    stackgroup[i].Nstacks = 0;
+    stackgroup[i].NSTACKS = D_NSTACKS;
+    ALLOCATE (stackgroup[i].Rcenter,  double, stackgroup[i].NSTACKS);
+    ALLOCATE (stackgroup[i].Dcenter,  double, stackgroup[i].NSTACKS);
+    ALLOCATE (stackgroup[i].imageID,  int,    stackgroup[i].NSTACKS);
+    ALLOCATE (stackgroup[i].imageSeq, int,    stackgroup[i].NSTACKS);
+  }
+
+  // first generate a list of obstime values
+  for (off_t i = 0; i < Nimage; i++) {
+    if (!isGPC1stack(image[i].photcode)) continue;
+
+    int Ng = -1;
+    switch (image[i].photcode) {
+      case 11000: Ng = 0; break;
+      case 11100: Ng = 1; break;
+      case 11200: Ng = 2; break;
+      case 11300: Ng = 3; break;
+      case 11400: Ng = 4; break;
+      default: myAbort("impossible photcode");
+    }
+
+    int N = stackgroup[Ng].Nstacks;
+    XY_to_RD (&stackgroup[Ng].Rcenter[N], &stackgroup[Ng].Dcenter[N], 0.5*image[i].NX, 0.5*image[i].NY, image[i].coords);
+
+    stackgroup[Ng].imageSeq[N] = i;
+    stackgroup[Ng].imageID[N] = image[i].imageID;
+
+    stackgroup[Ng].Nstacks ++;
+
+    if (stackgroup[Ng].Nstacks >= stackgroup[Ng].NSTACKS) {
+      stackgroup[Ng].NSTACKS += D_NSTACKS;
+      REALLOCATE (stackgroup[Ng].Rcenter,  double, stackgroup[Ng].NSTACKS);
+      REALLOCATE (stackgroup[Ng].Dcenter,  double, stackgroup[Ng].NSTACKS);
+      REALLOCATE (stackgroup[Ng].imageID,  int,    stackgroup[Ng].NSTACKS);
+      REALLOCATE (stackgroup[Ng].imageSeq, int,    stackgroup[Ng].NSTACKS);
+    }
+  }
+
+  
+
+
+  // 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);
+    ALLOCATE (warpgroup[i].onBoundary, int, 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;
+
+      warpgroup[i].onBoundary[j] = FALSE;
+
+      double R, D;
+      XY_to_RD (&R, &D, 0.0, 0.0, &image[N].coords);
+      R = ohana_normalize_angle_to_midpoint(R, 180.0);
+      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);
+      R = ohana_normalize_angle_to_midpoint(R, 180.0);
+      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);
+      R = ohana_normalize_angle_to_midpoint(R, 180.0);
+      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);
+      R = ohana_normalize_angle_to_midpoint(R, 180.0);
+      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 10 arcsec worth of padding
+      double dR = 10.0/3600.0 / cos (RAD_DEG*warpgroup[i].Dmin[j]);
+      double dD = 10.0/3600.0;
+
+      // at north pole, force test of all nearby skycells
+      if (warpgroup[i].Dmax[j] > 89.8) {
+	warpgroup[i].Rmin[j] =   0.0;
+	warpgroup[i].Rmax[j] = 360.0;
+	warpgroup[i].Dmax[j] =  90.0;
+	warpgroup[i].Dmin[j] -=  dD;
+	continue;
+      } 
+
+      if (warpgroup[i].Rmax[j] - warpgroup[i].Rmin[j] > 270.0) {
+	// Rmin and Rmax are in the range 0 - 360.  For images at the 0,360 boundary,
+	// "Rmax" is the lower edge, and "Rmin" is the upper edge.  we need to flip them 
+	// and then break the 0-360 range:
+	double tmp = warpgroup[i].Rmin[j];
+	warpgroup[i].Rmin[j] = warpgroup[i].Rmax[j] - 360.0;
+	warpgroup[i].Rmax[j] = tmp;
+	warpgroup[i].onBoundary[j] = TRUE;
+      }
+
+      warpgroup[i].Rmin[j] -=  dR;
+      warpgroup[i].Rmax[j] +=  dR;
+      warpgroup[i].Dmin[j] -=  dD;
+      warpgroup[i].Dmax[j] +=  dD;
+    }
+  }
+
+  free (uniqtimes); 
+  free (uniqcount); 
+  free (obstimes);
+  return TRUE;
+}
+
+int GetWarpSeq (Image *image, int obstime, unsigned short photcode, double Rave, double Dave, float X, float Y) {
+
+  if (!warpObstimeIndex) return -1;
+
+  int seq = myIndexGetEntry (warpObstimeIndex, obstime);
+  myAssert (seq > -1, "ooops");
+  myAssert (warpgroup[seq].photcode == photcode, "oops");
+  
+  double dPosMin = NAN;
+  int nPosMin = -1;
+
+  // we now have the warp group, but which is the correct warp?
+  for (int i = 0; i < warpgroup[seq].Nwarps; i++) {
+    if (warpgroup[seq].onBoundary[i]) {
+      int inRange1 = (Rave >= warpgroup[seq].Rmin[i]) && (Rave <= warpgroup[seq].Rmax[i]);
+      int inRange2 = (Rave >= warpgroup[seq].Rmin[i] + 360.0) && (Rave <= warpgroup[seq].Rmax[i] + 360.0);
+      if (!inRange1 && !inRange2) continue;
+    } else {
+      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];
+
+    // project Rave,Dave to image pixels
+    double Xtst, Ytst;
+    RD_to_XY (&Xtst, &Ytst, Rave, Dave, &image[N].coords);
+
+    // find the pixel offset
+    double dX = (Xtst - X);
+    double dY = (Ytst - Y);
+    
+    // skip detections which are within a small distance from the expected location
+    double dPos = hypot(dX,dY);
+    if (dPos < 20.0) return N; // 20.0 pixels = 5.0 arcsec
+
+    // check for multiple possible matches??
+
+    // record the min pos so we can see how things fail
+    if (isnan(dPosMin)) {
+      dPosMin = dPos;
+      nPosMin = i;
+    } else {
+      if (dPos < dPosMin) {
+	dPosMin = dPos;
+	nPosMin = i;
+      }
+    }
+  }
+  
+  // if we did not find any matched, give up
+  if (nPosMin < 0) return -1;
+
+  // return closest image
+  return warpgroup[seq].warps[nPosMin];
+}
+
+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[i].onBoundary);
+  }    
+
+  free (warpgroup);
+  myIndexFree (warpObstimeIndex);
+}
