Index: trunk/Ohana/src/opihi/cmd.astro/Makefile
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/Makefile	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/Makefile	(revision 37807)
@@ -71,4 +71,6 @@
 $(SRC)/star.$(ARCH).o		   \
 $(SRC)/transform.$(ARCH).o         \
+$(SRC)/vshimage.$(ARCH).o         \
+$(SRC)/shimage.$(ARCH).o         \
 $(SRC)/imsub.$(ARCH).o		   \
 $(SRC)/imfit.$(ARCH).o		   \
Index: trunk/Ohana/src/opihi/cmd.astro/cdensify.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/cdensify.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/cdensify.c	(revision 37807)
@@ -97,11 +97,5 @@
   // generate the PSF in a local tangent plane
   Coords coords;
-  coords.crpix1 = coords.crpix2 = 0.0;
-  coords.crval1 = coords.crval2 = 0.0;
-  coords.cdelt1 = coords.cdelt2 = 1.0;
-  coords.pc1_1  = coords.pc2_2  = 1.0;
-  coords.pc1_2  = coords.pc2_1  = 0.0;
-  coords.Npolyterms = 0;
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
 
   r = vr[0].elements.Flt;
Index: trunk/Ohana/src/opihi/cmd.astro/coords.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/coords.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/coords.c	(revision 37807)
@@ -71,5 +71,5 @@
     if ((mosbuffer = SelectBuffer (MOSAIC, OLDBUFFER, TRUE)) == NULL) goto escape;
     GetCoords (&moscoords, &mosbuffer[0].header);
-    RegisterMosaic (&moscoords);
+    coords.mosaic = &moscoords;
   }
   
Index: trunk/Ohana/src/opihi/cmd.astro/csystem.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/csystem.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/csystem.c	(revision 37807)
@@ -13,16 +13,24 @@
   if (argc != 5) goto syntax;
 
