Index: trunk/Ohana/src/addstar/Makefile
===================================================================
--- trunk/Ohana/src/addstar/Makefile	(revision 24921)
+++ trunk/Ohana/src/addstar/Makefile	(revision 24973)
@@ -58,4 +58,5 @@
 $(SRC)/greference.$(ARCH).o \
 $(SRC)/grefstars.$(ARCH).o \
+$(SRC)/GetZeroPointExposure.$(ARCH).o \
 $(SRC)/LoadStars.$(ARCH).o \
 $(SRC)/LoadHeaders.$(ARCH).o \
Index: trunk/Ohana/src/addstar/doc/notes.txt
===================================================================
--- trunk/Ohana/src/addstar/doc/notes.txt	(revision 24921)
+++ trunk/Ohana/src/addstar/doc/notes.txt	(revision 24973)
@@ -1,2 +1,49 @@
+
+2009.08.02
+
+  ZERO POINTS: addstar currently has a couple of inconsistent ways that it 
+  measures or uses image zero points:
+
+  * the standard zero point: data in the database is saved as M_inst +
+    25.0.  This value is written to the image header and checked when
+    the image is loaded.  This is completely archaic, but removing
+    this concept from the DVO system will require some work and some
+    care.  This value is NOT reflected in the image->Mcal values
+
+  * on-the-fly calibration: addstar was used by CFHT skyprobe to
+    measure the sky transparency.  This calculation was done by
+    measuring the zero point between the Tycho stars in the database
+    and the stars in the image.  Star data is saved in
+    AddToCalibration as they are found (in find_matches, or
+    equivalent).  At the end of the match processing, the zero point
+    for the image was calculated with FindCalibration and saved to the
+    image table but NOT the measure table.  For skyprobe, the stars
+    are normally not saved to the database -- the Tycho stars are kept
+    in the measure & average tables, and used as the reference for the
+    zero point calculation above.
+
+  For Pan-STARRS, the zero point analysis is performed by psastro
+  before we run addstar.  However, there are a few ways we could
+  choose to apply the zero point calculation:
+
+  * use the per-chip zero point reported in the individual chip
+    headers (easy, but fairly wrong)
+
+  * calculate the per-exposure zero point from the chip headers and
+    supply.
+
+  * calculate the per-exposure zero point from the MATCHED_REFS table
+
+  We also need to consider the zero point error.  We currently have
+  only dM (poisson error) + dMcal (calibration error).  We need to
+  divide dMsys into dMcal (photometry calibration error) and dMap
+  (aperture correction error).
+
+  * zero point options:
+    * NOMINAL : set Mcal = 0.0
+    * CHIP_HEADER : each chip has zero point in header
+    * PHU_HEADER : a single exposure-wide value in the header
+    * CHIP_AVERAGE : determine zero point for exposure from chip zero points
+    * MATCHED_REFS : recalculate from table
 
 2009.02.11
Index: trunk/Ohana/src/addstar/include/addstar.h
===================================================================
--- trunk/Ohana/src/addstar/include/addstar.h	(revision 24921)
+++ trunk/Ohana/src/addstar/include/addstar.h	(revision 24973)
@@ -118,4 +118,7 @@
 double  FAKE_THETA;     // boresite angle for fake images
 
+char    ZERO_POINT_OPTION[64];
+float	ZERO_POINT_OFFSET;
+
 // carries the mosaic into gstars
 
@@ -187,4 +190,5 @@
 HeaderSet *MatchHeaders           PROTO((int **extsize, int *nimage, int mode, Header **headers, int Nheaders));
 int        LoadData               PROTO((FILE *f, char *file, Image **images, int *nvalid, Stars **stars, int *Nstars, Header **headers, int *extsize, HeaderSet *headerSets, int NheaderSets));
+int        GetZeroPointExposure   PROTO((Header **headers, HeaderSet *headerSets, int Nimages));
 
 int        in_image               PROTO((double r, double d, Image *image));
