Index: /branches/ccl_branches/ipponly-20191108/ppNorm/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/Makefile.am	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/Makefile.am	(revision 41088)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/autogen.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/autogen.sh	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/autogen.sh	(revision 41088)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=ppNorm
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOLIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOLIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/configure.ac
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/configure.ac	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/configure.ac	(revision 41088)
@@ -0,0 +1,29 @@
+AC_PREREQ(2.61)
+
+AC_INIT([ppNorm], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDLDFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+IPP_STDOPTS
+IPP_STDCFLAGS
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+AC_OUTPUT
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/src/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/src/Makefile.am	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/src/Makefile.am	(revision 41088)
@@ -0,0 +1,17 @@
+bin_PROGRAMS = ppNormCalc
+
+ppNormCalc_CFLAGS = $(PPNORM_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+ppNormCalc_LDFLAGS = $(PPNORM_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
+ppNormCalc_SOURCES =		\
+	ppNormCalc.c
+
+noinst_HEADERS =
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormCalc.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormCalc.c	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormCalc.c	(revision 41088)
@@ -0,0 +1,155 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+void helpAndDie(const char *programName)
+{
+    printf("Calculate normalisation for flat fields.\n\n"
+           "Usage: %s [IN.mdc [OUT.mdc]]\n\n"
+           "where IN.mdc is a metadata config file containing the background\n"
+           "      value for each component of each exposure;\n"
+           "and   OUT.mdc is a metadata config file containing the output\n"
+           "      normalisation factors.\n"
+           "\n", programName);
+    exit(EXIT_FAILURE);
+}
+
+
+int main(int argc, char *argv[])
+{
+    psLibInit(NULL);
+    pmConfig *config = pmConfigRead(&argc, argv, NULL);
+    psFree(config);
+
+    if (argc > 3 ||
+        psArgumentGet(argc, argv, "-h") ||
+        psArgumentGet(argc, argv, "-help") ||
+        psArgumentGet(argc, argv, "--help") ||
+        psArgumentGet(argc, argv, "-?")) {
+        helpAndDie(argv[0]);
+    }
+
+    FILE *inFile = stdin;               // Input file stream
+    FILE *outFile = stdout;             // Output file stream
+
+    if (argc >= 2) {
+        psString resolved = pmConfigConvertFilename(argv[1], config, false, false); // Resolved filename
+        inFile = fopen(resolved, "r");
+        if (!inFile) {
+            psError(PS_ERR_IO, true, "Unable to open input file: %s\n\n", resolved);
+            psFree(resolved);
+            helpAndDie(argv[0]);
+        }
+        psFree(resolved);
+    }
+    if (argc == 3) {
+        psString resolved = pmConfigConvertFilename(argv[2], config, true, true); // Resolved filename
+        outFile = fopen(resolved, "w");
+        if (!outFile) {
+            psError(PS_ERR_IO, true, "Unable to open output file: %s\n\n", resolved);
+            psFree(resolved);
+            helpAndDie(argv[0]);
+        }
+        psFree(resolved);
+    }
+
+    psString inputMDC = psSlurpFile(inFile); // Input metadata config stuff
+    if (argc >= 2) {
+        fclose(inFile);
+    }
+
+    psU32 badLines = 0;                   // Number of bad lines
+    psMetadata *exposures = psMetadataConfigParse(NULL, &badLines, inputMDC, false); // Exposure statistics
+    if (badLines > 0) {
+        psWarning("%d bad lines found when reading input\n", badLines);
+    }
+
+    // Get a list of all the components
+    psMetadata *components = psMetadataAlloc(); // Components of the exposures
+    psMetadataIterator *expIter = psMetadataIteratorAlloc(exposures, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *expItem;            // Item from iteration
+    while ((expItem = psMetadataGetAndIncrement(expIter))) {
+        if (expItem->type != PS_DATA_METADATA) {
+            psLogMsg("ppNormCalc", PS_LOG_WARN,
+                     "Metadata item %s is not of type METADATA --- ignored.\n",
+                     expItem->name);
+            continue;
+        }
+
+        // Inspect each component for this exposure; add it if we don't know about it
+        psMetadata *comps = expItem->data.V; // The components
+        psMetadataIterator *compsIter = psMetadataIteratorAlloc(comps, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *compsItem;      // Item from iteration
+        while ((compsItem = psMetadataGetAndIncrement(compsIter))) {
+            if (compsItem->type != PS_DATA_F32) {
+                psLogMsg("ppNormCalc", PS_LOG_WARN,
+                         "Component %s within exposure %s is not of type F32 --- ignored.\n",
+                         compsItem->name, expItem->name);
+                continue;
+            }
+            if (!psMetadataLookup(components, compsItem->name)) {
+                psMetadataAddBool(components, PS_LIST_TAIL, compsItem->name, 0, NULL, true);
+            }
+        }
+        psFree(compsIter);
+    }
+
+    // Convert to a matrix
+    int numComps = psListLength(components->list); // Number of components
+    int numExps = psListLength(exposures->list); // Number of exposures
+    psImage *matrix = psImageAlloc(numComps, numExps, PS_TYPE_F32); // Matrix of backgrounds
+    psMetadataIteratorSet(expIter, PS_LIST_HEAD);
+    for (int expNum = 0; (expItem = psMetadataGetAndIncrement(expIter)); expNum++) {
+        if (expItem->type != PS_DATA_METADATA) {
+            continue;
+        }
+        psMetadata *comps = expItem->data.V; // The components
+        psMetadataIterator *compsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *compsItem;      // Item from iteration
+        for (int compNum = 0; (compsItem = psMetadataGetAndIncrement(compsIter)); compNum++) {
+            matrix->data.F32[expNum][compNum] = psMetadataLookupF32(NULL, comps, compsItem->name);
+        }
+        psFree(compsIter);
+    }
+    psFree(expIter);
+
+    // Do the normalisation
+    psVector *gains = psVectorAlloc(numComps, PS_TYPE_F32); // Vector of gains for each component
+    psVectorInit(gains, 100.0);
+    if (!pmFlatNormalize(NULL, &gains, matrix)) {
+        psLogMsg("ppNormCalc", PS_LOG_ERROR, "Normalisation didn't converge.\n");
+        exit(EXIT_FAILURE);
+    }
+    psFree(matrix);
+
+
+    // Output a MDC format to stdout
+    psMetadata *outputMD = psMetadataAlloc(); // Output metadata
+    psMetadataIterator *compsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *compsItem;          // Item from iteration
+    for (int compNum = 0; (compsItem = psMetadataGetAndIncrement(compsIter)); compNum++) {
+        psMetadataAddF32(outputMD, PS_LIST_TAIL, compsItem->name, 0, NULL, gains->data.F32[compNum]);
+    }
+    psFree(compsIter);
+    psString outputString = psMetadataConfigFormat(outputMD);
+    fprintf(outFile, "%s", outputString);
+    psFree(outputString);
+    psFree(outputMD);
+    if (argc == 3) {
+        fclose(outFile);
+    }
+
+    // Clean up
+    psFree(gains);
+    psFree(components);
+    psFree(exposures);
+
+    pmConfigDone();
+    psLibFinalize();
+
+    return EXIT_SUCCESS;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.c.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.c.in	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.c.in	(revision 41088)
@@ -0,0 +1,26 @@
+/*
+ * The line
+    { PPNORM_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "ppNormErrorCodes.h"
+
+void ppNormErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPNORM_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PPNORM_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPNORM_ERR_NERROR - PPNORM_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.dat
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.dat	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.dat	(revision 41088)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate ppNormErrorClasses.h
+#
+BASE = 7000		First value we use; lower values belong to psLib
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.h.in	(revision 41088)
+++ /branches/ccl_branches/ipponly-20191108/ppNorm/src/ppNormErrorCodes.h.in	(revision 41088)
@@ -0,0 +1,18 @@
+#if !defined(PPNORM_ERROR_CODES_H)
+#define PPNORM_ERROR_CODES_H
+/*
+ * The line
+ *  PPNORM_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPNORM_ERR_BASE = 7000,
+    PPNORM_ERR_${ErrorCode},
+    PPNORM_ERR_NERROR
+} ppNormErrorCode;
+
+void ppNormErrorRegister(void);
+
+#endif
