Index: /trunk/ippconfig/gpc1/Makefile.am
===================================================================
--- /trunk/ippconfig/gpc1/Makefile.am	(revision 27217)
+++ /trunk/ippconfig/gpc1/Makefile.am	(revision 27218)
@@ -18,4 +18,5 @@
 	ppImage.config \
         ppMerge.config \
+	ppStack.config \
 	psphot.config \
 	psastro.config \
Index: /trunk/ippconfig/gpc1/camera.config
===================================================================
--- /trunk/ippconfig/gpc1/camera.config	(revision 27217)
+++ /trunk/ippconfig/gpc1/camera.config	(revision 27218)
@@ -119,4 +119,5 @@
 	PSWARP		STR	gpc1/pswarp.config
 	REJECTIONS      STR     gpc1/rejections.config       # rejection recipe
+	PPSTACK		STR	gpc1/ppStack.config
 END
 
Index: /trunk/ippconfig/gpc1/ppStack.config
===================================================================
--- /trunk/ippconfig/gpc1/ppStack.config	(revision 27218)
+++ /trunk/ippconfig/gpc1/ppStack.config	(revision 27218)
@@ -0,0 +1,14 @@
+ZP.AIRMASS      METADATA                # Airmass terms by filter
+        g.00000       F32     0.0
+        r.00000       F32     0.0
+        i.00000       F32     0.0
+        z.00000       F32     0.0
+        y.00000       F32     0.0
+END
+ZP.TARGET       METADATA	        # Target zero point by filter
+        g.00000       F32     25.0
+        r.00000       F32     25.0
+        i.00000       F32     25.0
+        z.00000       F32     25.0
+        y.00000       F32     25.0
+END
Index: /trunk/ippconfig/recipes/ppStack.config
===================================================================
--- /trunk/ippconfig/recipes/ppStack.config	(revision 27217)
+++ /trunk/ippconfig/recipes/ppStack.config	(revision 27218)
@@ -62,4 +62,11 @@
 	y	F32	0.0
 END
+ZP.TARGET	METADATA		# Target zero point by filter
+	g	F32	25.0
+	r	F32	25.0
+	i	F32	25.0
+	z	F32	25.0
+	y	F32	25.0
+END
 
 PSF.INSTANCES	S32	15		# Number of instances for PSF generation
Index: /trunk/ppStack/src/ppStackConvolve.c
===================================================================
--- /trunk/ppStack/src/ppStackConvolve.c	(revision 27217)
+++ /trunk/ppStack/src/ppStackConvolve.c	(revision 27218)
@@ -266,8 +266,10 @@
         UPDATE_CONCEPT(outCell, "CELL.EXPOSURE", options->sumExposure);
         UPDATE_CONCEPT(outCell, "CELL.DARKTIME", NAN);
+        UPDATE_CONCEPT(outFPA,  "FPA.ZP",        options->zp);
 
         UPDATE_CONCEPT(unconvFPA,  "FPA.EXPOSURE",  options->sumExposure);
         UPDATE_CONCEPT(unconvCell, "CELL.EXPOSURE", options->sumExposure);
         UPDATE_CONCEPT(unconvCell, "CELL.DARKTIME", NAN);
+        UPDATE_CONCEPT(unconvFPA,  "FPA.ZP",        options->zp);
     }
 
Index: /trunk/ppStack/src/ppStackOptions.c
===================================================================
--- /trunk/ppStack/src/ppStackOptions.c	(revision 27217)
+++ /trunk/ppStack/src/ppStackOptions.c	(revision 27218)
@@ -58,4 +58,6 @@
     options->quality = 0;
     options->psf = NULL;
+    options->sumExposure = NAN;
+    options->zp = NAN;
     options->inputSeeing = NULL;
     options->targetSeeing = NAN;
Index: /trunk/ppStack/src/ppStackOptions.h
===================================================================
--- /trunk/ppStack/src/ppStackOptions.h	(revision 27217)
+++ /trunk/ppStack/src/ppStackOptions.h	(revision 27218)
@@ -23,4 +23,5 @@
     float targetSeeing;                 // Target seeing FWHM
     float sumExposure;                  // Sum of exposure times
+    float zp;                           // Zero point for output
     psVector *inputMask;                // Mask for inputs
     psArray *sourceLists;               // Individual lists of sources for matching
Index: /trunk/ppStack/src/ppStackSources.c
===================================================================
--- /trunk/ppStack/src/ppStackSources.c	(revision 27217)
+++ /trunk/ppStack/src/ppStackSources.c	(revision 27218)
@@ -119,11 +119,19 @@
         return false;
     }
+    psMetadata *zpTargetMenu = psMetadataLookupMetadata(NULL, recipe, "ZP.TARGET"); // Target zero point terms
+    if (!zpTargetMenu) {
+        psError(PPSTACK_ERR_CONFIG, false, "Unable to find ZP.TARGET in recipe.");
+        return false;
+    }
 
     int num = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of inputs
     psAssert(num == sourceLists->n, "Wrong number of source lists: %ld\n", sourceLists->n);
 
