Index: /trunk/ppStats/src/Makefile.am
===================================================================
--- /trunk/ppStats/src/Makefile.am	(revision 19625)
+++ /trunk/ppStats/src/Makefile.am	(revision 19626)
@@ -3,11 +3,22 @@
 libppStats_la_LDFLAGS = $(PPSTATS_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 
-bin_PROGRAMS = ppStats
+bin_PROGRAMS = ppStats ppStatsFromMetadata
+
 ppStats_CFLAGS 	= $(PPSTATS_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
 ppStats_LDFLAGS = $(PPSTATS_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 ppStats_LDADD 	= libppStats.la
 
+ppStatsFromMetadata_CFLAGS  = $(PPSTATS_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+ppStatsFromMetadata_LDFLAGS = $(PPSTATS_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
 ppStats_SOURCES =		\
 	ppStats.c	        
+
+ppStatsFromMetadata_SOURCES =		\
+	ppStatsFromMetadata.c	        \
+	ppStatsFromMetadataEntries.c	\
+	ppStatsFromMetadataParse.c	\
+	ppStatsFromMetadataStats.c	\
+	ppStatsFromMetadataPrint.c	
 
 libppStats_la_SOURCES = 		\
Index: /trunk/ppStats/src/ppStats.h
===================================================================
--- /trunk/ppStats/src/ppStats.h	(revision 19625)
+++ /trunk/ppStats/src/ppStats.h	(revision 19626)
@@ -4,4 +4,5 @@
 
 #define PPSTATS_RECIPE "PPSTATS"
+#define PPSTATS_MD_RECIPE "PPSTATS_METADATA"
 
 typedef struct {
@@ -27,4 +28,13 @@
     psList *cells;                      // Cells to look at
 } ppStatsData;
+
+typedef struct {
+    char *keyword;
+    psDataType type;
+    char *statistic;
+    char *flag;
+    psMetadataItem *value;
+    psVector *vector;
+} ppStatsEntry;
 
 // Allocator
@@ -129,3 +139,12 @@
     );
 
+// used by ppStatsFromMetadata:
+psDataType psDataTypeFromString (char *typename);
+ppStatsEntry *ppStatsEntryAlloc ();
+
+psArray *ppStatsFromMetadataEntries (psMetadata *recipe);
+bool ppStatsFromMetadataParse (psMetadata *input, psArray *entries);
+bool ppStatsFromMetadataStats (psArray *entries);
+bool ppStatsFromMetadataPrint (psArray *entries, char *filename);
+
 #endif
Index: /trunk/ppStats/src/ppStatsFromMetadata.c
===================================================================
--- /trunk/ppStats/src/ppStatsFromMetadata.c	(revision 19626)
+++ /trunk/ppStats/src/ppStatsFromMetadata.c	(revision 19626)
@@ -0,0 +1,69 @@
+# include "ppStatsInternal.h"
+
+// USAGE: ppStatsFromMetadata (input) (output) [-recipe PPSTATS_METADATA recipe]
+int main(int argc, char **argv) {
+
+    unsigned int nFail;
+    bool status;
+
+    psLibInit(NULL);
+
+    // Parse the configuration and arguments
+    pmConfig *config = pmConfigRead(&argc, argv, PPSTATS_MD_RECIPE);
+    if (!config) {
+        psErrorStackPrint(stderr, "Unable to read configuration.\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc != 4) {
+        psErrorStackPrint(stderr, "USAGE: ppStatsFromMetadata (input) (output) (recipe)\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    psMetadata *input = NULL;
+    if (!strcmp (argv[1], "-")) {
+	psString string = psSlurpFile (stdin);
+	input = psMetadataConfigParse (NULL, &nFail, string, false);
+	psFree (string);
+    } else {
+	input = psMetadataConfigRead (NULL, &nFail, argv[1], false);
+    }
+
+    // parse the recipe to determine the fields of interest
+    psMetadata *recipes = psMetadataLookupPtr (&status, config->recipes, PPSTATS_MD_RECIPE);
+    if (!recipes) {
+        psErrorStackPrint(stderr, "missing recipe set %s\n", PPSTATS_MD_RECIPE);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    psMetadata *recipe = psMetadataLookupPtr (&status, recipes, argv[3]);
+    if (!recipes) {
+        psErrorStackPrint(stderr, "missing ppStatsFromMetadata recipe %s\n", argv[3]);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    psArray *entries = ppStatsFromMetadataEntries (recipe);
+    if (!entries) {
+        psErrorStackPrint(stderr, "problem with recipe %s\n", PPSTATS_MD_RECIPE);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (!ppStatsFromMetadataParse (input, entries)) {
+        psErrorStackPrint(stderr, "problem with input data\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+    
+    // calculate the stats for the non-constant entries (already calculated)
+    if (!ppStatsFromMetadataStats (entries)) {
+        psErrorStackPrint(stderr, "problem calculating statistics\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // calculate the stats for the non-constant entries (already calculated)
+    if (!ppStatsFromMetadataPrint (entries, argv[2])) {
+        psErrorStackPrint(stderr, "problem calculating statistics\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    exit (0);
+}
Index: /trunk/ppStats/src/ppStatsFromMetadataEntries.c
===================================================================
--- /trunk/ppStats/src/ppStatsFromMetadataEntries.c	(revision 19626)
+++ /trunk/ppStats/src/ppStatsFromMetadataEntries.c	(revision 19626)
@@ -0,0 +1,110 @@
+# include "ppStatsInternal.h"
+
+psArray *ppStatsFromMetadataEntries (psMetadata *recipe) {
+
+    bool status = false;
+
+    psMetadataItem *item = NULL;
+
+    psArray *entries = psArrayAllocEmpty(16);
+
+    // the recipe file should include a collection of psMetadata items with the name ENTRY
+    
+    // psMetadataPrint (stderr, recipe, 1);
+
+    // loop over the items, selecting those with the name "ENTRY"
+    psMetadataIterator *iter = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, NULL); // Iterator
+    while ((item = psMetadataGetAndIncrement(iter))) {
+	if (strcmp (item->name, "ENTRY")) continue;
+	
+	if (item->type != PS_DATA_METADATA) {
+	    psError(PS_ERR_UNKNOWN, false, "ppStatsFromMetadata ENTRY not of type METADATA\n");
+	    return NULL;
+	}
+
+	psMetadata *entryMD = item->data.md;
+
+	ppStatsEntry *entry = ppStatsEntryAlloc ();
+
+	entry->keyword = psMetadataLookupStr (&status, entryMD, "KEYWORD");
+	if (!entry->keyword) {
+	    psError(PS_ERR_UNKNOWN, true, "ppStatsFromMetadata recipe ENTRY missing KEYWORD value\n");
+	    return NULL;
+	}
+	
+	entry->statistic = psMetadataLookupStr (&status, entryMD, "STATISTIC");
+	if (!entry->statistic) {
+	    psError(PS_ERR_UNKNOWN, true, "ppStatsFromMetadata recipe ENTRY %s missing STATISTIC\n", entry->keyword);
+	    return NULL;
+	}
+
+	entry->flag = psMetadataLookupStr (&status, entryMD, "FLAG");
+	if (!entry->flag) {
+	    psError(PS_ERR_UNKNOWN, true, "ppStatsFromMetadata recipe ENTRY %s missing FLAG\n", entry->keyword);
+	    return NULL;
+	}
+
+	char *typename = psMetadataLookupStr (&status, entryMD, "TYPE");
+	if (!typename) {
+	    psError(PS_ERR_UNKNOWN, true, "ppStatsFromMetadata recipe ENTRY %s missing TYPE\n", entry->keyword);
+	    return NULL;
+	}
+
+	entry->type = psDataTypeFromString (typename);
+	if (!entry->type) {
+	    psError(PS_ERR_UNKNOWN, true, "ppStatsFromMetadata recipe ENTRY %s has invalid TYPE %s\n", entry->keyword, typename);
+	    return NULL;
+	}
+	
+	// fprintf (stderr, "adding %s to array of %ld\n", entry->keyword, entries->n);
+	psArrayAdd (entries, 16, entry);
+	psFree (entry);
+    }
+    
+    return entries;
+}
+
+void ppStatsEntryFree (ppStatsEntry *entry) {
+
+    if (!entry) return;
+
+    psFree (entry->keyword);
+    psFree (entry->statistic);
+    psFree (entry->flag);
+    psFree (entry->value);
+    psFree (entry->vector);
+    return;
+}
+
+ppStatsEntry *ppStatsEntryAlloc () {
+
+    ppStatsEntry *entry = (ppStatsEntry *) psAlloc (sizeof(ppStatsEntry));
+    psMemSetDeallocator(entry, (psFreeFunc) ppStatsEntryFree);
+    
+    entry->keyword = NULL;
+    entry->statistic = NULL;
+    entry->flag = NULL;
+    entry->value = NULL;
+    entry->vector = NULL;
+    return entry;
+}
+
+psDataType psDataTypeFromString (char *typename) {
+
+    if (!strcmp (typename, "U8"))  return PS_DATA_U8;
+    if (!strcmp (typename, "U16")) return PS_DATA_U16;
+    if (!strcmp (typename, "U32")) return PS_DATA_U32;
+    if (!strcmp (typename, "U64")) return PS_DATA_U64;
+
+    if (!strcmp (typename, "S8"))  return PS_DATA_S8;
+    if (!strcmp (typename, "S16")) return PS_DATA_S16;
+    if (!strcmp (typename, "S32")) return PS_DATA_S32;
+    if (!strcmp (typename, "S64")) return PS_DATA_S64;
+
+    if (!strcmp (typename, "F32")) return PS_DATA_F32;
+    if (!strcmp (typename, "F64")) return PS_DATA_F64;
+
+    if (!strcmp (typename, "STR")) return PS_DATA_STRING;
+
+    return 0;
+}
Index: /trunk/ppStats/src/ppStatsFromMetadataParse.c
===================================================================
--- /trunk/ppStats/src/ppStatsFromMetadataParse.c	(revision 19626)
+++ /trunk/ppStats/src/ppStatsFromMetadataParse.c	(revision 19626)
@@ -0,0 +1,141 @@
+# include "ppStatsInternal.h"
+
+// loop over the metadata, adding entries of interest to their data arrays
+bool ppStatsFromMetadataParse (psMetadata *input, psArray *entries) {
+
+    psMetadataItem *item = NULL;
+
+    // loop over the items, selecting those with the name "ENTRY"
+    psMetadataIterator *iter = psMetadataIteratorAlloc(input, PS_LIST_HEAD, NULL); // Iterator
+
+    while ((item = psMetadataGetAndIncrement(iter))) {
+	if (item->type == PS_DATA_METADATA) {
+	    psMetadata *folder = item->data.md;
+	    if (!ppStatsFromMetadataParse (folder, entries)) {
+		psError(PS_ERR_UNKNOWN, false, "error parse metadata folder %s\n", item->name);
+		return false;
+	    }
+	    continue;
+	}
+
+	// find the matching entry, if one exists (if not, this is not an error: we are not
+	// obliged to determine stats for all entries).  we may have more than one entry for a
+	// given keyword (multiple stats for a single input item are allowed).
+	for (int i = 0; i < entries->n; i++) {
+	    ppStatsEntry *entry = entries->data[i];
+	    if (strcmp (entry->keyword, item->name)) continue;
+
+	    // check if the types match?
+	    if (entry->type != item->type) {
+		fprintf (stderr, "WARNING?  mismatched type, skipping\n");
+		continue;
+	    }
+
+	    // save the value
+
+	    // if constant, save or compare with existing value
+	    if (!strcasecmp (entry->statistic, "constant")) {
+		if (entry->value) {
+		    // check that they match
+		} else {
+		    entry->value = psMemIncrRefCounter (item);
+		}
+		continue;
+	    }
+
+	    bool useRMS = false;
+	    if (!strcasecmp (entry->statistic, "rms")) {
+		useRMS = true;
+	    }
+
+	    // only numerical values can have stats; all others must be 'constant'
+	    if (entry->type >= PS_DATA_BOOL) {
+		psError(PS_ERR_UNKNOWN, false, "only numerical types can have non-constant stats: %s\n", entry->keyword);
+		return false;
+	    }
+
+	    if (!entry->vector) {
+		entry->vector = psVectorAllocEmpty (16, entry->type);
+	    }
+
+	    switch (item->type) {
+	      case PS_DATA_U8:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.U8));
+		} else {
+		    psVectorAppend (entry->vector, item->data.U8);
+		}
+		break;
+	      case PS_DATA_U16:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.U16));
+		} else {
+		    psVectorAppend (entry->vector, item->data.U16);
+		}
+		break;
+	      case PS_DATA_U32:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.U32));
+		} else {
+		    psVectorAppend (entry->vector, item->data.U32);
+		}
+		break;
+	      case PS_DATA_U64:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.U64));
+		} else {
+		    psVectorAppend (entry->vector, item->data.U64);
+		}
+		break;
+
+	      case PS_DATA_S8:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.S8));
+		} else {
+		    psVectorAppend (entry->vector, item->data.S8);
+		}
+		break;
+	      case PS_DATA_S16:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.S16));
+		} else {
+		    psVectorAppend (entry->vector, item->data.S16);
+		}
+		break;
+	      case PS_DATA_S32:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.S32));
+		} else {
+		    psVectorAppend (entry->vector, item->data.S32);
+		}
+		break;
+	      case PS_DATA_S64:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.S64));
+		} else {
+		    psVectorAppend (entry->vector, item->data.S64);
+		}
+		break;
+
+	      case PS_DATA_F32:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.F32));
+		} else {
+		    psVectorAppend (entry->vector, item->data.F32);
+		}
+		break;
+	      case PS_DATA_F64:
+		if (useRMS) {
+		    psVectorAppend (entry->vector, PS_SQR(item->data.F64));
+		} else {
+		    psVectorAppend (entry->vector, item->data.F64);
+		}
+		break;
+	      default:
+		psError(PS_ERR_UNKNOWN, true, "ppStatsFromMetadata recipe ENTRY %s has invalid type\n", entry->keyword);
+		return false;
+	    }
+	}
+    }
+    return true;
+}
Index: /trunk/ppStats/src/ppStatsFromMetadataPrint.c
===================================================================
--- /trunk/ppStats/src/ppStatsFromMetadataPrint.c	(revision 19626)
+++ /trunk/ppStats/src/ppStatsFromMetadataPrint.c	(revision 19626)
@@ -0,0 +1,35 @@
+# include "ppStatsInternal.h"
+
+// calculate the stats for the non-constant entries (already calculated)
+bool ppStatsFromMetadataPrint (psArray *entries, char *filename) {
+
+    FILE *f = NULL;
+    if (!strcmp (filename, "-")) {
+	f = stdout;
+    } else {
+	f = fopen (filename, "w");
+	if (f == NULL) {
+	    psError(PS_ERR_UNKNOWN, false, "ppStatsFromMetadata cannot open output file %s\n", filename);
+	    return false;
+	}
+    }
+
+    // at this point, we have entries with values (of type STR or F32) or NULL
+    for (int i = 0; i < entries->n; i++) {
+	ppStatsEntry *entry = entries->data[i];
+
+	if (!entry->value) continue;
+
+	if (entry->value->type == PS_DATA_STRING) {
+	    fprintf (f, "%s %s ", entry->flag, entry->value->data.str);
+	}
+
+	if (entry->value->type == PS_DATA_F32) {
+	    fprintf (f, "%s %f ", entry->flag, entry->value->data.F32);
+	}
+    }
+    fprintf (f, "\n");
+
+    if (f != stdout) fclose (f);
+    return true;
+}
Index: /trunk/ppStats/src/ppStatsFromMetadataStats.c
===================================================================
--- /trunk/ppStats/src/ppStatsFromMetadataStats.c	(revision 19626)
+++ /trunk/ppStats/src/ppStatsFromMetadataStats.c	(revision 19626)
@@ -0,0 +1,34 @@
+# include "ppStatsInternal.h"
+
+// calculate the stats for the non-constant entries (already calculated)
+bool ppStatsFromMetadataStats (psArray *entries) {
+
+    for (int i = 0; i < entries->n; i++) {
+	ppStatsEntry *entry = entries->data[i];
+
+	if (!strcasecmp (entry->statistic, "constant")) continue;
+	
+	// XXX skip or warn on missing stats?
+	if (!entry->vector) continue;
+
+	psStatsOptions option;
+	if (!strcasecmp (entry->statistic, "rms")) {
+	    option = psStatsOptionFromString ("ROBUST_MEDIAN");
+	} else {
+	    option = psStatsOptionFromString (entry->statistic);
+	}
+	
+	psStats *stats = psStatsAlloc (option);
+
+	psVectorStats (stats, entry->vector, NULL, NULL, 0);
+
+	double value = psStatsGetValue (stats, option);
+
+	if (!strcasecmp (entry->statistic, "rms")) {
+	    value = sqrt(value);
+	}
+
+	entry->value = psMetadataItemAllocF32 (entry->keyword, entry->statistic, value);
+    }
+    return true;
+}