Index: trunk/Ohana/src/addstar/src/ConfigInit.c
===================================================================
--- trunk/Ohana/src/addstar/src/ConfigInit.c	(revision 24921)
+++ trunk/Ohana/src/addstar/src/ConfigInit.c	(revision 24973)
@@ -77,4 +77,9 @@
   if (!ScanConfig (config, "EXTNAME-KEYWORD",        "%s",  0, ExtnameKeyword)) {
       strcpy (ExtnameKeyword, "EXTNAME"); 
+  }
+
+  // if this config variable is not set, do not set any zero point offset
+  if (!ScanConfig (config, "ZERO_POINT_OPTION", "%s",  0, ZERO_POINT_OPTION)) {
+      strcpy (ZERO_POINT_OPTION, "NOMINAL"); 
   }
 
Index: trunk/Ohana/src/addstar/src/GetZeroPointExposure.c
===================================================================
--- trunk/Ohana/src/addstar/src/GetZeroPointExposure.c	(revision 24973)
+++ trunk/Ohana/src/addstar/src/GetZeroPointExposure.c	(revision 24973)
@@ -0,0 +1,96 @@
+# include "addstar.h"
+
+// XXX this needs to handle the nominal zero point for the photcode(s) and the airmass term
+int GetZeroPointExposure (Header **headers, HeaderSet *headerSets, int Nimages) {
+
+    if (!strcasecmp(ZERO_POINT_OPTION, "NOMINAL")) {
+	ZERO_POINT_OFFSET = 0.0;
+	return (TRUE);
+    }
+
+    if (!strcasecmp(ZERO_POINT_OPTION, "CHIP_HEADER")) {
+	ZERO_POINT_OFFSET = 0.0;
+	return (TRUE);
+    }
+
+    if (!strcasecmp(ZERO_POINT_OPTION, "CHIP_AVERAGE")) {
+	int i, Nzpt, Nmid, Nhead;
+	float *zpt, ZPT_OBS;
+	PhotCode *photcode;
+	char photname[80];
+
+	Nzpt = 0;
+	ALLOCATE (zpt, float, Nimages);
+
+	for (i = 0; i < Nimages; i++) {
+	    if (!strcmp(headerSets[i].exthead, "PHU")) continue;
+	    Nhead = headerSets[i].extnum_head;
+
+	    if (!gfits_scan (headers[Nhead], "ZPT_OBS", "%f", 1, &ZPT_OBS)) {
+		fprintf (stderr, "zero point not supplied in header\n");
+		continue;
+	    }
+	    
+	    /* get photcode from header */
+	    if (!gfits_scan (headers[Nhead], "PHOTCODE", "%s", 1, photname)) {
+		fprintf (stderr, "photcode not supplied in header\n");
+		continue;
+	    }
+	    photcode = GetPhotcodebyName (photname);
+	    if (photcode == NULL) {
+		fprintf (stderr, "photcode %s not found in photcode table\n", photname);
+		continue;
+	    }
+	    // photcode table stores zero point in milli-mags :
+	    // Mrel = measure[0].M - ZERO_POINT + code[0].K*(measure[0].airmass - 1.000) + SCALE*code[0].C - measure[0].Mcal;
+	    // ZERO_POINT above is the standard 25.0
+	    // measure[0].M = Mobs + 2.5log(exptime)
+	    // Mrel should be equivalent to Mref.  thus Mref - Mobs - 2.5log(exptime) = -Mcal
+
+	    zpt[Nzpt] = 0.001*photcode[0].C - ZPT_OBS;
+	    Nzpt ++;
+	}
+	    
+	if (Nzpt == 0) {
+	    fprintf (stderr, "WARNING: zero point is not measured, no valid entries in headers\n");
+	    ZERO_POINT_OFFSET = 0.0;
+	    free (zpt);
+	    return (FALSE);
+	} 
+
+	fsort (zpt, Nzpt);
+	Nmid = Nzpt / 2;  // Nzpt = 1,2,3,4 -> Nmid = 0,1,1,2  2: (0 1), 4: 0 (1 2) 3 
+	ZERO_POINT_OFFSET = (Nzpt % 2) ? zpt[Nmid] : 0.5*(zpt[Nmid] + zpt[Nmid-1]);
+	free (zpt);
+	return (TRUE);
+    }
+    
+    // this option is not consistent with the options for photcodes
+    if (!strcasecmp(ZERO_POINT_OPTION, "PHU_HEADER")) {
+	int i, Nhead;
+	float ZPT_OBS;
+
+	ZERO_POINT_OFFSET = 0.0;
+	for (i = 0; i < Nimages; i++) {
+	    if (strcmp(headerSets[i].exthead, "PHU")) continue;
+	    Nhead = headerSets[i].extnum_head;
+	    
+	    if (!gfits_scan (headers[Nhead], "ZPT_OBS", "%f", 1, &ZPT_OBS)) {
+		fprintf (stderr, "WARNING: zero point is not measured, no valid entries in headers\n");
+		return (FALSE);
+	    }
+	    ZERO_POINT_OFFSET = ZPT_OBS;
+	    return (TRUE);
+	}
+	fprintf (stderr, "WARNING: zero point is not measured, no valid entries in headers\n");
+	return (FALSE);
+    }
+
+    if (!strcasecmp(ZERO_POINT_OPTION, "MATCHED_REFS")) {
+	fprintf (stderr, "ERROR: MATCHED_REFS zero point analysis is not implemented\n");
+	exit (1);
+    }
+
+    fprintf (stderr, "ERROR: invalid value for ZERO_POINT_OPTION: %s\n", ZERO_POINT_OPTION);
+    exit (1);
+}
Index: trunk/Ohana/src/addstar/src/LoadData.c
===================================================================
--- trunk/Ohana/src/addstar/src/LoadData.c	(revision 24921)
+++ trunk/Ohana/src/addstar/src/LoadData.c	(revision 24973)
@@ -24,4 +24,8 @@
   // find image rootname
   name = filebasename (file);