-    psVector *zp = psVectorAlloc(num, PS_TYPE_F32); // Zero points for each image
+    psVector *zp = psVectorAlloc(num, PS_TYPE_F32); // Relative zero points for each image
+    psVector *zpExp = psVectorAlloc(num, PS_TYPE_F32); // Measured zero points for each image (maybe)
+    int zpExpNum = 0;                                  // Number of measured zero points
     const char *filter = NULL;          // Filter name
     float airmassTerm = NAN;            // Airmass term
+    float zpTarget = NAN;               // Target zero point
     float sumExpTime = 0.0;             // Sum of the exposure time
     int numGoodImages = 0;              // Number of good images
@@ -160,4 +168,8 @@
         float airmass = psMetadataLookupF32(NULL, file->fpa->concepts, "FPA.AIRMASS"); // Airmass
         const char *expFilter = psMetadataLookupStr(NULL, file->fpa->concepts, "FPA.FILTER"); // Filter name
+        zpExp->data.F32[i] = psMetadataLookupF32(NULL, file->fpa->concepts, "FPA.ZP"); // Exposure zero point
+        psLogMsg("ppStack", PS_LOG_INFO,
+                 "Image %d: %.2f sec exposure in %s at airmass %.2f with zero point %.2f",
+                 i, exptime, expFilter, airmass, zpExp->data.F32[i]);
         if (!isfinite(exptime) || exptime == 0 || !isfinite(airmass) || airmass == 0 ||
             !expFilter || strlen(expFilter) == 0) {
@@ -168,9 +180,14 @@
             return false;
         }
+        if (isfinite(zpExp->data.F32[i])) {
+            zpExp->data.F32[i] += 2.5 * log10(exptime);
+            zpExpNum++;
+        }
 
         if (!filter) {
             filter = expFilter;
-            airmassTerm = psMetadataLookupF32(NULL, airmassZP, filter);
-            if (!isfinite(airmassTerm)) {
+            bool mdok;
+            airmassTerm = psMetadataLookupF32(&mdok, airmassZP, filter);
+            if (!mdok || !isfinite(airmassTerm)) {
                 psError(PPSTACK_ERR_CONFIG, false,
                         "Unable to find airmass term (ZP.AIRMASS) for filter %s", filter);
@@ -178,4 +195,11 @@
                 return false;
             }
+            zpTarget = psMetadataLookupF32(&mdok, zpTargetMenu, filter);
+            if (!mdok || !isfinite(zpTarget)) {
+                psError(PPSTACK_ERR_CONFIG, false,
+                        "Unable to find target zero point (ZP.TARGET) for filter %s", filter);
+                psFree(zp);
+                return false;
+            }
         } else if (strcmp(filter, expFilter) != 0) {
             psError(PPSTACK_ERR_CONFIG, false, "Filters don't match: %s vs %s", filter, expFilter);
@@ -184,6 +208,7 @@
         }
 
-        zp->data.F32[i] = airmassTerm * airmass - 2.5 * log10(exptime);
+        zp->data.F32[i] = airmassTerm * airmass + 2.5 * log10(exptime);
         sumExpTime += exptime;
+
     }
 
@@ -193,4 +218,6 @@
         psLogMsg("ppStack", PS_LOG_WARN, "No images with sources to measure transparency.");
         options->quality = PPSTACK_ERR_REJECTED;
+        psFree(zp);
+        psFree(zpExp);
         return true;
     }
@@ -205,5 +232,13 @@
         options->sources = psMemIncrRefCounter(sources);
         psLogMsg("ppStack", PS_LOG_WARN, "Single image with sources --- no need to match transparency.");
+        psFree(zp);
+        psFree(zpExp);
         return true;
+    }
+
+    if (zpExpNum == numGoodImages) {
+        for (int i = 0; i < num; i++) {
+            zp->data.F32[i] = zpExp->data.F32[i];
+        }
     }
 
@@ -244,8 +279,13 @@
                 }
                 psArray *sources = sourceLists->data[i]; // Sources of interest
-                float magCorr = airmassTerm - 2.5*log10(sumExpTime) - zp->data.F32[i] - trans->data.F32[i];
+                float magCorr = zp->data.F32[i] + trans->data.F32[i] - 2.5*log10(sumExpTime);
+                if (zpExpNum == numGoodImages) {
+                    // Using measured zero points, so attempt to set target zero point
+                    magCorr -= zpTarget;
+                }
                 options->norm->data.F32[i] = magCorr;
-                psLogMsg("ppStack", PS_LOG_INFO, "Applying magnitude correction to image %d: %f\n",
-                         i, magCorr);
+                psLogMsg("ppStack", PS_LOG_INFO,
+                         "Applying scale correction to image %d: %f mag (%f)\n",
+                         i, magCorr, trans->data.F32[i]);
 
                 for (int j = 0; j < sources->n; j++) {
@@ -258,4 +298,12 @@
             }
         }
+
+        if (zpExpNum == numGoodImages) {
+            // Producing image with target zero point
+            options->zp = zpTarget;
+        } else {
+            options->zp = NAN;
+        }
+
 
 #ifdef TESTING