-  switch (argv[1][0]) {
-    case 'C': input = COORD_CELESTIAL; break;
-    case 'G': input = COORD_GALACTIC; break;
-    case 'E': input = COORD_ECLIPTIC; break;
-    default: goto syntax;
+  if (!strcmp(argv[1], "G2004")) {
+    input = COORD_GALACTIC_REID_2004;
+  } else {
+    switch (argv[1][0]) {
+      case 'C': input = COORD_CELESTIAL; break;
+      case 'G': input = COORD_GALACTIC; break;
+      case 'E': input = COORD_ECLIPTIC; break;
+      default: goto syntax;
+    }
   }
 
-  switch (argv[2][0]) {
-    case 'C': output = COORD_CELESTIAL; break;
-    case 'G': output = COORD_GALACTIC; break;
-    case 'E': output = COORD_ECLIPTIC; break;
-    default: goto syntax;
+  if (!strcmp(argv[2], "G2004")) {
+    output = COORD_GALACTIC_REID_2004;
+  } else {
+    switch (argv[2][0]) {
+      case 'C': output = COORD_CELESTIAL; break;
+      case 'G': output = COORD_GALACTIC; break;
+      case 'E': output = COORD_ECLIPTIC; break;
+      default: goto syntax;
+    }
   }
 
Index: trunk/Ohana/src/opihi/cmd.astro/fitplx.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/fitplx.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/fitplx.c	(revision 37807)
@@ -104,13 +104,8 @@
   /* project coordinates to a plane centered on the object with units of arcsec */
   Coords coords;
+  InitCoords (&coords, "DEC--SIN");
   coords.crval1 = Rmean;
   coords.crval2 = Dmean;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   double *X, *Y, *t, *pX, *pY, *dX, *dY;
Index: trunk/Ohana/src/opihi/cmd.astro/fitpm.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/fitpm.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/fitpm.c	(revision 37807)
@@ -100,13 +100,8 @@
   /* project coordinates to a plane centered on the object with units of arcsec */
   Coords coords;
+  InitCoords (&coords, "DEC--SIN");
   coords.crval1 = Rmean;
   coords.crval2 = Dmean;
-  coords.crpix1 = 0;
-  coords.crpix2 = 0;
   coords.cdelt1 = coords.cdelt2 = 1.0 / 3600.0;
-  coords.pc1_1  = coords.pc2_2 = 1.0;
-  coords.pc1_2  = coords.pc2_1 = 0.0;
-  coords.Npolyterms = 1;
-  strcpy (coords.ctype, "DEC--SIN");
 
   double *X, *Y, *t, *dX, *dY;
Index: trunk/Ohana/src/opihi/cmd.astro/gauss.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/gauss.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/gauss.c	(revision 37807)
@@ -30,4 +30,11 @@
   if (!GetImageData (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   Nborder = 3;
Index: trunk/Ohana/src/opihi/cmd.astro/getcoords.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/getcoords.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/getcoords.c	(revision 37807)
@@ -54,5 +54,5 @@
   if (!strcmp(&coords.ctype[4], "-WRP")) {
     if (MOSAIC == NULL) {
-      gprint (GP_ERR, "must supply mosaic for WRP coords\n");
+      gprint (GP_ERR, "must supply mosaic for WRP coords with -mosaic [buffer]\n");
       return (FALSE);
     }
@@ -65,5 +65,5 @@
     }
     GetCoords (&moscoords, &mosbuffer[0].header);
-    RegisterMosaic (&moscoords);
+    coords.mosaic = &moscoords;
   }
   
Index: trunk/Ohana/src/opihi/cmd.astro/init.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/init.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/init.c	(revision 37807)
@@ -61,4 +61,6 @@
 int times                   PROTO((int, char **));
 int transform               PROTO((int, char **));
+int vshimage                PROTO((int, char **));
+int shimage                 PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -121,4 +123,6 @@
   {1, "star",        star,         "star stats at rough coords"},
   {1, "transform",   transform,    "geometric transformation of image"},
+  {1, "vshimage",    vshimage,     "generate images for vector spherical harmonic terms"},
+  {1, "shimage",     shimage,      "generate images for spherical harmonic terms"},
 }; 
 
Index: trunk/Ohana/src/opihi/cmd.astro/objload.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/objload.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/objload.c	(revision 37807)
@@ -19,4 +19,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   Objtype = 0;
Index: trunk/Ohana/src/opihi/cmd.astro/region.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/region.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.astro/region.c	(revision 37807)
@@ -17,4 +17,11 @@
   if (!GetGraph (&graphmode, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   NoClear = FALSE;
@@ -80,5 +87,6 @@
   if (!ohana_str_to_radec (&Ra, &Dec, argv[1], argv[2])) return (FALSE);
   Radius = atof (argv[3]);
-  strcpy (graphmode.coords.ctype, "DEC--TAN");
+  InitCoords (&graphmode.coords, "DEC--TAN");
+
   if (argc == 5) {
     if (!strcasecmp (argv[4], "TAN")) 
@@ -99,6 +107,11 @@
       strcpy (graphmode.coords.ctype, "DEC--PAR");
   }
+  
+  graphmode.coords.crval1 = Ra;
+  graphmode.coords.crval2 = Dec;
 
-  
+  graphmode.coords.pc1_1 = (graphmode.flipeast) ? -1 : 1;
+  graphmode.coords.pc2_2 = (graphmode.flipnorth) ? -1 : 1;
+
   /* ask kapa for coordinate limits, to get the right aspect ratio */
   KapaGetLimits (kapa, &dx, &dy);
@@ -132,14 +145,4 @@
   set_int_variable ("NORTH_UP", !graphmode.flipnorth);
 
-  graphmode.coords.pc1_1 = (graphmode.flipeast) ? -1 : 1;
-  graphmode.coords.pc2_2 = (graphmode.flipnorth) ? -1 : 1;
-
-  graphmode.coords.pc1_2 = graphmode.coords.pc2_1 = 0.0;
-  graphmode.coords.crval1 = Ra;
-  graphmode.coords.crval2 = Dec;
-  graphmode.coords.crpix1 = 0.0;
-  graphmode.coords.crpix2 = 0.0;
-  graphmode.coords.cdelt1 = graphmode.coords.cdelt2 = 1.0;
-
   if (!NoClear) KapaClearSections (kapa);
   KapaSetLimits (kapa, &graphmode);
Index: trunk/Ohana/src/opihi/cmd.astro/shimage.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/shimage.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.astro/shimage.c	(revision 37807)
@@ -0,0 +1,64 @@
+# include "data.h"
+
+int shimage (int argc, char **argv) {
+  
+  int ix, iy;
+  Buffer *FrIm, *FiIm, *src;
+
+  if (argc != 6) {
+    gprint (GP_ERR, "USAGE: shimage (image) l m Fr Fi\n");
+    gprint (GP_ERR, "  set the Fr and Fi values for pixels in the given image for (l,m)\n");
+    return (FALSE);
+  }
+
+  if ((src = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+
+  Coords coords;
+  GetCoords (&coords, &src[0].header);
+  if (!strcmp(&coords.ctype[4], "-WRP")) {
+    fprintf (stderr, "mosaic astrometry not yet supported for vshimage\n");
+    return FALSE;
+  }
+
+  int l = atoi(argv[2]);
+  int m = atoi(argv[3]);
+
+  if ((FrIm = SelectBuffer (argv[4], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((FiIm = SelectBuffer (argv[5], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+
+  int Nx = src[0].header.Naxis[0];
+  int Ny = src[0].header.Naxis[1];
+
+  gfits_free_matrix (&FrIm[0].matrix); gfits_free_header (&FrIm[0].header); CreateBuffer (FrIm, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&FiIm[0].matrix); gfits_free_header (&FiIm[0].header); CreateBuffer (FiIm, Nx, Ny, -32, 1.0, 0.0);
+
+  // l=0 allocates space for a single value
+  SHterms *terms = SHtermsInit (0);
+
+  float *Fr = (float *) FrIm[0].matrix.buffer;
+  float *Fi = (float *) FiIm[0].matrix.buffer;
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+
+      int Npix = ix + iy*Nx;
+
+      double R, D;
+      int status = XY_to_RD (&R, &D, ix, iy, &coords);
+
+      if (!status) {
+	Fr[Npix] = NAN;
+	Fi[Npix] = NAN;
+	continue;
+      }
+      
+      SHtermsForLM (terms, R, D, l, m); 
+
+      Fr[Npix] = terms->Fr[0];
+      Fi[Npix] = terms->Fi[0];
+    }
+  }
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.astro/vshimage.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/vshimage.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.astro/vshimage.c	(revision 37807)
@@ -0,0 +1,74 @@
+# include "data.h"
+
+int vshimage (int argc, char **argv) {
+  
+  int ix, iy;
+  Buffer *Rbim, *Reim, *Dbim, *Deim, *src;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: vsh image l m dRb dRe dDb dDe\n");
+    gprint (GP_ERR, "  set the dRb, dRe, dDb, dDe values for pixels in the given image for (l,m)\n");
+    return (FALSE);
+  }
+
+  if ((src = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+
+  Coords coords;
+  GetCoords (&coords, &src[0].header);
+  if (!strcmp(&coords.ctype[4], "-WRP")) {
+    fprintf (stderr, "mosaic astrometry not yet supported for vshimage\n");
+    return FALSE;
+  }
+
+  int l = atoi(argv[2]);
+  int m = atoi(argv[3]);
+
+  if ((Rbim = SelectBuffer (argv[4], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((Reim = SelectBuffer (argv[5], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((Dbim = SelectBuffer (argv[6], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((Deim = SelectBuffer (argv[7], ANYBUFFER, TRUE)) == NULL) return (FALSE);
+
+  int Nx = src[0].header.Naxis[0];
+  int Ny = src[0].header.Naxis[1];
+
+  gfits_free_matrix (&Rbim[0].matrix); gfits_free_header (&Rbim[0].header); CreateBuffer (Rbim, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&Reim[0].matrix); gfits_free_header (&Reim[0].header); CreateBuffer (Reim, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&Dbim[0].matrix); gfits_free_header (&Dbim[0].header); CreateBuffer (Dbim, Nx, Ny, -32, 1.0, 0.0);
+  gfits_free_matrix (&Deim[0].matrix); gfits_free_header (&Deim[0].header); CreateBuffer (Deim, Nx, Ny, -32, 1.0, 0.0);
+
+  // l=0 allocates space for a single value
+  VSHterms *terms = VSHtermsInit (0);
+
+  float *Rb = (float *) Rbim[0].matrix.buffer;
+  float *Re = (float *) Reim[0].matrix.buffer;
+  float *Db = (float *) Dbim[0].matrix.buffer;
+  float *De = (float *) Deim[0].matrix.buffer;
+
+  for (ix = 0; ix < Nx; ix++) {
+    for (iy = 0; iy < Ny; iy++) {
+
+      int Npix = ix + iy*Nx;
+
+      double R, D;
+      int status = XY_to_RD (&R, &D, ix, iy, &coords);
+
+      if (!status) {
+	Rb[Npix] = NAN;
+	Re[Npix] = NAN;
+	Db[Npix] = NAN;
+	De[Npix] = NAN;
+	continue;
+      }
+      
+      VSHtermsForLM (terms, R, D, l, m); 
+
+      Rb[Npix] = terms->dR_B[0];
+      Re[Npix] = terms->dR_E[0];
+      Db[Npix] = terms->dD_B[0];
+      De[Npix] = terms->dD_E[0];
+    }
+  }
+  VSHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 37807)
@@ -39,4 +39,5 @@
 $(SRC)/densify.$(ARCH).o	\
 $(SRC)/device.$(ARCH).o	\
+$(SRC)/dft2d.$(ARCH).o	\
 $(SRC)/dimendown.$(ARCH).o	\
 $(SRC)/dimenup.$(ARCH).o	\
@@ -160,4 +161,10 @@
 $(SRC)/vstats.$(ARCH).o		   \
 $(SRC)/xsection.$(ARCH).o          \
+$(SRC)/vsh.$(ARCH).o	   \
+$(SRC)/vshfit.$(ARCH).o	   \
+$(SRC)/shterms.$(ARCH).o	   \
+$(SRC)/shfit.$(ARCH).o	   \
+$(SRC)/shdot.$(ARCH).o	   \
+$(SRC)/shapply.$(ARCH).o	   \
 $(SRC)/wd.$(ARCH).o		   \
 $(SRC)/write_vectors.$(ARCH).o	   \
Index: trunk/Ohana/src/opihi/cmd.data/center.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/center.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/center.c	(revision 37807)
@@ -16,4 +16,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   // XXX need an option to center the image based on the current plot limits
Index: trunk/Ohana/src/opihi/cmd.data/dft2d.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/dft2d.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/dft2d.c	(revision 37807)
@@ -0,0 +1,142 @@
+# include "data.h"
+
+// perform a 2D fourier transform.  
+
+static int NxLast = 0;
+static int NyLast = 0;
+static float *Fterms = NULL;
+
+int dft2d (int argc, char **argv) {
+  
+  int ix, iy, wx, wy;
+  Buffer *src = NULL;
+  Buffer *tgt = NULL;
+
+  if (argc != 4) goto usage;
+  
+  if (strcasecmp(argv[2], "to")) goto usage;
+  if ((src = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) goto usage;
+  if ((tgt = SelectBuffer (argv[3], ANYBUFFER, TRUE)) == NULL) goto usage;
+
+  int Nx = src[0].matrix.Naxis[0];
+  int Ny = src[0].matrix.Naxis[1];
+  ResetBuffer (tgt, Nx, Ny, -32, 0.0, 1.0);
+
+  if (Nx % 2) goto usage;
+  if (Ny % 2) goto usage;
+
+  // generate and save the fourier terms, if needed
+  if (Fterms && (NxLast == Nx) && (NyLast == Ny)) goto reuse;
+
+  if (!Fterms) {
+    ALLOCATE (Fterms, float, SQ(Nx*Ny));
+    NxLast = Nx;
+    NyLast = Ny;
+  } else {
+    REALLOCATE (Fterms, float, SQ(Nx*Ny));
+    NxLast = Nx;
+    NyLast = Ny;
+  }
+
+  // x-dir cosine terms
+  float Wx = 2*M_PI/(float)Nx;
+  float Wy = 2*M_PI/(float)Ny;
+  for (wx = 0; wx <= Nx / 2; wx++) {
+    for (wy = 0; wy <= Ny / 2; wy++) {
+      float rx = ((wx == 0) || (wx == Nx/2)) ? (1.0/Nx) : (2.0/Nx);
+      float ry = ((wy == 0) || (wy == Ny/2)) ? (1.0/Ny) : (2.0/Ny);
+      float rn = rx*ry;
+      for (ix = 0; ix < Nx; ix++) {
+	float fxc = cos(wx*Wx*ix);
+	for (iy = 0; iy < Ny; iy++) {
+	  float fyc = cos(wy*Wy*iy);
+	  int Nf = wx + wy*Ny;
+	  int out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxc*fyc*rn;
+	  if (wy == 0) continue;
+	  if (wy == Ny/2) continue;
+	  float fys = sin(wy*Wy*iy);
+	  Nf = wx + (Ny - wy)*Ny;
+	  out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxc*fys*rn;
+	}
+	if (wx == 0) continue;
+	if (wx == Nx/2) continue;
+	float fxs = sin(wx*Wx*ix);
+	for (iy = 0; iy < Ny; iy++) {
+	  float fyc = cos(wy*Wy*iy);
+	  int Nf = (Nx - wx) + wy*Ny;
+	  int out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxs*fyc*rn;
+	  if (wy == 0) continue;
+	  if (wy == Ny/2) continue;
+	  float fys = sin(wy*Wy*iy);
+	  Nf = (Nx - wx) + (Ny - wy)*Ny;
+	  out = ix + iy*Nx + Nf*Nx*Ny;
+	  Fterms[out] = fxs*fys*rn;
+	}
+      }
+    }
+  }
+
+ reuse:
+  {
+    // generate and save the dot products
+    float *sv = (float *)src->matrix.buffer;
+    float *tv = (float *)tgt->matrix.buffer;
+    for (wx = 0; wx <= Nx/2; wx++) {
+      for (wy = 0; wy <= Ny/2; wy++) {
+	float fsum = 0.0;
+	int Nf = (wx + wy*Nx)*Nx*Ny;
+	for (ix = 0; ix < Nx; ix++) {
+	  for (iy = 0; iy < Ny; iy++) {
+	    float Ft = Fterms[Nf + ix + iy*Nx];
+	    float Fi = sv[ix + iy*Nx];
+	    fsum += Ft*Fi;
+	    // fprintf (stderr, "%d,%d : %d,%d : %f %f %f\n", ix, iy, wx, wy, Ft, Fi, fsum);
+	  }
+	}
+	tv[wx + wy*Nx] = fsum;
+      }
+    }
+  }
+  return (TRUE);
+
+ usage:
+  gprint (GP_ERR, "USAGE: dft2d (input) to (output)\n");
+  gprint (GP_ERR, "  NOTE: Nx & Ny must both be even\n");
+  return (FALSE);
+}
+
+
+/*** 
+
+     2D direct fourier transform:
+
+     we have an image src of size [Nx,Ny].  in each direction,
+     we can generate the following fourier components:
+
+     cos(0*2pi*x/Nx), cos(1*2pi*x/Nx),... cos((Nx/2)  *2pi*x/Nx), 
+     sin((Nx/2-1)*2pi*x/Nx), sin((Nx/2-2)*2pi*x/Nx),.. sin(1*2pi*x/Nx)
+
+     Note that there are 2 fewer sin elements than there are cos
+     elements:
+       cos(0) = 1 -> DC term
+       sin(0) = 0
+       sin(pi*x) = 0 for integer values of x
+		     
+     we represent the output with the highest frequency in the middle and the DC element
+     in the 0,0 corner
+
+     to generate the output elements, we need to take the dot product 
+     of the input image with an image for the given x,y frequency
+ 
+     thus, for an Nx*Ny input image we need a cube of fourier terms of dimension
+     Nx*Ny*(Nx*Ny) (only one of which is trivial)
+
+     In the assumption that we will likely re-do the same size image multiple times, I
+     store the fourier factor array once it is generated.
+
+     NOTE: do I need to require Nx & Ny even?
+
+ ***/
Index: trunk/Ohana/src/opihi/cmd.data/dimenup.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/dimenup.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/dimenup.c	(revision 37807)
@@ -23,8 +23,5 @@
     return (FALSE);
   }
-
-  gfits_free_matrix (&buf[0].matrix);
-  gfits_free_header (&buf[0].header);
-  CreateBuffer (buf, Nx, Ny, -32, 0.0, 1.0);
+  ResetBuffer (buf, Nx, Ny, -32, 0.0, 1.0);
 
   out = (float *) buf[0].matrix.buffer;
Index: trunk/Ohana/src/opihi/cmd.data/erase.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/erase.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/erase.c	(revision 37807)
@@ -15,4 +15,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if (argc < 2) {
Index: trunk/Ohana/src/opihi/cmd.data/fit2d.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/fit2d.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/fit2d.c	(revision 37807)
@@ -138,4 +138,6 @@
     y = yvec[0].elements.Flt;
     z = zvec[0].elements.Flt;
+    if (Weight) dz = dzvec[0].elements.Flt;
+
     for (i = 0; i < xvec[0].Nelements; i++, x++, y++, z++) {
       if (mask[i]) continue;
Index: trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/init.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/init.c	(revision 37807)
@@ -1,3 +1,4 @@
 # include "data.h"
+// XXX adding a comment
 
 int accum            PROTO((int, char **));
@@ -31,4 +32,5 @@
 int densify          PROTO((int, char **));
 int device           PROTO((int, char **));
+int dft2d            PROTO((int, char **));
 int dimendown        PROTO((int, char **));
 int dimenup          PROTO((int, char **));
@@ -147,4 +149,10 @@
 int vpop             PROTO((int, char **));
 int vsmooth          PROTO((int, char **));
+int vsh              PROTO((int, char **));
+int vshfit           PROTO((int, char **));
+int shterms          PROTO((int, char **));
+int shfit            PROTO((int, char **));
+int shdot            PROTO((int, char **));
+int shapply          PROTO((int, char **));
 int wd               PROTO((int, char **));
 int write_vectors    PROTO((int, char **));
@@ -189,4 +197,5 @@
   {1, "densify",      densify,          "create an image histogram from a set of vectors"},
   {1, "device",       device,           "set / get current graphics device"},
+  {1, "dft2d",        dft2d,            "2D discrete fourier transform"},
   {1, "dimendown",    dimendown,        "convert image to vector"},
   {1, "dimenup",      dimenup,          "convert vector to image"},
@@ -314,4 +323,10 @@
   {1, "vstats",       vstats,           "statistics on a vector"},
   {1, "vzload",       vzload,           "load vectors as overlay on image display (scaled points)"},
+  {1, "vsh",          vsh,              "Vector Spherical Harmonics"},
+  {1, "vshfit",       vshfit,           "Vector Spherical Harmonics fits"},
+  {1, "shterms",      shterms,          "Spherical Harmonics terms"},
+  {1, "shfit",        shfit,            "Spherical Harmonics fits"},
+  {1, "shdot",        shdot,            "Spherical Harmonics dot product"},
+  {1, "shapply",      shapply,          "Spherical Harmonics fit application"},
   {1, "wd",           wd,               "write an image to a file"},
   {1, "write",        write_vectors,    "write vectors to datafile"},
Index: trunk/Ohana/src/opihi/cmd.data/load.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/load.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/load.c	(revision 37807)
@@ -23,4 +23,11 @@
   if (!GetImageData (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   ISCEL = FALSE;
Index: trunk/Ohana/src/opihi/cmd.data/point.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/point.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/point.c	(revision 37807)
@@ -21,4 +21,11 @@
   if (!GetImageData (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   celestial = FALSE;
Index: trunk/Ohana/src/opihi/cmd.data/read_vectors.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 37807)
@@ -5,4 +5,5 @@
 
 void read_vectors_cleanup ();
+int read_table_sizes (Header *header);
 
 int datafile (int argc, char **argv) {
@@ -331,15 +332,14 @@
 
   off_t Nbytes;
-  int i, j, N, Nextend, Ny, Binary, vecType, padIfShort;
-  char type[16], ID[80], *CCDKeyword;
+  int i, j, N, Ny, Binary, vecType;
+  char type[16], ID[80];
   FTable table;
   Header header;
   Vector **vec;
-  int FITS_TRANSPOSE;
 
   table.buffer = NULL;
   header.buffer = NULL;
 
-  FITS_TRANSPOSE = FALSE;
+  int FITS_TRANSPOSE = FALSE;
   if ((N = get_argument (argc, argv, "-transpose"))) {
     remove_argument (N, &argc, argv);
@@ -347,5 +347,5 @@
   }
 
-  CCDKeyword = NULL;
+  char *CCDKeyword = NULL;
   if ((N = get_argument (argc, argv, "-keyword"))) {
     remove_argument (N, &argc, argv);
@@ -354,5 +354,5 @@
   }
 
-  padIfShort = FALSE;
+  int padIfShort = FALSE;
   if ((N = get_argument (argc, argv, "-pad-if-short"))) {
     remove_argument (N, &argc, argv);
@@ -360,8 +360,33 @@
   }
 
-  Nextend = -1;
+  int getSizes = FALSE;
+  if ((N = get_argument (argc, argv, "-sizes"))) {
+    remove_argument (N, &argc, argv);
+    getSizes = TRUE;
+  }
+
+  int Nextend = -1;
   if ((N = get_argument (argc, argv, "-extnum"))) {
     remove_argument (N, &argc, argv);
     Nextend = atoi (extname);
+  }
+
+  int start = 0;
+  int Nrows = -1; // -1 : read entire table
+  if ((N = get_argument (argc, argv, "-range"))) {
+    remove_argument (N, &argc, argv);
+    start = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+    Nrows = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  // if CharAsVectors, char fields will be saved as vectors NAME:0 -- NAME:n for n characters
+  // else char fields will be saved as $NAME:0 - $NAME:m for m rows
+  // if (Ny > 10000), force CharAsVectors
+  int CharAsVectors = FALSE;
+  if ((N = get_argument (argc, argv, "-char-vectors"))) {
+    remove_argument (N, &argc, argv);
+    CharAsVectors = TRUE;
   }
 
@@ -397,6 +422,24 @@
     }
     if (!gfits_load_header (f, &header)) ESCAPE ("error reading header for extension %d\n", Nextend);
-    if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension %d\n", Nextend);
-
+
+    if (getSizes) {
+      read_table_sizes (&header);
+      if (CCDKeyword != NULL) free (CCDKeyword); 
+      gfits_free_header (&header); 
+      return TRUE;
+    }
+
+    if (Nrows == -1) {
+      Nrows = header.Naxis[1] - start;
+    }
+    if (start < 0) ESCAPE ("invalid range: start < 0\n");
+    if (start >= header.Naxis[1]) ESCAPE ("invalid range: start >= Ny (%d)\n", header.Naxis[1]);
+    if (Nrows < 0) ESCAPE ("invalid range: Nrows < 0\n");
+    if (start + Nrows > header.Naxis[1]) ESCAPE ("invalid range: start + Nrows > Ny (%d)\n", header.Naxis[1]);
+
+    // Ny = 100, start = 0, Nrows = -1 -> Nrows => 100
+    // Ny = 100, start = 10, Nrows = 90
+
+    if (!gfits_fread_ftable_range (f, padIfShort, &table, start, Nrows)) ESCAPE ("error reading table for extension %d\n", Nextend);
   } else {
     if (CCDKeyword == NULL) {
@@ -428,5 +471,17 @@
 	continue;
       }
-      if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension\n");
+
+      if (Nrows == -1) {
+	Nrows = header.Naxis[1] - start;
+      }
+      if (start < 0) ESCAPE ("invalid range: start < 0\n");
+      if (start >= header.Naxis[1]) ESCAPE ("invalid range: start >= Ny (%d)\n", header.Naxis[1]);
+      if (Nrows < 0) ESCAPE ("invalid range: Nrows < 0\n");
+      if (start + Nrows > header.Naxis[1]) ESCAPE ("invalid range: start + Nrows > Ny (%d)\n", header.Naxis[1]);
+
+      if (!gfits_fread_ftable_range (f, padIfShort, &table, start, Nrows)) ESCAPE ("error reading table for extension %d\n", Nextend);
+
+      // if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension\n");
+
       break;
     }
@@ -465,5 +520,5 @@
     if (!FITS_TRANSPOSE) {
       // read string column into a list rather than a vector
-      if (!strcmp (type, "char")) {
+      if (!strcmp (type, "char") && !CharAsVectors && (Ny < 3000)) {
 	char *fieldName = argv[i];
 	char *Ptr = data;
@@ -537,2 +592,19 @@
   if (vec) free (vec);
 }
+
+// read -fits foo -sizes -- Nx, Ny, Nfields -> $table:Nx, $table:Ny, $table:$Nfields
+// read -fits foo -fields 
+
+int read_table_sizes (Header *header) {
+  
+  int Nfields;
+
+  gfits_scan (header, "TFIELDS", "%d", 1, &Nfields);
+
+  set_int_variable ("table:Nx", header->Naxis[0]);
+  set_int_variable ("table:Nx", header->Naxis[0]);
+  set_int_variable ("table:Nfields", Nfields);
+
+  return TRUE;
+}
+
Index: trunk/Ohana/src/opihi/cmd.data/relocate.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/relocate.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/relocate.c	(revision 37807)
@@ -17,4 +17,11 @@
   FREE (name);
 
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
+
   if (argc != 3) {
     gprint (GP_ERR, "USAGE: relocate x y [-n]\n");
Index: trunk/Ohana/src/opihi/cmd.data/resize.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/resize.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/resize.c	(revision 37807)
@@ -17,4 +17,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if ((N = get_argument (argc, argv, "-by-image"))) {
Index: trunk/Ohana/src/opihi/cmd.data/save.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/save.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/save.c	(revision 37807)
@@ -16,4 +16,11 @@
   FREE (name);
 
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
+
   celestial = FALSE;
   if ((N = get_argument (argc, argv, "-c"))) {
Index: trunk/Ohana/src/opihi/cmd.data/shapply.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/shapply.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/shapply.c	(revision 37807)
@@ -0,0 +1,63 @@
+# include "data.h"
+
+int shapply (int argc, char **argv) {
+  
+  int i, j;
+  Vector *Rvec, *Dvec, *Frvec, *Fivec, *Vrvec, *Vivec;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: shapply R D lmax Fr Fi Vr Vi\n");
+    gprint (GP_ERR, "  apply the Ylm values (Fr,Fi) at the given R,D values to get the fit at those positions\n");
+    return (FALSE);
+  }
+
+  if ((Rvec = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if (Rvec->Nelements != Dvec->Nelements) {
+    gprint (GP_ERR, "R and D sizes do not match\n");
+    return FALSE;
+  }
+
+  int lmax = atoi(argv[3]);
+
+  if ((Frvec = SelectVector (argv[4], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[5], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if (Frvec->Nelements != Fivec->Nelements) { 
+    gprint (GP_ERR, "Fr and Fi sizes do not match\n");
+    return FALSE;
+  }
+
+  if ((Vrvec = SelectVector (argv[6], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Vivec = SelectVector (argv[7], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  SHterms *terms = SHtermsInit (lmax);
+  if (Frvec->Nelements != terms->Nterms) {
+    gprint (GP_ERR, "Lmax (%d) terms (%d) does not match size of Fr,Fi vectors (%d) \n", lmax, terms->Nterms, Frvec->Nelements);
+    return FALSE;
+  }
+
+  ResetVector (Vrvec, OPIHI_FLT, Rvec->Nelements);
+  ResetVector (Vivec, OPIHI_FLT, Rvec->Nelements);
+
+  // measure the dot product \sum(F_i * Ylm_i)
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    SHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double *Fr = Frvec->elements.Flt;
+    double *Fi = Fivec->elements.Flt;
+
+    double Vr = 0.0;
+    double Vi = 0.0;
+    for (j = 0; j < terms->Nterms; j++) {
+      Vr += Fr[j] * terms->Fr[j];
+      Vi += Fi[j] * terms->Fi[j];
+    }
+    Vrvec->elements.Flt[i] = Vr;
+    Vivec->elements.Flt[i] = Vi;
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/shdot.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/shdot.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/shdot.c	(revision 37807)
@@ -0,0 +1,69 @@
+# include "data.h"
+
+int shdot (int argc, char **argv) {
+  
+  int i, j;
+  Vector *Rvec, *Dvec, *Fvec, *Lvec, *Mvec, *Frvec, *Fivec;
+  double *Fr, *Fi;
+
+  if (argc != 9) {
+    gprint (GP_ERR, "USAGE: shdot R D value lmax Lout Mout Fr Fi\n");
+    gprint (GP_ERR, "  find the dot product of the scalar field value (at points R,D) to spherical harmonics up to the given lmax\n");
+    return (FALSE);
+  }
+
+  if ((Rvec = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fvec = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[4]);
+
+  if ((Lvec  = SelectVector (argv[5], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Mvec  = SelectVector (argv[6], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Frvec = SelectVector (argv[7], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[8], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  SHterms *terms = SHtermsInit (lmax);
+
+  ALLOCATE_ZERO (Fr, double, terms->Nterms);
+  ALLOCATE_ZERO (Fi, double, terms->Nterms);
+
+  ResetVector (Frvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Fivec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Lvec,  OPIHI_FLT, terms->Nterms);
+  ResetVector (Mvec,  OPIHI_FLT, terms->Nterms);
+
+  // measure the dot product \sum(F_i * Ylm_i)
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    SHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double Fv = Fvec->elements.Flt[i];
+
+    for (j = 0; j < terms->Nterms; j++) {
+      Fr[j] += Fv * terms->Fr[j];
+      Fi[j] += Fv * terms->Fi[j];
+    }
+  }
+
+  // for (j = 0; j < terms->Nterms; j++) {
+  //   fprintf (stderr, "%d : %d %d : %f %f\n", j, terms->l[j], terms->m[j], Fr[j], Fi[j]);
+  // }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    Fr[j] /= Fvec->Nelements;
+    Fi[j] /= Fvec->Nelements;
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    // fprintf (stderr, "%d : %d %d : %f %f\n", j, terms->l[j], terms->m[j], 4*M_PI*Fr[j], 4*M_PI*Fi[j]);
+    Lvec[0].elements.Flt[j]  = terms->l[j];
+    Mvec[0].elements.Flt[j]  = terms->m[j];
+    Frvec[0].elements.Flt[j] = 4*M_PI*Fr[j];
+    Fivec[0].elements.Flt[j] = 4*M_PI*Fi[j];
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/shfit.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/shfit.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/shfit.c	(revision 37807)
@@ -0,0 +1,163 @@
+# include "data.h"
+
+int shfit (int argc, char **argv) {
+  
+  int i, j, k;
+  Vector *Rvec, *Dvec, *Fvec, *Lvec, *Mvec, *Frvec, *Fivec;
+  double *Fr, *Fi;
+
+  if (argc != 9) {
+    gprint (GP_ERR, "USAGE: shfit R D value lmax Lout Mout Fr Fi\n");
+    gprint (GP_ERR, "  fit the scalar field value (at points R,D) to spherical harmonics up to the given lmax\n");
+    return (FALSE);
+  }
+
+  if ((Rvec = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fvec = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[4]);
+
+  if ((Lvec  = SelectVector (argv[5], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Mvec  = SelectVector (argv[6], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Frvec = SelectVector (argv[7], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[8], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  SHterms *terms = SHtermsInit (lmax);
+
+  ALLOCATE_ZERO (Fr, double, terms->Nterms);
+  ALLOCATE_ZERO (Fi, double, terms->Nterms);
+
+  ResetVector (Frvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Fivec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Lvec,  OPIHI_FLT, terms->Nterms);
+  ResetVector (Mvec,  OPIHI_FLT, terms->Nterms);
+
+  // we only fit the linearlly independent terms: Re(m >= 0), Im(m > 0)
+  int Nre = 0;
+  int Nim = 0;
+  for (i = 0; i < terms->Nterms; i++) {
+    if (terms->m[i] >= 0) Nre ++;
+    if (terms->m[i] >  0) Nim ++;
+  }
+  int *Jre = NULL;
+  int *Jim = NULL;
+  ALLOCATE (Jre, int, Nre);
+  ALLOCATE (Jim, int, Nim);
+
+  Nre = Nim = 0;
+  for (i = 0; i < terms->Nterms; i++) {
+    if (terms->m[i] >= 0) {
+      Jre[Nre] = i;
+      Nre ++;
+    }
+    if (terms->m[i] >  0) {
+      Jim[Nim] = i;
+      Nim ++;
+    }
+  }
+
+  double **Are, **bre, **Aim, **bim;
+  ALLOCATE (Are, double *, Nre);
+  ALLOCATE (bre, double *, Nre);
+  ALLOCATE (Aim, double *, Nim);
+  ALLOCATE (bim, double *, Nim);
+  for (i = 0; i < Nre; i++) {
+    ALLOCATE_ZERO (Are[i], double, Nre);
+    ALLOCATE_ZERO (bre[i], double, 1);
+  }
+  for (i = 0; i < Nim; i++) {
+    ALLOCATE_ZERO (Aim[i], double, Nim);
+    ALLOCATE_ZERO (bim[i], double, 1);
+  }
+
+  // measure the dot product \sum(F_i * Ylm_i) and the cross terms (\sum(Y_lm * Y_jk))
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    SHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double Fv = Fvec->elements.Flt[i];
+
+    for (j = 0; j < Nre; j++) {
+      int jre = Jre[j];
+      bre[j][0] += Fv * terms->Fr[jre];
+    }
+    for (j = 0; j < Nim; j++) {
+      int jim = Jim[j];
+      bim[j][0] += Fv * terms->Fi[jim];
+    }
+
+    for (j = 0; j < Nre; j++) {
+      int jre = Jre[j];
+      for (k = j; k < Nre; k++) {
+	int kre = Jre[k];
+	Are[j][k] += terms->Fr[jre] * terms->Fr[kre];
+      }
+    }
+
+    for (j = 0; j < Nim; j++) {
+      int jim = Jim[j];
+      for (k = j; k < Nim; k++) {
+	int kim = Jim[k];
+	Aim[j][k] += terms->Fi[jim] * terms->Fi[kim];
+      }
+    }
+  }
+
+  for (j = 1; j < Nre; j++) {
+    for (k = 0; k < j; k++) {
+      Are[j][k] = Are[k][j];
+    }	
+  }
+  for (j = 1; j < Nim; j++) {
+    for (k = 0; k < j; k++) {
+      Aim[j][k] = Aim[k][j];
+    }	
+  }
+
+  fprintf (stderr, "--- Are --- : bre \n");
+  for (j = 0; j < Nre; j++) {
+    fprintf (stderr, "%10.6f ", Are[j][j]);
+    fprintf (stderr, " : %10.6f\n", bre[j][0]);
+  }
+
+  fprintf (stderr, "--- Aim --- : bim \n");
+  for (i = 0; i < Nim; i++) {
+    fprintf (stderr, "%10.6f : ", Aim[i][i]);
+    fprintf (stderr, " : %10.6f\n", bim[i][0]);
+  }
+
+  if (!dgaussjordan (Are, bre, Nre, 1)) {
+    gprint (GP_ERR, "failed to fit data : ill-conditioned matrix\n");
+    return FALSE;
+  }
+  if (!dgaussjordan (Aim, bim, Nim, 1)) {
+    gprint (GP_ERR, "failed to fit data : ill-conditioned matrix\n");
+    return FALSE;
+  }
+
+  for (j = 0; j < terms->Nterms; j++) { 
+    Fr[j] = 0.0;
+    Fi[i] = 0.0;
+  }
+  for (j = 0; j < Nre; j++) {
+    int jre = Jre[j];
+    Fr[jre] = bre[j][0];
+  }
+  for (j = 0; j < Nim; j++) {
+    int jim = Jim[j];
+    Fi[jim] = bim[j][0];
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    // fprintf (stderr, "%d : %d %d : %f %f\n", j, terms->l[j], terms->m[j], 4*M_PI*Fr[j], 4*M_PI*Fi[j]);
+    Lvec[0].elements.Flt[j]  = terms->l[j];
+    Mvec[0].elements.Flt[j]  = terms->m[j];
+    Frvec[0].elements.Flt[j] = Fr[j];
+    Fivec[0].elements.Flt[j] = Fi[j];
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/shterms.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/shterms.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/shterms.c	(revision 37807)
@@ -0,0 +1,43 @@
+# include "data.h"
+
+int shterms (int argc, char **argv) {
+  
+  int i;
+  Vector *Frvec, *Fivec, *lvec, *mvec;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: shterms Fr Fi l m lmax R D\n");
+    gprint (GP_ERR, "  set the Fr and Fi values for sh terms up to lmax at R,D\n");
+    return (FALSE);
+  }
+
+  if ((Frvec = SelectVector (argv[1], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Fivec = SelectVector (argv[2], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((lvec  = SelectVector (argv[3], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((mvec  = SelectVector (argv[4], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[5]);
+
+  SHterms *terms = SHtermsInit (lmax);
+
+  double R = atof(argv[6]);
+  double D = atof(argv[7]);
+
+  ResetVector (Frvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Fivec, OPIHI_FLT, terms->Nterms);
+  ResetVector (lvec,  OPIHI_FLT, terms->Nterms);
+  ResetVector (mvec,  OPIHI_FLT, terms->Nterms);
+
+  SHtermsForRD (terms, R, D); 
+
+  for (i = 0; i < terms->Nterms; i++) {
+    Frvec[0].elements.Flt[i] = terms->Fr[i];
+    Fivec[0].elements.Flt[i] = terms->Fi[i];
+    lvec[0].elements.Flt[i]  = terms->l[i];
+    mvec[0].elements.Flt[i]  = terms->m[i];
+  }
+
+  SHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/tv.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/tv.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/tv.c	(revision 37807)
@@ -18,4 +18,13 @@
   if (!GetImage (&data, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    remove_argument (N, &argc, argv);
+    channel = GetKapaChannelFromString (argv[N]);
+    remove_argument (N, &argc, argv);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   /* shell exits on pipe close, FIX */
Index: trunk/Ohana/src/opihi/cmd.data/tvchannel.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/tvchannel.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/tvchannel.c	(revision 37807)
@@ -22,23 +22,31 @@
   }
 
-  Nchannel = atoi (argv[1]);
+  Nchannel = GetKapaChannelFromString (argv[1]);
+  if (!Nchannel) return FALSE;
+
+  KiiSetChannel (kapa, Nchannel - 1);
+  return (TRUE);
+}
+
+int GetKapaChannelFromString (char *string) {
+
+  int Nchannel = atoi (string);
   if (Nchannel == 0) {
     // try the string values R/Red, G/Green, B/Blue
-    if (!strcasecmp (argv[1], "R") || !strcasecmp (argv[1], "RED")) {
+    if (!strcasecmp (string, "R") || !strcasecmp (string, "RED")) {
       Nchannel = 1;
     }
-    if (!strcasecmp (argv[1], "G") || !strcasecmp (argv[1], "GREEN")) {
+    if (!strcasecmp (string, "G") || !strcasecmp (string, "GREEN")) {
       Nchannel = 2;
     }
-    if (!strcasecmp (argv[1], "B") || !strcasecmp (argv[1], "BLUE")) {
+    if (!strcasecmp (string, "B") || !strcasecmp (string, "BLUE")) {
       Nchannel = 3;
     }
   }
-  if ((Nchannel < 1) || (Nchannel > 3)) {
-    gprint (GP_ERR, "invalid channel : use 1 - 3 or (R)ed, (G)reen, (B)lue\n");
-    return (FALSE);
+  if ((Nchannel < 1) || (Nchannel > 10)) {
+    gprint (GP_ERR, "invalid channel : use 1 - 10 or (R)ed, (G)reen, (B)lue\n");
+    gprint (GP_ERR, "   (R)ed, (G)reen, (B)lue == (1,2,3)\n");
+    return (0);
   }
-    
-  KiiSetChannel (kapa, Nchannel - 1);
-  return (TRUE);
+  return Nchannel;
 }
Index: trunk/Ohana/src/opihi/cmd.data/tvcontour.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/tvcontour.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/tvcontour.c	(revision 37807)
@@ -71,4 +71,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if ((argc != 4) && (argc != 5)) {
Index: trunk/Ohana/src/opihi/cmd.data/tvgrid.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/tvgrid.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/tvgrid.c	(revision 37807)
@@ -24,4 +24,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   if (argc != 3) {
Index: trunk/Ohana/src/opihi/cmd.data/vload.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/vload.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/vload.c	(revision 37807)
@@ -18,4 +18,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   type = KII_OVERLAY_BOX;
Index: trunk/Ohana/src/opihi/cmd.data/vsh.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/vsh.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/vsh.c	(revision 37807)
@@ -0,0 +1,43 @@
+# include "data.h"
+
+int vsh (int argc, char **argv) {
+  
+  int i;
+  Vector *Rbvec, *Revec, *Dbvec, *Devec;
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: vsh dRb dRe dDb dDe lmax R D\n");
+    gprint (GP_ERR, "  set the dRb, dRe, dDb, dDe values for vsh terms up to lmax at R,D\n");
+    return (FALSE);
+  }
+
+  if ((Rbvec = SelectVector (argv[1], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Revec = SelectVector (argv[2], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dbvec = SelectVector (argv[3], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Devec = SelectVector (argv[4], ANYVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[5]);
+
+  VSHterms *terms = VSHtermsInit (lmax);
+
+  double R = atof(argv[6]);
+  double D = atof(argv[7]);
+
+  ResetVector (Rbvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Revec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Dbvec, OPIHI_FLT, terms->Nterms);
+  ResetVector (Devec, OPIHI_FLT, terms->Nterms);
+
+  VSHtermsForRD (terms, R, D); 
+
+  for (i = 0; i < terms->Nterms; i++) {
+    Rbvec[0].elements.Flt[i] = terms->dR_B[i];
+    Revec[0].elements.Flt[i] = terms->dR_E[i];
+    Dbvec[0].elements.Flt[i] = terms->dD_B[i];
+    Devec[0].elements.Flt[i] = terms->dD_E[i];
+  }
+
+  VSHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/vshfit.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/vshfit.c	(revision 37807)
+++ trunk/Ohana/src/opihi/cmd.data/vshfit.c	(revision 37807)
@@ -0,0 +1,62 @@
+# include "data.h"
+
+int vshfit (int argc, char **argv) {
+  
+  int i, j;
+  Vector *Rvec, *Dvec, *dRvec, *dDvec;
+  double *Re, *Rb, *De, *Db;
+
+  if (argc != 6) {
+    gprint (GP_ERR, "USAGE: vshfit Rvec Dvec dRvec dDvec lmax\n");
+    gprint (GP_ERR, "  fit the vector field dRvec, dDvec (at points Rvec,Dvec) to vector spherical harmonics up to the given lmax\n");
+    return (FALSE);
+  }
+
+  if ((Rvec  = SelectVector (argv[1], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((Dvec  = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((dRvec = SelectVector (argv[3], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+  if ((dDvec = SelectVector (argv[4], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  int lmax = atoi(argv[5]);
+
+  VSHterms *terms = VSHtermsInit (lmax);
+
+  ALLOCATE_ZERO (Re, double, terms->Nterms);
+  ALLOCATE_ZERO (Rb, double, terms->Nterms);
+  ALLOCATE_ZERO (De, double, terms->Nterms);
+  ALLOCATE_ZERO (Db, double, terms->Nterms);
+
+  for (i = 0; i < Rvec->Nelements; i++) {
+
+    VSHtermsForRD (terms, Rvec->elements.Flt[i], Dvec->elements.Flt[i]); 
+
+    double dR = dRvec->elements.Flt[i];
+    double dD = dDvec->elements.Flt[i];
+
+    for (j = 0; j < terms->Nterms; j++) {
+      Rb[j] += dR * terms->dR_B[j];
+      Re[j] += dR * terms->dR_E[j];
+      Db[j] += dD * terms->dD_B[j];
+      De[j] += dD * terms->dD_E[j];
+    }
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    fprintf (stderr, "%d : %d %d : %f %f %f %f\n", j, terms->l[j], terms->m[j], Rb[j], Re[j], Db[j], De[j]);
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    Rb[j] /= terms->Nterms;
+    Re[j] /= terms->Nterms;
+    Db[j] /= terms->Nterms;
+    De[j] /= terms->Nterms;
+  }
+
+  for (j = 0; j < terms->Nterms; j++) {
+    fprintf (stderr, "%d : %d %d : %f %f %f %f\n", j, terms->l[j], terms->m[j], Rb[j], Re[j], Db[j], De[j]);
+  }
+
+  VSHtermsFree (terms);
+
+  return (TRUE);
+}
Index: trunk/Ohana/src/opihi/cmd.data/vzload.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/vzload.c	(revision 37246)
+++ trunk/Ohana/src/opihi/cmd.data/vzload.c	(revision 37807)
@@ -18,4 +18,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   MAX_OUTPUT_SIZE = 10.0;
Index: trunk/Ohana/src/opihi/dvo/Makefile
===================================================================
--- trunk/Ohana/src/opihi/dvo/Makefile	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/Makefile	(revision 37807)
@@ -46,4 +46,5 @@
 $(SRC)/badimages.$(ARCH).o	  	\
 $(SRC)/catdir.$(ARCH).o             	\
+$(SRC)/catname.$(ARCH).o             	\
 $(SRC)/cmatch.$(ARCH).o	  	\
 $(SRC)/cmpload.$(ARCH).o	  	\
Index: trunk/Ohana/src/opihi/dvo/avextract.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/avextract.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/avextract.c	(revision 37807)
@@ -5,5 +5,5 @@
   off_t i, j, n, m;
   int N, Npts, NPTS, last, next, state, Nfields, Nreturn, Ncstack, Nstack;
-  int Nsecfilt, VERBOSE, needMeasures;
+  int Nsecfilt, VERBOSE;
   char **cstack, name[1024];
   void *Signal;
@@ -137,5 +137,5 @@
   // this does all the work of re-packaging the command, calling it on the remote machines, then loading in the results
   if (PARALLEL && !HOST_ID) {
-      int status = HostTableParallelOps (skylist, argc, argv, RESULT_FILE, TRUE, 0, VERBOSE);
+    int status = HostTableParallelOps (skylist, argc, argv, RESULT_FILE, TRUE, 0, VERBOSE);
 
     dbFreeFields (fields, Nfields);
@@ -163,11 +163,8 @@
   }
 
-  // check the requested fields : are all average/secfilt entries, or do we need measures?
-  needMeasures = FALSE;
-  for (i = 0; !needMeasures && (i < Nfields); i++) {
-    if (fields[i].photcode == NULL) continue; // non-measure fields do not have a photcode
-    if (fields[i].photcode[0].type == PHOT_REF) needMeasures = TRUE;
-    if (fields[i].photcode[0].type == PHOT_DEP) needMeasures = TRUE;
-  }
+  // check the requested fields
+  int needMeasure = dbFieldNeedMeasure (fields, Nfields);
+  int needLensobj = dbFieldNeedLensobj (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, TRUE);
 
   // grab data from all selected sky regions
@@ -189,5 +186,7 @@
     catalog.filename = (HOST_ID || PARALLEL_LOCAL) ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_SECF;
-    catalog.catflags |= needMeasures ? LOAD_MEAS : SKIP_MEAS;
+    catalog.catflags |= needMeasure ? LOAD_MEAS : SKIP_MEAS;
+    catalog.catflags |= needLensobj ? LOAD_LENSOBJ : SKIP_LENSOBJ;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = 0;
 
@@ -205,10 +204,22 @@
       // XXX for measure values, this could be optimized for one loop over measures...
 
-      m = catalog.average[j].measureOffset;
-
       dbExtractAveragesInitAve ();  // reset counters for saved fields (costs very little)
 
+      Average *average = &catalog.average[j];
+
+      m = average->measureOffset;
+      Measure *measure = needMeasure ? &catalog.measure[m] : NULL;
+
+      m = average->lensobjOffset;
+      Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+      m = average->starparOffset;
+      StarPar *starpar = needStarpar ? &catalog.starpar[m] : NULL;
+
+      m = j*Nsecfilt;
+      SecFilt *secfilt = &catalog.secfilt[m];
+
       for (n = 0; n < Nfields; n++) {
-	values[n] = dbExtractAverages (&catalog.average[j], &catalog.secfilt[j*Nsecfilt], &catalog.measure[m], &fields[n]);
+	values[n] = dbExtractAverages (average, secfilt, measure, lensobj, starpar, &fields[n]);
       }
 
Index: trunk/Ohana/src/opihi/dvo/avmatch.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/avmatch.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/avmatch.c	(revision 37807)
@@ -149,11 +149,8 @@
   }
 
-  // check the requested fields : are all average/secfilt entries, or do we need measures?
-  int needMeasures = FALSE;
-  for (i = 0; !needMeasures && (i < Nfields); i++) {
-    if (fields[i].photcode == NULL) continue; // assert this?
-    if (fields[i].photcode[0].type == PHOT_REF) needMeasures = TRUE;
-    if (fields[i].photcode[0].type == PHOT_DEP) needMeasures = TRUE;
-  }
+  // check the requested fields
+  int needMeasure = dbFieldNeedMeasure (fields, Nfields);
+  int needLensobj = dbFieldNeedLensobj (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, TRUE);
 
   /* create output storage vectors */
@@ -194,5 +191,7 @@
     catalog.filename = HOST_ID ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_SECF;
-    catalog.catflags |= needMeasures ? LOAD_MEAS : SKIP_MEAS;
+    catalog.catflags |= needMeasure ? LOAD_MEAS    : SKIP_MEAS;
+    catalog.catflags |= needLensobj ? LOAD_LENSOBJ : SKIP_LENSOBJ;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = 0;
 
@@ -219,10 +218,23 @@
       if (Ncat == -2) continue; // no matches to this point
 
-      m = catalog.average[Ncat].measureOffset;
+      m = Ncat;
+      Average *average = &catalog.average[m];
+
+      m = average->measureOffset;
+      Measure *measure = needMeasure ? &catalog.measure[m] : NULL;
+
+      m = average->lensobjOffset;
+      Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+      m = average->starparOffset;
+      StarPar *starpar = needStarpar ? &catalog.starpar[m] : NULL;
+
+      m = Ncat*Nsecfilt;
+      SecFilt *secfilt = &catalog.secfilt[m];
 
       // reset counters for saved fields, extract fields
       dbExtractAveragesInitAve (); 
       for (n = 0; n < Nfields; n++) {
-	values[n] = dbExtractAverages (&catalog.average[Ncat], &catalog.secfilt[Ncat*Nsecfilt], &catalog.measure[m], &fields[n]);
+	values[n] = dbExtractAverages (average, secfilt, measure, lensobj, starpar, &fields[n]);
       }
 
Index: trunk/Ohana/src/opihi/dvo/catname.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/catname.c	(revision 37807)
+++ trunk/Ohana/src/opihi/dvo/catname.c	(revision 37807)
@@ -0,0 +1,104 @@
+# include "dvoshell.h"
+
+int catname (int argc, char **argv) {
+  
+  int i, N;
+  struct stat filestat;
+
+  int ShowHost = FALSE;
+  if ((N = get_argument (argc, argv, "-host"))) {
+    remove_argument (N, &argc, argv);
+    ShowHost = TRUE;
+  }
+  int ShowBackup = FALSE;
+  if ((N = get_argument (argc, argv, "-backup"))) {
+    remove_argument (N, &argc, argv);
+    ShowBackup = TRUE;
+  }
+  int FullName = FALSE;
+  if ((N = get_argument (argc, argv, "-full-name"))) {
+    remove_argument (N, &argc, argv);
+    FullName = TRUE;
+  }
+  int ShowFlags = FALSE;
+  if ((N = get_argument (argc, argv, "-flags"))) {
+    remove_argument (N, &argc, argv);
+    ShowFlags = TRUE;
+  }
+  int PARALLEL = FALSE;
+  if ((N = get_argument (argc, argv, "-parallel"))) {
+    remove_argument (N, &argc, argv);
+    PARALLEL = TRUE;
+  }
+
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: catname name [-host] [-backup] [-flags]\n");
+    return (FALSE);
+  }
+
+  char *CATDIR = GetCATDIR();
+  if (!CATDIR) {
+    gprint (GP_ERR, "CATDIR is not set\n");
+    return FALSE;
+  }
+  SkyTable *sky = GetSkyTable ();
+  if (!sky) {
+    gprint (GP_ERR, "failed to load sky table for database\n");
+    return FALSE;
+  }
+  HostTable *table = HostTableLoad (CATDIR, sky->hosts);
+  if (!table) {
+    gprint (GP_ERR, "ERROR: failure reading Host Table %s for database %s\n", sky->hosts, CATDIR);
+    return FALSE;
+  }    
+  SkyList *skylist = SkyListByName (sky, argv[1]);
+
+  // prepare to handle interrupt signals
+  signal (SIGINT, handle_interrupt);
+  interrupt = FALSE;
+
+  for (i = 0; (i < skylist[0].Nregions) && !interrupt; i++) {
+    char *filename = NULL;
+    if (PARALLEL) {
+      SkyRegion *region = skylist[0].regions[i];
+      int hostID = (region->hostFlags & DATA_USE_BCK) ? region->backupID : region->hostID;
+      int index = table->index[hostID];
+    
+      char hostfile[1024];
+      snprintf (hostfile, 1024, "%s/%s.cpt", table->hosts[index].pathname, region->name);
+      filename = hostfile;
+    } else {
+      filename = skylist[0].filename[i];
+    }
+
+    gprint (GP_ERR, "%3d %s", i, skylist[0].regions[i][0].name);
+    if (stat (filename, &filestat) != -1) {
+      gprint (GP_ERR, " +");
+    } else {
+      gprint (GP_ERR, " -");
+    } 
+    if (ShowHost) {
+      gprint (GP_ERR, "  %3d", skylist[0].regions[i][0].hostID);
+    } else {
+      gprint (GP_ERR, "     ");
+    }
+    if (FullName) {
+      gprint (GP_ERR, "  %s", filename);
+    }
+    if (ShowBackup) {
+      gprint (GP_ERR, "  %3d", skylist[0].regions[i][0].backupID);
+    } else {
+      gprint (GP_ERR, "     ");
+    }
+    if (ShowFlags) {
+      gprint (GP_ERR, "  0x%04x", skylist[0].regions[i][0].hostFlags);
+    } else {
+      gprint (GP_ERR, "        ");
+    }
+    gprint (GP_ERR, "\n");
+    set_str_variable ("CATNAME", filename);
+  }
+
+  return (TRUE);
+  SkyListFree (skylist);
+}
Index: trunk/Ohana/src/opihi/dvo/cmpload.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/cmpload.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/cmpload.c	(revision 37807)
@@ -24,4 +24,11 @@
   if (!GetImage (NULL, &kapa, name)) return (FALSE);
   FREE (name);
+
+  int channel = 0;
+  if ((N = get_argument (argc, argv, "-ch"))) {
+    channel = GetKapaChannelFromString (argv[N]);
+    if (!channel) return FALSE;
+    KiiSetChannel (kapa, channel - 1);
+  }
 
   Objtype = 0;
Index: trunk/Ohana/src/opihi/dvo/dvo_host_utils.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/dvo_host_utils.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/dvo_host_utils.c	(revision 37807)
@@ -137,4 +137,5 @@
 
   // determine the sky region 
+  // XXX EAM 20141230 : this needs to respect the -region selection
   double Rmin, Rmax, Dmin, Dmax;
   get_skyregion (&Rmin, &Rmax, &Dmin, &Dmax);
Index: trunk/Ohana/src/opihi/dvo/find_matches.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/find_matches.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/find_matches.c	(revision 37807)
@@ -38,4 +38,5 @@
    * far from the projection pole. We use the center of the region (catalog) for crval1,2.
    */
+  InitCoords (&tcoords, "DEC--ARC");
   tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
   if (region[0].Dmax < 90) {
@@ -44,11 +45,5 @@
     tcoords.crval2 = 90.0;
   }
-  tcoords.crpix1 = 0;
-  tcoords.crpix2 = 0;
   tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
-  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
-  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
-  tcoords.Npolyterms = 1;
-  strcpy (tcoords.ctype, "DEC--ARC");
 
   // this region includes a boundary layer of size RADIUS
Index: trunk/Ohana/src/opihi/dvo/gimages.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/gimages.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/gimages.c	(revision 37807)
@@ -85,5 +85,5 @@
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
   image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   int DistortImage = wordhash ("-DIS");
@@ -102,5 +102,4 @@
       } 
     }      
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
 
     typehash = wordhash (&image[i].coords.ctype[4]);
@@ -129,12 +128,8 @@
       if (fabs(Ro - Ra) > 120.0) continue;
 
+      InitCoords (&local, "DEC--TAN");
       local.crval1 = Ro;
       local.crval2 = Do;
-      local.crpix1 = local.crpix2 = 0.0;
       local.cdelt1 = local.cdelt2 = 1.0/3600.0;
-      local.pc1_1 = local.pc2_2 = 1.0;
-      local.pc2_1 = local.pc1_2 = 0.0;
-      local.Npolyterms = 1;
-      strcpy (local.ctype, "DEC--TAN");
 
       if (typehash == DistortImage) {
Index: trunk/Ohana/src/opihi/dvo/images.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/images.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/images.c	(revision 37807)
@@ -131,5 +131,5 @@
   /* it is not an error for the database not to have any images */
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (TRUE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   if (SOLO_MOSAIC && photcode) {
@@ -152,4 +152,6 @@
   SetVector (&Yvec, OPIHI_FLT, NPTS);
 
+  Image *mosaic = NULL;
+
   ALLOCATE (plist, int, NPTS);
   n = N = 0;
@@ -157,6 +159,8 @@
     if (ByName && strncmp (image[i].name, name, strlen(name))) continue;
     if (TimeSelect && ((image[i].tzero < tzero) || (image[i].tzero+image[i].trate*image[i].NY > tzero + trange))) continue;
-    if (!(Nmosaic = FindMosaicForImage (image, Nimage, i))) continue;
-    Nmosaic --; // XXX kind of a hack: FindMosaicForImage returns 0 or the mosaic seq number + 1
+
+    mosaic = image[i].parent;
+    Nmosaic = (SOLO_MOSAIC && mosaic) ? mosaic - image : -1;
+
     if (photcode) {
       if ( photcodeEquiv && (photcode[0].code != GetPhotcodeEquivCodebyCode(image[i].photcode))) continue;
@@ -170,15 +174,15 @@
     typehash = wordhash (&image[i].coords.ctype[4]);
 
-    if (photcode && SOLO_MOSAIC) {
+    if (photcode && SOLO_MOSAIC && (Nmosaic >= 0)) {
       // mosaic (DIS) images are not currently given a photcode : plot these via the WRP entries
       /* DIS images represent a field, not a chip */
       if (typehash != ChipImage) continue;
       if (foundMosaic[Nmosaic]) continue;
-      x[0] = -0.5*image[Nmosaic].NX; y[0] = -0.5*image[Nmosaic].NY;
-      x[1] = +0.5*image[Nmosaic].NX; y[1] = -0.5*image[Nmosaic].NY;
-      x[2] = +0.5*image[Nmosaic].NX; y[2] = +0.5*image[Nmosaic].NY;
-      x[3] = -0.5*image[Nmosaic].NX; y[3] = +0.5*image[Nmosaic].NY;
+      x[0] = -0.5*mosaic->NX; y[0] = -0.5*mosaic->NY;
+      x[1] = +0.5*mosaic->NX; y[1] = -0.5*mosaic->NY;
+      x[2] = +0.5*mosaic->NX; y[2] = +0.5*mosaic->NY;
+      x[3] = -0.5*mosaic->NX; y[3] = +0.5*mosaic->NY;
       for (j = 0; j < Npts; j++) {
-	status = XY_to_RD (&r[j], &d[j], x[j], y[j], &image[Nmosaic].coords);
+	status = XY_to_RD (&r[j], &d[j], x[j], y[j], &mosaic->coords);
 	if (!status) break;
 	r[j] = ohana_normalize_angle (r[j]);
@@ -198,6 +202,7 @@
 
     /* DIS images represent a field, not a chip */
-    if ((typehash == DistortImage) && !WITH_MOSAIC) continue;
-    if ((typehash != DistortImage) &&  SOLO_MOSAIC) continue;
+    if ((typehash == DistortImage) && !WITH_MOSAIC) continue; // do not plot the mosaic images
+    if ((typehash != DistortImage) &&  SOLO_MOSAIC) continue; // plot only the mosaic images
+
     if (typehash == DistortImage) {
       x[0] = -0.5*image[i].NX; y[0] = -0.5*image[i].NY;
Index: trunk/Ohana/src/opihi/dvo/imbox.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/imbox.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/imbox.c	(revision 37807)
@@ -43,10 +43,18 @@
   SetVector (&Yvec, OPIHI_FLT, NPTS);
 
+  mosaic.ctype[0] = 0;  
+
   while (gfits_fread_header (f, &header)) {
     if (!GetCoords (&coords, &header)) goto skip;
     if (!SOLO_PHU && !strcmp (&coords.ctype[4], "-DIS")) {
       mosaic = coords;
-      RegisterMosaic (&mosaic);
       goto skip;
+    }
+    if (!strcmp (&coords.ctype[4], "-WRP")) {
+      if (!mosaic.ctype[0]) {
+	fprintf (stderr, "PHU mosaic not found\n");
+	return FALSE;
+      }
+      coords.mosaic = &mosaic;
     }
 
Index: trunk/Ohana/src/opihi/dvo/imdata.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/imdata.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/imdata.c	(revision 37807)
@@ -87,5 +87,5 @@
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
   image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
   GetTimeFormat (&TimeReference, &TimeFormat);
 
@@ -103,5 +103,4 @@
     I = subset[i];
 
-    if (!FindMosaicForImage (image, Nimage, I)) continue;
     skyset = SkyListByImage (sky, -1, &image[I]);
 
Index: trunk/Ohana/src/opihi/dvo/imdense.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/imdense.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/imdense.c	(revision 37807)
@@ -22,5 +22,5 @@
 
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   Rmin = graphmode.coords.crval1 - 182.0;
@@ -42,5 +42,4 @@
     /* project this image to screen display coords */
     status = FALSE;
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     XY_to_RD (&r, &d, x, y, &image[i].coords);
     r = ohana_normalize_angle (r);
Index: trunk/Ohana/src/opihi/dvo/imextract.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/imextract.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/imextract.c	(revision 37807)
@@ -90,5 +90,5 @@
 
   if ((image = LoadImagesDVO (&Nimage)) == NULL) goto escape;
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
   dbExtractImagesInit (); 
 
Index: trunk/Ohana/src/opihi/dvo/imlist.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/imlist.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/imlist.c	(revision 37807)
@@ -38,10 +38,4 @@
   }
 
-  // int RegionSelect = FALSE;
-  // if ((N = get_argument (argc, argv, "-region"))) {
-  //   remove_argument (N, &argc, argv);
-  //   RegionSelect = TRUE;
-  // }
-
   PhotcodeValue = NULL;
   PhotcodeSelect = FALSE;
@@ -76,6 +70,13 @@
   }
 
+  int MAX_LIST = -1;
+  if ((N = get_argument (argc, argv, "-max-list"))) {
+    remove_argument (N, &argc, argv);
+    MAX_LIST = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
   if (argc != 1) {
-    gprint (GP_ERR, "USAGE: image [-time start range] [-region] [-name string]\n");
+    gprint (GP_ERR, "USAGE: image [-time start range] [-region] [-name string] [-photcode code] [-Nphotcode N] [-max-list N]\n");
     return (FALSE);
   }
@@ -83,9 +84,9 @@
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
   image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
-  BuildChipMatch (image, Nimage);
+  MAX_LIST = MAX_LIST < 0 ? Nsubset : MIN(MAX_LIST, Nsubset);
 
   GetTimeFormat (&TimeReference, &TimeFormat);
 
-  for (j = 0; j < Nsubset; j++) {
+  for (j = 0; j < MAX_LIST; j++) {
     i = subset[j];
     if (NameSelect && (strstr (image[i].name, name) == (char *) NULL)) continue;
@@ -97,5 +98,4 @@
       }
     }
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     t = TimeValue (image[i].tzero, TimeReference, TimeFormat);
     if (!strcmp(&image[i].coords.ctype[4], "-DIS")) {
Index: trunk/Ohana/src/opihi/dvo/imstats.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/imstats.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/imstats.c	(revision 37807)
@@ -31,5 +31,5 @@
 
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   /* assign vector values */
@@ -44,5 +44,4 @@
     else 
       Yvec.elements.Flt[i] = image[i].dMcal;
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     XY_to_RD (&r, &d, 0.5*image[i].NX, 0.5*image[i].NY, &image[i].coords);
     gprint (GP_ERR, "%d %8.4f %8.4f %10d %6d  %5.3f %6.3f %6.3f\n", 
Index: trunk/Ohana/src/opihi/dvo/init.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/init.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/init.c	(revision 37807)
@@ -7,4 +7,5 @@
 int calmextract     PROTO((int, char **));
 int catlog          PROTO((int, char **));
+int catname         PROTO((int, char **));
 int catdir_define   PROTO((int, char **));
 int ccd             PROTO((int, char **));
@@ -68,4 +69,5 @@
 //  {1, "calextract",  calextract,   "extract photometry calibration"},
 //  {1, "calmextract", calmextract,  "extract photometry calibration"},
+  {1, "catname",     catname,      "list catalog files by name"},
   {1, "catdir",      catdir_define,"re-define CATDIR"},
 //  {1, "ccd",         ccd,          "plot color-color diagram"},
Index: trunk/Ohana/src/opihi/dvo/mextract.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/mextract.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/mextract.c	(revision 37807)
@@ -211,4 +211,7 @@
   }
 
+  // int needLensing = dbFieldNeedLensing (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, FALSE);
+
   // grab data from all selected sky regions
   Signal = signal (SIGINT, handle_interrupt);
@@ -227,4 +230,6 @@
     catalog.filename = HOST_ID ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_MEAS | LOAD_SECF;
+    // catalog.catflags |= needLensing ? LOAD_LENSING : SKIP_LENSING;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = Nsecfilt;
 
@@ -259,6 +264,18 @@
 	// extract the relevant values for this measurement
 	dbExtractMeasuresInitMeas (); // reset counters for saved fields  (costs very little
+
+	Average *average = &catalog.average[j];
+
+	int Nstarpar = average->starparOffset;
+	StarPar *starpar = needStarpar ? &catalog.starpar[Nstarpar] : NULL;
+
+	// int Nlensing = average->lensobjOffset;
+	// Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+	int Nsec = j*Nsecfilt;
+	SecFilt *secfilt = &catalog.secfilt[Nsec];
+
 	for (n = 0; n < Nfields; n++) {
-	  values[n] = dbExtractMeasures (&catalog.average[j], &catalog.secfilt[j*Nsecfilt], &catalog.measure[m], &fields[n]);
+	  values[n] = dbExtractMeasures (average, secfilt, &catalog.measure[m], NULL, starpar, &fields[n]);
 	}
 	// fprintf (stderr, "object: ave: %f, cat: %f, averef %d\n", fields[n].name, values[2], values[3], catalog.measure[m].averef);
Index: trunk/Ohana/src/opihi/dvo/mmatch.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/mmatch.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/mmatch.c	(revision 37807)
@@ -233,4 +233,7 @@
   ALLOCATE (index, off_t, Nelem);
 
+  // int needLensing = dbFieldNeedLensing (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, FALSE);
+
   // grab data from all selected sky regions
   Signal = signal (SIGINT, handle_interrupt);
@@ -246,4 +249,5 @@
     catalog.filename = HOST_ID ? hostfile : skylist[0].filename[i];
     catalog.catflags = LOAD_AVES | LOAD_SECF | LOAD_MEAS;
+    catalog.catflags |= needStarpar ? LOAD_STARPAR : SKIP_STARPAR;
     catalog.Nsecfilt = Nsecfilt;
 
@@ -282,6 +286,18 @@
 	// extract the relevant values for this measurement
 	dbExtractMeasuresInitMeas (); // reset counters for saved fields  (costs very little
+
+	Average *average = &catalog.average[Ncat];
+
+	int Nstarpar = average->starparOffset;
+	StarPar *starpar = needStarpar ? &catalog.starpar[Nstarpar] : NULL;
+
+	// int Nlensing = average->lensobjOffset;
+	// Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+	int Nsec = Ncat*Nsecfilt;
+	SecFilt *secfilt = &catalog.secfilt[Nsec];
+
 	for (n = 0; n < Nfields; n++) {
-	  values[n] = dbExtractMeasures (&catalog.average[Ncat], &catalog.secfilt[Ncat*Nsecfilt], &catalog.measure[m], &fields[n]);
+	  values[n] = dbExtractMeasures (average, secfilt, &catalog.measure[m], NULL, starpar, &fields[n]);
 	}
 
Index: trunk/Ohana/src/opihi/dvo/mmextract.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/mmextract.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/mmextract.c	(revision 37807)
@@ -174,4 +174,7 @@
   }
 
+  // int needLensing = dbFieldNeedLensing (fields, Nfields);
+  int needStarpar = dbFieldNeedStarpar (fields, Nfields, FALSE);
+
   // grab data from all selected sky regions
   Signal = signal (SIGINT, handle_interrupt);
@@ -213,7 +216,19 @@
 	// extract the relevant values for this measurement 
 	dbExtractMeasuresInitMeas (); // reset counters for saved fields 
+
+	Average *average = &catalog.average[j];
+
+	int Nstarpar = average->starparOffset;
+	StarPar *starpar = needStarpar ? &catalog.starpar[Nstarpar] : NULL;
+
+	// int Nlensing = average->lensobjOffset;
+	// Lensobj *lensobj = needLensobj ? &catalog.lensobj[m] : NULL;
+
+	int Nsec = j*Nsecfilt;
+	SecFilt *secfilt = &catalog.secfilt[Nsec];
+
 	for (n = 0; n < Nfields; n++) {
 	  // values needs to be a pointer to a type with FLT and INT (with a union, we would save a bit of memory...)
-	  values[n] = dbExtractMeasures (&catalog.average[j], &catalog.secfilt[j*Nsecfilt], &catalog.measure[m], &fields[n]);
+	  values[n] = dbExtractMeasures (average, secfilt, &catalog.measure[m], NULL, starpar, &fields[n]);
 	}
 	// fprintf (stderr, "object: ave: %f, cat: %f, averef %d\n", fields[n].name, values[2], values[3], catalog.measure[m].averef);
Index: trunk/Ohana/src/opihi/dvo/objectcoverage.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/objectcoverage.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/objectcoverage.c	(revision 37807)
@@ -147,15 +147,12 @@
   strcpy (buf[0].file, "(empty)");
 
+  InitCoords (&coords, projection);
   coords.crval1 = RaCenter;
   coords.crval2 = DecCenter;
   coords.crpix1 = 0.5*Nx;
   coords.crpix2 = 0.5*Ny;
-  strcpy (coords.ctype, projection);
-  coords.pc1_1 = -1.0;
+  coords.pc1_1 = -1.0; // sky parity
   coords.pc2_2 = +1.0;
-  coords.pc1_2 =  0.0;
-  coords.pc2_1 =  0.0;
   coords.cdelt1 = coords.cdelt2 = pixscale;
-  coords.Npolyterms = 0;
 
   PutCoords (&coords, &buf[0].header);
@@ -166,6 +163,5 @@
     for (xs = 0; xs < Nx; xs++) {
       status = XY_to_RD (&r, &d, (double)(xs), (double)(ys), &coords);
-      status &= (r >= 0);
-      status &= (r <= 360);
+      r = ohana_normalize_angle (r);
       if (status) {
 	V[ys*Nx + xs] = ShowDensity ?  0 : 2;
Index: trunk/Ohana/src/opihi/dvo/showtile.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/showtile.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/showtile.c	(revision 37807)
@@ -28,11 +28,5 @@
 
   /* reference for coords is this image */
-  coords.crpix1 = coords.crpix2 = 0.0;
-  coords.crval1 = coords.crval2 = 0.0;
-  coords.cdelt1 = coords.cdelt2 = 1.0;
-  coords.pc1_1  = coords.pc2_2  = 1.0;
-  coords.pc1_2  = coords.pc2_1  = 0.0;
-  coords.Npolyterms = 0;
-  strcpy (coords.ctype, "DEC--TAN");
+  InitCoords (&coords, "DEC--TAN");
   
   /* fill in top-left region */
Index: trunk/Ohana/src/opihi/dvo/simage.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/simage.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/simage.c	(revision 37807)
@@ -42,37 +42,8 @@
   }
   /* get astrometry information */
-  strcpy (coords.ctype, "NONE");
-  gfits_scan (&header, "CTYPE2",   "%s",  1, coords.ctype);
-  if (strcmp (coords.ctype, "DEC--PLY")) {
-    gprint (GP_ERR, "ERROR: wrong astrometric info in header\n");
+  if (!GetCoords (&coords, &header)) {
+    gprint (GP_ERR, "ERROR: can't get coord info from header\n");
     return (FALSE);
   }
-  gfits_scan (&header, "CDELT1",   "%f", 1, &coords.cdelt1); 
-  gfits_scan (&header, "CDELT2",   "%f", 1, &coords.cdelt2);
-  gfits_scan (&header, "CRVAL1",   "%lf", 1, &coords.crval1);
-  gfits_scan (&header, "CRVAL2",   "%lf", 1, &coords.crval2);  
-  gfits_scan (&header, "CRPIX1",   "%f", 1, &coords.crpix1);
-  gfits_scan (&header, "CRPIX2",   "%f", 1, &coords.crpix2);
-  gfits_scan (&header, "PC001001", "%f", 1, &coords.pc1_1);
-  gfits_scan (&header, "PC001002", "%f", 1, &coords.pc1_2);
-  gfits_scan (&header, "PC002001", "%f", 1, &coords.pc2_1);
-  gfits_scan (&header, "PC002002", "%f", 1, &coords.pc2_2);
-  /* RA Terms */
-  gfits_scan (&header, "PCA1X2Y0", "%f", 1, &coords.polyterms[0][0]);
-  gfits_scan (&header, "PCA1X1Y1", "%f", 1, &coords.polyterms[1][0]);
-  gfits_scan (&header, "PCA1X0Y2", "%f", 1, &coords.polyterms[2][0]);
-  gfits_scan (&header, "PCA1X3Y0", "%f", 1, &coords.polyterms[3][0]);
-  gfits_scan (&header, "PCA1X2Y1", "%f", 1, &coords.polyterms[4][0]);
-  gfits_scan (&header, "PCA1X1Y2", "%f", 1, &coords.polyterms[5][0]);
-  gfits_scan (&header, "PCA1X0Y3", "%f", 1, &coords.polyterms[6][0]);
-  /* Dec Terms */			    
-  gfits_scan (&header, "PCA2X2Y0", "%f", 1, &coords.polyterms[0][1]);
-  gfits_scan (&header, "PCA2X1Y1", "%f", 1, &coords.polyterms[1][1]);
-  gfits_scan (&header, "PCA2X0Y2", "%f", 1, &coords.polyterms[2][1]);
-  gfits_scan (&header, "PCA2X3Y0", "%f", 1, &coords.polyterms[3][1]);
-  gfits_scan (&header, "PCA2X2Y1", "%f", 1, &coords.polyterms[4][1]);
-  gfits_scan (&header, "PCA2X1Y2", "%f", 1, &coords.polyterms[5][1]);
-  gfits_scan (&header, "PCA2X0Y3", "%f", 1, &coords.polyterms[6][1]);
-  coords.Npolyterms = 2; /* how many do we use? */
 
   /* find number of stars */
Index: trunk/Ohana/src/opihi/dvo/skycoverage.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/skycoverage.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/skycoverage.c	(revision 37807)
@@ -204,15 +204,12 @@
   strcpy (buf[0].file, "(empty)");
 
+  InitCoords (&coords, projection);
   coords.crval1 = RaCenter;
   coords.crval2 = DecCenter;
   coords.crpix1 = 0.5*Nx;
   coords.crpix2 = 0.5*Ny;
-  strcpy (coords.ctype, projection);
-  coords.pc1_1 = -1.0;
+  coords.pc1_1 = -1.0; // sky parity
   coords.pc2_2 = +1.0;
-  coords.pc1_2 =  0.0;
-  coords.pc2_1 =  0.0;
   coords.cdelt1 = coords.cdelt2 = pixscale;
-  coords.Npolyterms = 0;
 
   PutCoords (&coords, &buf[0].header);
@@ -222,5 +219,5 @@
       return (FALSE);
   }
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   V = (float *)buf[0].matrix.buffer;
@@ -230,6 +227,6 @@
     for (xs = 0; xs < Nx; xs++) {
       status = XY_to_RD (&r, &d, (double)(xs), (double)(ys), &coords);
-      status &= (r >= 0);
-      status &= (r <= 360);
+      r = ohana_normalize_angle (r);
+
       // are we in a part of the projection covering the sky or not?
       if (status) {
@@ -284,6 +281,4 @@
       }
     }
-
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
 
     typehash = wordhash (&image[i].coords.ctype[4]);
Index: trunk/Ohana/src/opihi/dvo/subpix.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/subpix.c	(revision 37246)
+++ trunk/Ohana/src/opihi/dvo/subpix.c	(revision 37807)
@@ -115,8 +115,7 @@
   /* load all images, extract those touching Ra, Dec */
   if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
-  BuildChipMatch (image, Nimage);
+  // BuildChipMatch (image, Nimage);
 
   for (i = 0; i < Nimage; i++) {
-    if (!FindMosaicForImage (image, Nimage, i)) continue;
     status = RD_to_XY (&X, &Y, Ra, Dec, &image[i].coords);
     if (!status || (X < 0) || (X > image[i].NX) || (Y < 0) || (Y > image[i].NY)) continue;
@@ -134,5 +133,4 @@
   for (i = 0; i < Nsub; i++) {
     I = index[i];
-    if (!FindMosaicForImage (image, Nimage, I)) continue;
     Timage = image[I].tzero;
     for (j = 0; j < Nmeasure; j++) {
Index: trunk/Ohana/src/opihi/include/astro.h
===================================================================
--- trunk/Ohana/src/opihi/include/astro.h	(revision 37246)
+++ trunk/Ohana/src/opihi/include/astro.h	(revision 37807)
@@ -12,25 +12,3 @@
 void FreeAstro (void);
 
-typedef struct {
-  int    isIdentity;	      // identity transformation 
-  double phi;		      // saved in radians
-  double Xo;		      // saved in radians
-  double xo;		      // saved in degrees
-  double sin_phi_cos_Xo;      // pre-computed values
-  double sin_phi_sin_Xo;      // pre-computed values
-  double cos_phi_cos_Xo;      // pre-computed values
-  double cos_phi_sin_Xo;      // pre-computed values
-  double cos_phi;	      // pre-computed values
-  double sin_phi;	      // pre-computed values
-  double cos_Xo;	      // pre-computed values
-  double sin_Xo;	      // pre-computed values
-} CoordTransform;
-
-#ifndef MOVED_TO_LIBDVO
-typedef enum {COORD_NONE, COORD_CELESTIAL, COORD_GALACTIC, COORD_ECLIPTIC} CoordTransformSystem;
-#endif
-
-CoordTransform *InitTransform (CoordTransformSystem input, CoordTransformSystem output);
-int ApplyTransform (double *x, double *y, double X, double Y, CoordTransform *transform);
-
 # endif
Index: trunk/Ohana/src/opihi/include/data.h
===================================================================
--- trunk/Ohana/src/opihi/include/data.h	(revision 37246)
+++ trunk/Ohana/src/opihi/include/data.h	(revision 37807)
@@ -224,3 +224,6 @@
 void ListMedImages ();
 
+// in tvchannel.c
+int GetKapaChannelFromString (char *string);
+
 # endif
Index: trunk/Ohana/src/opihi/include/dvomath.h
===================================================================
--- trunk/Ohana/src/opihi/include/dvomath.h	(revision 37246)
+++ trunk/Ohana/src/opihi/include/dvomath.h	(revision 37807)
@@ -187,4 +187,5 @@
 int           ListBuffersToList     PROTO((char *name));
 int           CreateBuffer          PROTO((Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale));
+int           ResetBuffer           PROTO((Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale));
 Buffer       *SelectBuffer          PROTO((char *name, int mode, int verbose));
 void          dump_buffers          PROTO((int n));  /* deprecated? */
Index: trunk/Ohana/src/opihi/lib.shell/BufferOps.c
===================================================================
--- trunk/Ohana/src/opihi/lib.shell/BufferOps.c	(revision 37246)
+++ trunk/Ohana/src/opihi/lib.shell/BufferOps.c	(revision 37807)
@@ -88,4 +88,12 @@
 }
 
+int ResetBuffer (Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale) {
+
+  gfits_free_matrix (&buf[0].matrix);
+  gfits_free_header (&buf[0].header);
+  CreateBuffer (buf, Nx, Ny, bitpix, bzero, bscale);
+  return TRUE;
+}
+
 int CreateBuffer (Buffer *buf, int Nx, int Ny, int bitpix, float bzero, float bscale) {
 
Index: trunk/Ohana/src/opihi/pantasks/controller_status.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/controller_status.c	(revision 37246)
+++ trunk/Ohana/src/opihi/pantasks/controller_status.c	(revision 37807)
@@ -3,10 +3,17 @@
 int controller_status (int argc, char **argv) {
 
-  int status;
-  char command[1024];
+  int i, status;
+  char command[1024], tmpline[1024];
   IOBuffer buffer;
 
-  if (argc != 1) {
-    gprint (GP_ERR, "USAGE: controller status\n");
+  if ((argc > 1) && (!strcasecmp(argv[1], "help")))  {
+    gprint (GP_ERR, "USAGE: controller status [options]\n");
+    gprint (GP_ERR, "  OPTIONS:\n");
+    gprint (GP_ERR, "  -cmd command\n");
+    gprint (GP_ERR, "  -host hostname\n");
+    gprint (GP_ERR, "  -state state\n");
+    gprint (GP_ERR, "  -age seconds\n");
+    gprint (GP_ERR, "  +jobs [-nohost]\n");
+    gprint (GP_ERR, "  +host [-nojobs]\n");
     return (FALSE);
   }
@@ -19,6 +26,10 @@
   }
 
+  snprintf (command, 1024, "status");
+  for (i = 1; i < argc; i++) {
+    snprintf (tmpline, 1024, "%s %s", command, argv[i]);
+    strcpy (command, tmpline);
+  }
 
-  sprintf (command, "status");
   InitIOBuffer (&buffer, 0x100);
 
Index: trunk/Ohana/src/opihi/pantasks/test/runmacro.sh
===================================================================
--- trunk/Ohana/src/opihi/pantasks/test/runmacro.sh	(revision 37807)
+++ trunk/Ohana/src/opihi/pantasks/test/runmacro.sh	(revision 37807)
@@ -0,0 +1,94 @@
+
+## a basic test of memory allocation
+
+exec rm -f tmp.txt
+verbose off
+$Npass = 0
+
+macro sample.macro
+  echo "in macro"
+  exec date >> tmp.txt
+end
+
+# a basic task which just runs 'echo'
+task	       basic
+  command      date "+starting at %H:%M:%S.%N"
+  host         local
+
+  periods      -poll 0.1
+  periods      -exec 0.2
+  periods      -timeout 2
+  
+  trange       -reset
+  trange       18:38:45 18:39:10 -nmax 2
+  # trange       23:54:45 23:59:59
+  # trange       00:43:15 00:43:20
+  # trange       00:42:20 00:42:30
+  # trange       14:41:30 14:41:40
+
+  stdout tmp.txt
+  stderr tmp.txt
+
+  task.exec
+    sample.macro       
+  end
+
+  # success
+  task.exit    0
+    $Npass ++
+  end
+
+  # default exit status
+  task.exit    default
+    echo       "basic: exit status: $EXIT"
+  end
+
+  # operation times out?
+  task.exit    timeout
+    echo       "basic: timeout"
+  end
+end
+
+macro memcheck.init
+ list word -x "ps -p $PID -o rss"
+ $startmem = $word:1
+end
+macro memcheck
+ list word -x "ps -p $PID -o rss"
+ $endmem = $word:1
+ # echo growth: {$endmem - $startmem}
+end
+
+macro done
+ stop
+ date -var dtime -seconds -reftime $start
+ if ($VERBOSE > 3)
+  status
+  exec wc -l tmp.txt
+ end
+
+ $answer = `wc -l tmp.txt`
+ list word -split $answer
+ if ($word:0 != $Npass) 
+   echo "missing lines in tmp.txt: $word:0"
+ end
+ if ($Npass/$dtime < 4.2) 
+   echo "tasks running too slow: {$Npass/$dtime}"
+ end
+
+ memcheck
+
+ if ($endmem - $startmem > 10)
+   $PASS = 0
+   echo "failed memcheck"
+   echo "growth: {$endmem-$startmem}"
+   echo "kB/loop: {($endmem-$startmem)/10000}"
+ end
+end
+
+memcheck.init
+echo "starting memcheck"
+echo "wait a few seconds and type 'done'"
+
+run
+date -var start -seconds
Index: trunk/Ohana/src/opihi/pantasks/test/trange.sh
===================================================================
--- trunk/Ohana/src/opihi/pantasks/test/trange.sh	(revision 37807)
+++ trunk/Ohana/src/opihi/pantasks/test/trange.sh	(revision 37807)
@@ -0,0 +1,85 @@
+
+## a basic test of memory allocation
+
+exec rm -f tmp.txt
+verbose off
+$Npass = 0
+
+# a basic task which just runs 'echo'
+task	       basic
+  command      date +%H:%M:%S.%N
+  host         local
+
+  periods      -poll 0.1
+  periods      -exec 0.2
+  periods      -timeout 2
+  
+  #trange       00:54:45 00:54:50 -nmax 100
+  trange       -reset
+  trange       23:54:45 23:59:59
+  # trange       00:43:15 00:43:20
+  # trange       00:42:20 00:42:30
+  # trange       14:41:30 14:41:40
+
+  stdout tmp.txt
+  stderr tmp.txt
+
+  # success
+  task.exit    0
+    $Npass ++
+  end
+
+  # default exit status
+  task.exit    default
+    echo       "basic: exit status: $EXIT"
+  end
+
+  # operation times out?
+  task.exit    timeout
+    echo       "basic: timeout"
+  end
+end
+
+macro memcheck.init
+ list word -x "ps -p $PID -o rss"
+ $startmem = $word:1
+end
+macro memcheck
+ list word -x "ps -p $PID -o rss"
+ $endmem = $word:1
+ # echo growth: {$endmem - $startmem}
+end
+
+macro done
+ stop
+ date -var dtime -seconds -reftime $start
+ if ($VERBOSE > 3)
+  status
+  exec wc -l tmp.txt
+ end
+
+ $answer = `wc -l tmp.txt`
+ list word -split $answer
+ if ($word:0 != $Npass) 
+   echo "missing lines in tmp.txt: $word:0"
+ end
+ if ($Npass/$dtime < 4.2) 
+   echo "tasks running too slow: {$Npass/$dtime}"
+ end
+
+ memcheck
+
+ if ($endmem - $startmem > 10)
+   $PASS = 0
+   echo "failed memcheck"
+   echo "growth: {$endmem-$startmem}"
+   echo "kB/loop: {($endmem-$startmem)/10000}"
+ end
+end
+
+memcheck.init
+echo "starting memcheck"
+echo "wait a few seconds and type 'done'"
+
+run
+date -var start -seconds
Index: trunk/Ohana/src/opihi/pcontrol/Makefile
===================================================================
--- trunk/Ohana/src/opihi/pcontrol/Makefile	(revision 37246)
+++ trunk/Ohana/src/opihi/pcontrol/Makefile	(revision 37807)
@@ -11,5 +11,5 @@
 
 # programs may add their own internal requirements here
-LIBS1         = -lkapa -lFITS -lohana
+LIBS1         = -ldvo -lkapa -lFITS -lohana
 LIBS2         = -lbasiccmd -lshell -ldata 
 FULL_CFLAGS   = $(BASE_CFLAGS)
Index: trunk/Ohana/src/opihi/pcontrol/status.c
===================================================================
--- trunk/Ohana/src/opihi/pcontrol/status.c	(revision 37246)
+++ trunk/Ohana/src/opihi/pcontrol/status.c	(revision 37807)
@@ -85,5 +85,5 @@
 
 usage:
-  gprint (GP_ERR, "USAGE: status [-cmd command] [-host hostname] [-state state] [-age seconds] [+jobs,-nohost] [+host,-nojobs]\n");
+  gprint (GP_LOG, "USAGE: status [-cmd command] [-host hostname] [-state state] [-age seconds] [+jobs,-nohost] [+host,-nojobs]\n");
   return FALSE;
 }
