Index: trunk/psModules/src/concepts/Makefile.am
===================================================================
--- trunk/psModules/src/concepts/Makefile.am	(revision 7835)
+++ trunk/psModules/src/concepts/Makefile.am	(revision 7849)
@@ -5,4 +5,5 @@
 libpsmoduleconcepts_la_SOURCES  = \
 	pmConcepts.c \
+	pmConceptsAverage.c \
 	pmConceptsRead.c \
 	pmConceptsWrite.c \
@@ -13,4 +14,5 @@
 psmoduleinclude_HEADERS = \
 	pmConcepts.h \
+	pmConceptsAverage.h \
 	pmConceptsRead.h \
 	pmConceptsWrite.h \
Index: trunk/psModules/src/concepts/pmConceptsAverage.c
===================================================================
--- trunk/psModules/src/concepts/pmConceptsAverage.c	(revision 7849)
+++ trunk/psModules/src/concepts/pmConceptsAverage.c	(revision 7849)
@@ -0,0 +1,132 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmConcepts.h"
+#include "pmFPA.h"
+
+#include "pmConceptsAverage.h"
+
+
+// Update a metadata entry directly
+#define MD_UPDATE(MD, NAME, TYPE, VALUE) \
+{ \
+    psMetadataItem *item = psMetadataLookup(MD, NAME); \
+    item->data.TYPE = VALUE; \
+}
+
+
+// Set a variety of concepts in a cell by averaging over several
+bool pmConceptsAverageCells(pmCell *target,// Target cell
+                            psList *sources, // List of source cells
+                            int xBin, int yBin, // Binning in x and y
+                            psRegion *trimsec, // The trim section
+                            psRegion *biassec // The bias section
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_INT_POSITIVE(sources->n, false);
+    PS_ASSERT_INT_NONNEGATIVE(xBin, false);
+    PS_ASSERT_INT_NONNEGATIVE(yBin, false);
+    PS_ASSERT_PTR_NON_NULL(trimsec, false);
+
+    bool success = true;                // Result of setting everything
+    float gain       = 0.0;             // Gain
+    float readnoise  = 0.0;             // Read noise
+    float saturation = INFINITY;        // Saturation level
+    float bad        = -INFINITY;       // Bad level
+    float exposure   = 0.0;             // Exposure time
+    float darktime   = 0.0;             // Dark time
+    double time      = 0.0;             // Time of observation
+    psTimeType timeSys = 0;             // Time system
+    int readdir      = 0;               // Cell read direction
+
+    int nCells = 0;                     // Number of cells;
+    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
+    pmCell *cell = NULL;                // Source cell from iteration
+    while ((cell = psListGetAndIncrement(sourcesIter))) {
+        if (!cell) {
+            continue;
+        }
+
+        nCells++;
+        gain       += psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN");
+        readnoise  += psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE");
+        exposure   += psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE");
+        darktime   += psMetadataLookupF32(NULL, cell->concepts, "CELL.DARKTIME");
+        psTime *cellTime = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TIME");
+        time       += psTimeToMJD(cellTime);
+        if (nCells == 1) {
+            timeSys = psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS");
+            readdir = psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR");
+        } else {
+            if (timeSys != psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS")) {
+                psLogMsg(__func__, PS_LOG_ERROR, "Differing time systems in use: %d vs %d\n", timeSys,
+                         psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS"));
+                success = false;
+            }
+            if (readdir != psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR")) {
+                psLogMsg(__func__, PS_LOG_ERROR, "Differing cell read directions in use: %d vs %d\n", readdir,
+                         psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR"));
+                success = false;
+            }
+        }
+
+        float cellSaturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
+        if (cellSaturation < saturation) {
+            saturation = cellSaturation;
+        }
+        float cellBad = psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD");
+        if (cellBad > bad) {
+            bad = cellBad;
+        }
+    }
+    psFree(sourcesIter);
+
+    gain      /= (float)nCells;
+    readnoise /= (float)nCells;
+    exposure  /= (float)nCells;
+    darktime  /= (float)nCells;
+    time      /= (double)nCells;
+
+    MD_UPDATE(target->concepts, "CELL.GAIN", F32, gain);
+    MD_UPDATE(target->concepts, "CELL.READNOISE", F32, readnoise);
+    MD_UPDATE(target->concepts, "CELL.SATURATION", F32, saturation);
+    MD_UPDATE(target->concepts, "CELL.BAD", F32, bad);
+    MD_UPDATE(target->concepts, "CELL.EXPOSURE", F32, exposure);
+    MD_UPDATE(target->concepts, "CELL.DARKTIME", F32, darktime);
+    MD_UPDATE(target->concepts, "CELL.TIMESYS", S32, timeSys);
+    MD_UPDATE(target->concepts, "CELL.X0", S32, 0);
+    MD_UPDATE(target->concepts, "CELL.Y0", S32, 0);
+    if (xBin > 0) {
+        MD_UPDATE(target->concepts, "CELL.XBIN", S32, xBin);
+    }
+    if (yBin > 0) {
+        MD_UPDATE(target->concepts, "CELL.YBIN", S32, yBin);
+    }
+    MD_UPDATE(target->concepts, "CELL.READDIR", S32, readdir);
+
+    // CELL.TIME needs special care
+    {
+        psMetadataItem *timeItem = psMetadataLookup(target->concepts, "CELL.TIME");
+        psFree(timeItem->data.V);
+        timeItem->data.V = psTimeFromMJD(time);
+    }
+
+    // CELL.TRIMSEC needs special care
+    if (trimsec) {
+        psMetadataItem *trimsecItem = psMetadataLookup(target->concepts, "CELL.TRIMSEC");
+        psFree(trimsecItem->data.V);
+        trimsecItem->data.V = psMemIncrRefCounter(trimsec);
+    }
+
+    // CELL.BIASSEC needs special care
+    if (biassec) {
+        psMetadataItem *biassecItem = psMetadataLookup(target->concepts, "CELL.BIASSEC");
+        psFree(biassecItem->data.V);
+        biassecItem->data.V = psMemIncrRefCounter(biassec);
+    }
+
+    return success;
+}
+
Index: trunk/psModules/src/concepts/pmConceptsAverage.h
===================================================================
--- trunk/psModules/src/concepts/pmConceptsAverage.h	(revision 7849)
+++ trunk/psModules/src/concepts/pmConceptsAverage.h	(revision 7849)
@@ -0,0 +1,15 @@
+#ifndef PM_CONCEPTS_AVERAGE_H
+#define PM_CONCEPTS_AVERAGE_H
+
+#include <pslib.h>
+#include "pmFPA.h"
+
+// Set a variety of concepts in a cell by averaging over several
+bool pmConceptsAverageCells(pmCell *target,// Target cell
+                            psList *sources, // List of source cells
+                            int xBin, int yBin, // Binning in x and y
+                            psRegion *trimsec, // The trim section
+                            psRegion *biassec // The bias section
+                           );
+
+#endif