+
+  // if zero points are calculated for the full exposure using more than just the matched chip header,
+  // we need to perform that analysis here
+  GetZeroPointExposure (headers, headerSets, Nimages);
 
   // now run through the images, interpret the headers and read the stars
Index: trunk/Ohana/src/addstar/src/ReadImageHeader.c
===================================================================
--- trunk/Ohana/src/addstar/src/ReadImageHeader.c	(revision 24921)
+++ trunk/Ohana/src/addstar/src/ReadImageHeader.c	(revision 24973)
@@ -8,4 +8,5 @@
   double tmp, sec, Cerror, ZeroPt, FWHM_X, FWHM_Y;
   char *c, photname[64], line[80];
+  PhotCode *photcodeData;
 
   // zero out the entire image structure
@@ -104,9 +105,10 @@
       return (FALSE);
     }
-    photcode = GetPhotcodeCodebyName (photname);
-    if (photcode == 0) {
+    photcodeData = GetPhotcodebyName (photname);
+    if (photcodeData == NULL) {
       fprintf (stderr, "photcode %s not found in photcode table\n", photname);
       return (FALSE);
     }
+    photcode = photcodeData[0].code;
   }
   if (photcode == 0) { 
@@ -191,4 +193,8 @@
   }
 
+  // XXX this is archaic: we used to set a fixed zero point of 25 to shift data into the range
+  // 0 - 32 so it would fit in an unsigned int.  This is also needed because some programs like
+  // sextractor will put in an arbitrary zero point that we need to understand to get back to
+  // instrumental mags.  
   gfits_scan (header, "ZERO_PT", "%lf", 1, &ZeroPt);
   if (ZeroPt != GetZeroPoint()) {
@@ -197,6 +203,17 @@
   }
 
+  // if it exists, find the 
+  if (!strcasecmp(ZERO_POINT_OPTION, "CHIP_HEADER")) {
+      float ZPT_OBS;
+      if (!gfits_scan (header, "ZPT_OBS", "%f", 1, &ZPT_OBS)) {
+	  fprintf (stderr, "zero point not supplied in header\n");
+	  ZERO_POINT_OFFSET = 0.0;
+      } else {
+	  ZERO_POINT_OFFSET = 0.001*photcodeData[0].C - ZPT_OBS;
+      }	  
+  }
+
   /* secz is in units milli-airmass */
-  image[0].Mcal = 0.0;
+  image[0].Mcal = ZERO_POINT_OFFSET;
   image[0].Xm   = NAN_S_SHORT;
   image[0].flags = 0;
