Index: /branches/ccl_branches/ipponly-20191108/ppBackground/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/Makefile.am	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/Makefile.am	(revision 41114)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/autogen.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/autogen.sh	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/autogen.sh	(revision 41114)
@@ -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=ppBackground
+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/ppBackground/configure.ac
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/configure.ac	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/configure.ac	(revision 41114)
@@ -0,0 +1,44 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([ppBackground], [0.1.1], [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])
+PKG_CHECK_MODULES([PPSTATS], [ppStats >= 1.0.0]) 
+PKG_CHECK_MODULES([PSPHOT], [psphot])
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+IPP_STDCFLAGS
+
+IPP_VERSION
+
+AC_SUBST([PPBACKGROUND_CFLAGS])
+AC_SUBST([PPBACKGROUND_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+
+AC_OUTPUT
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/Makefile.am	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/Makefile.am	(revision 41114)
@@ -0,0 +1,62 @@
+bin_PROGRAMS = ppBackground ppBackgroundStack
+
+# Force recompilation of ppBackgroundVersion.c, since it gets the version information
+ppBackgroundVersion.c: ppBackgroundVersionDefinitions.h
+ppBackgroundVersionDefinitions.h: ppBackgroundVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl PPBACKGROUND ppBackgroundVersionDefinitions.h.in ppBackgroundVersionDefinitions.h
+FORCE: ;
+
+BUILT_SOURCES = ppBackgroundVersionDefinitions.h
+
+
+ppBackground_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPSTATS_CFLAGS) $(PSPHOT_CFLAGS) $(PPBACKGROUND_CFLAGS)
+ppBackground_LDFLAGS  = $(PSLIB_LIBS)   $(PSMODULE_LIBS)   $(PPSTATS_LIBS)   $(PSPHOT_LIBS)   $(PPBACKGROUND_LIBS)
+
+ppBackgroundStack_CPPFLAGS = $(ppBackground_CPPFLAGS)
+ppBackgroundStack_LDFLAGS = $(ppBackground_LDFLAGS)
+
+ppBackground_SOURCES =		\
+	ppBackground.c		\
+	ppBackgroundArguments.c	\
+	ppBackgroundCamera.c	\
+	ppBackgroundData.c	\
+	ppBackgroundLoop.c	\
+	ppBackgroundVersion.c   \
+	ppBackgroundRestore.c	\
+	ppBackgroundErrorCodes.c	\
+	ppBackgroundExit.c
+
+ppBackgroundStack_SOURCES = \
+	ppBackgroundStack.c \
+	ppBackgroundStackArguments.c \
+	ppBackgroundStackCamera.c \
+	ppBackgroundStackData.c \
+	ppBackgroundStackLoop.c \
+	ppBackgroundStackMath.c \
+	ppBackgroundVersion.c \
+	ppBackgroundErrorCodes.c \
+	ppBackgroundExit.c
+
+
+noinst_HEADERS = \
+	ppBackground.h		\
+	ppBackgroundErrorCodes.h
+
+### Error codes.
+BUILT_SOURCES = ppBackgroundErrorCodes.h ppBackgroundErrorCodes.c
+CLEANFILES = ppBackgroundErrorCodes.h ppBackgroundErrorCodes.c
+
+ppBackgroundErrorCodes.h : ppBackgroundErrorCodes.dat ppBackgroundErrorCodes.h.in
+	$(ERRORCODES) --data=ppBackgroundErrorCodes.dat --outdir=. ppBackgroundErrorCodes.h
+
+ppBackgroundErrorCodes.c : ppBackgroundErrorCodes.dat ppBackgroundErrorCodes.c.in ppBackgroundErrorCodes.h
+	$(ERRORCODES) --data=ppBackgroundErrorCodes.dat --outdir=. ppBackgroundErrorCodes.c
+
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackground.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackground.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackground.c	(revision 41114)
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <psphot.h>
+
+#include "ppBackground.h"
+
+int main(int argc, char *argv[])
+{
+    ppBackgroundVersionPrint();
+
+    pmErrorRegister();
+    psphotErrorRegister();
+    ppBackgroundErrorRegister();
+
+    printf("Initializing data\n");
+    ppBackgroundData *data = ppBackgroundDataInit(&argc, argv);
+    if (!data) {
+        goto DIE;
+    }
+    printf("Reading Arguments\n");
+    if (!ppBackgroundArguments(data, argc, argv)) {
+        goto DIE;
+    }
+    printf("Setting up Camera\n");
+    if (!ppBackgroundCamera(data)) {
+        goto DIE;
+    }
+
+    printf("Looping over data!\n");
+    if (!ppBackgroundLoop(data)) {
+        goto DIE;
+    }
+
+ DIE:
+    ; // Empty statement to satisy compiler
+    psExit exitValue = ppBackgroundExitCode(PS_EXIT_SUCCESS); // Exit code
+
+    if (data && data->stats && data->statsFile) {
+        psString stats = psMetadataConfigFormat(data->stats); // Statistics to output
+        if (!stats || strlen(stats) == 0) {
+            psError(PPBACKGROUND_ERR_IO, false, "Unable to format statistics file");
+        } else if (fprintf(data->statsFile, "%s", stats) != strlen(stats)) {
+            psError(PPBACKGROUND_ERR_IO, true, "Unable to write statistics file");
+        }
+        psFree(stats);
+        if (fclose(data->statsFile) == EOF) {
+            psError(PPBACKGROUND_ERR_IO, true, "Unable to close statistics file");
+        }
+        data->statsFile = NULL;
+        exitValue = ppBackgroundExitCode(exitValue);
+    }
+
+    if (data) {
+        psString dump_file = psMetadataLookupStr(NULL, data->config->arguments, "-dumpconfig");
+        if (dump_file) {
+            if (!pmConfigDump(data->config, dump_file)) {
+                psError(psErrorCodeLast(), false, "Unable to dump configuration.");
+                exitValue = ppBackgroundExitCode(exitValue);
+            }
+        }
+        psFree(data);
+    }
+
+    pmConfigDone();
+    psLibFinalize();
+
+    return ppBackgroundExitCode(exitValue);
+}
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackground.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackground.h	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackground.h	(revision 41114)
@@ -0,0 +1,74 @@
+#ifndef PP_VIZPSF_H
+#define PP_VIZPSF_H
+
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackgroundErrorCodes.h"
+
+#define PPBACKGROUND_RECIPE "PPBACKGROUND"      // Recipe name
+
+// Data for processing
+typedef struct {
+    psString patternName;               // Filename of pattern
+    psString backgroundName;            // Filename of background
+    psString imageName;                 // Filenames of input image
+    psString maskName;                  // Filenames of input mask
+    psString varianceName;              // Filenames of input variance
+    psString auxMaskName;               // Filename of auxillary mask
+    psString outRoot;                   // Output root name
+    psMetadata *stats;                  // Statistics for output
+    FILE *statsFile;                    // Output statistics file
+    pmConfig *config;                   // Configuration
+} ppBackgroundData;
+
+/// Initialise data for processing
+ppBackgroundData *ppBackgroundDataInit(int *argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse command-line arguments
+bool ppBackgroundArguments(ppBackgroundData *data, // Data for processing
+                        int argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse camera configurations
+bool ppBackgroundCamera(ppBackgroundData *data // Data for processing
+    );
+
+/// Loop over input data, processing
+bool ppBackgroundLoop(ppBackgroundData *data // Data for processing
+    );
+
+/// Determine the binning from the recipe if available.
+psImageBinning *ppBackgroundBinningByRecipe(const psImage *image, // Image for which to generate a bg model
+					    const pmConfig *config, // Configuration
+					    psString recipe_name,
+					    psString Xbin_name,
+					    psString Ybin_name
+					    );
+
+
+/// Restore the background to an image
+bool ppBackgroundRestore(
+    pmChip *chip,                       // Chip to correct
+    const pmChip *background,           // Chip with background model
+    const pmChip *pattern,              // Chip with pattern
+    const pmFPAview *view,              // View to data
+    pmConfig *config,                   // Configuration
+    psImageMaskType maskBad             // value to use for bad pixels
+    );
+
+/// Determine exit code
+psExit ppBackgroundExitCode(
+    psExit exitValue                    // Current exit code
+    );
+
+/// Add version information to header
+bool ppBackgroundVersionHeader(
+    psMetadata *header                  // Header to supplement
+    );
+
+/// Print version information to stdout
+void ppBackgroundVersionPrint(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundArguments.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundArguments.c	(revision 41114)
@@ -0,0 +1,102 @@
+/** @file ppBackgroundArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppBackground
+ *
+ *  @author Paul Price
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackground.h"
+
+/// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  ppBackgroundData *data   // Run-time data
+    )
+{
+    fprintf(stderr, "\nPan-STARRS background replacement\n\n");
+    fprintf(stderr, "Usage: %s OUTPUT_ROOT\n\n", program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(data);
+
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+
+bool ppBackgroundArguments(ppBackgroundData *data, int argc, char *argv[])
+{
+    assert(data);
+    assert(data->config);
+
+    psMetadata *arguments = data->config->arguments;
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-pattern", 0, "Filename of pattern correction", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-background", 0, "Filename of background model", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-image", 0, "Filename of image (required)", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-mask", 0, "Filename of mask", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-variance", 0, "Filename of variance image", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-auxmask", 0, "Filename of auxiliary mask", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stats", 0, "Output statistics file", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-dumpconfig", 0, "Output configuration file", NULL);
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 2) {
+        usage(argv[0], arguments, data);
+    }
+
+    data->patternName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-pattern"));
+    data->backgroundName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-background"));
+    data->imageName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-image"));
+    data->maskName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-mask"));
+    data->varianceName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-variance"));
+    data->auxMaskName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-auxmask"));
+    data->outRoot = psStringCopy(argv[1]);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "OUTPUT", 0, "Output root name", data->outRoot);
+
+    psTrace("ppBackground", 1, "Done reading command-line arguments\n");
+
+    if (!data->patternName && !data->backgroundName && !data->auxMaskName) {
+//        psError(PPBACKGROUND_ERR_CONFIG, true, "Must specify at least one of -pattern and -background -auxmask");
+//        return false;
+    }
+    if (!data->imageName) {
+        psError(PPBACKGROUND_ERR_CONFIG, true, "Must specify -image");
+        return false;
+    }
+
+    PS_ASSERT_STRING_NON_EMPTY(data->outRoot, false);
+
+    const char *statsName = psMetadataLookupStr(NULL, arguments, "-stats");
+    if (statsName) {
+        psString resolved = pmConfigConvertFilename(statsName, data->config, true, true); // Resolved filename
+        if (!resolved) {
+            psError(psErrorCodeLast(), false, "Unable to resolve statistics file %s", statsName);
+            return false;
+        }
+        data->statsFile = fopen(resolved, "w");
+        if (!data->statsFile) {
+            psError(PPBACKGROUND_ERR_IO, true, "Unable to open statistics file %s = %s", statsName, resolved);
+            psFree(resolved);
+            return false;
+        }
+        psFree(resolved);
+        data->stats = psMetadataAlloc();
+        pmConfigRunFilenameAddWrite(data->config, "STATS", statsName);
+    }
+
+    return true;
+}
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundCamera.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundCamera.c	(revision 41114)
@@ -0,0 +1,114 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackground.h"
+
+/// Add a single filename to the arguments as an array, so that it can be used with pmFPAfileBindFromArgs, etc
+static void fileArguments(const char *file, // The symbolic name for the file
+                          const char *name, // The name of the file
+                          const char *comment, // Description of the file
+                          pmConfig *config // Configuration
+    )
+{
+    psArray *files = psArrayAlloc(1); // Array with file names
+    files->data[0] = psStringCopy(name);
+    if (psMetadataLookup(config->arguments, file)) {
+        psMetadataRemoveKey(config->arguments, file);
+    }
+    psMetadataAddArray(config->arguments, PS_LIST_TAIL, file, 0, comment, files);
+    psFree(files);
+    return;
+}
+
+
+bool ppBackgroundCamera(ppBackgroundData *data // Run-time data
+    )
+{
+    bool status;                        // Status of file definition
+
+    if (data->patternName) {
+        fileArguments("PATTERN", data->patternName, "Input pattern", data->config);
+        pmFPAfileDefineFromArgs(&status, data->config, "PPBACKGROUND.PATTERN", "PATTERN"); // File
+        if (!status) {
+            psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.PATTERN");
+            return false;
+        }
+    }
+
+    if (data->backgroundName) {
+        fileArguments("BACKGROUND", data->backgroundName, "Input background model", data->config);
+        pmFPAfileDefineFromArgs(&status, data->config, "PPBACKGROUND.BACKGROUND", "BACKGROUND"); // File
+        if (!status) {
+            psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.BACKGROUND");
+            return false;
+        }
+    }
+
+    fileArguments("IMAGE", data->imageName, "Input uncorrected image", data->config);
+    pmFPAfile *image = pmFPAfileDefineFromArgs(&status, data->config, "PPBACKGROUND.IMAGE",
+                                               "IMAGE"); // File
+    if (!status || !image) {
+        psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.IMAGE");
+        return false;
+    }
+
+    fileArguments("MASK", data->maskName, "Input uncorrected mask", data->config);
+    pmFPAfileBindFromArgs(&status, image, data->config, "PPBACKGROUND.MASK", "MASK"); // File
+    if (!status) {
+        psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.MASK");
+        return false;
+    }
+
+    fileArguments("VARIANCE", data->varianceName, "Input uncorrected variance", data->config);
+    pmFPAfileBindFromArgs(&status, image, data->config, "PPBACKGROUND.VARIANCE", "VARIANCE"); // File
+    if (!status) {
+        psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.VARIANCE");
+        return false;
+    }
+
+    if (data->auxMaskName) {
+        fileArguments("AUXMASK", data->auxMaskName, "Auxiliary mask", data->config);
+        pmFPAfileBindFromArgs(&status, image, data->config, "PPBACKGROUND.AUXMASK", "AUXMASK"); // File
+        if (!status) {
+            psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.MASK");
+            return false;
+        }
+    }
+
+    pmFPAfile *output = pmFPAfileDefineOutput(data->config, image->fpa, "PPBACKGROUND.OUTPUT");
+    if (!output) {
+        psError(psErrorCodeLast(), false, "Unable to define output.");
+        return false;
+    }
+    output->save = true;
+
+    pmFPAfile *outMask = pmFPAfileDefineOutput(data->config, output->fpa, "PPBACKGROUND.OUTPUT.MASK");
+    if (!outMask) {
+        psError(psErrorCodeLast(), false, "Unable to define output.");
+        return false;
+    }
+    outMask->save = true;
+
+    pmFPAfile *outVariance = pmFPAfileDefineOutput(data->config, output->fpa, "PPBACKGROUND.OUTPUT.VARIANCE");
+    if (!outVariance) {
+        psError(psErrorCodeLast(), false, "Unable to define output.");
+        return false;
+    }
+    outVariance->save = true;
+
+#if 0
+    // Now the camera has been determined, we can read the recipe
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, data->config->recipes, PPBACKGROUND_RECIPE); // Recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPBACKGROUND_RECIPE);
+        return false;
+    }
+#endif
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundData.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundData.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundData.c	(revision 41114)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackground.h"
+
+// Destructor
+static void backgroundDataFree(ppBackgroundData *data // Data to free
+    )
+{
+    psFree(data->patternName);
+    psFree(data->backgroundName);
+    psFree(data->imageName);
+    psFree(data->maskName);
+    psFree(data->varianceName);
+    psFree(data->auxMaskName);
+    psFree(data->outRoot);
+    psFree(data->stats);
+    psFree(data->config);
+    return;
+}
+
+
+ppBackgroundData *ppBackgroundDataAlloc(void)
+{
+    ppBackgroundData *data = psAlloc(sizeof(ppBackgroundData)); // Processing data, to return
+    psMemSetDeallocator(data, (psFreeFunc)backgroundDataFree);
+
+    data->patternName = NULL;
+    data->backgroundName = NULL;
+    data->imageName = NULL;
+    data->maskName = NULL;
+    data->varianceName = NULL;
+    data->auxMaskName = NULL;
+    data->outRoot = NULL;
+    data->stats = NULL;
+    data->statsFile = NULL;
+    data->config = NULL;
+
+    return data;
+}
+
+
+ppBackgroundData *ppBackgroundDataInit(int *argc, char **argv)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    ppBackgroundData *data = ppBackgroundDataAlloc(); // Processing data, to return
+    data->config = pmConfigRead(argc, argv, PPBACKGROUND_RECIPE);
+    if (!data->config) {
+        psError(psErrorCodeLast(), false, "Unable to read configuration.");
+        return NULL;
+    }
+    return data;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.c.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.c.in	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.c.in	(revision 41114)
@@ -0,0 +1,25 @@
+#include "pslib.h"
+#include "ppBackgroundErrorCodes.h"
+
+/*
+ * The line
+    { PPBACKGROUND_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+void ppBackgroundErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPBACKGROUND_ERR_BASE, "First value we use" },
+       { PPBACKGROUND_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPBACKGROUND_ERR_NERROR - PPBACKGROUND_ERR_BASE; ///< number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *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/ppBackground/src/ppBackgroundErrorCodes.dat
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.dat	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.dat	(revision 41114)
@@ -0,0 +1,11 @@
+#
+# This file is used to generate ppBackgroundErrorClasses.h
+#
+BASE = 17000		First value we use
+UNKNOWN			Unknown ppBackground error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+IO			Problem in I/O
+DATA                    Problem in data values
+PROG			Programming error
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.h.in	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundErrorCodes.h.in	(revision 41114)
@@ -0,0 +1,18 @@
+#if !defined(PPBACKGROUND_ERROR_CODES_H)
+#define PPBACKGROUND_ERROR_CODES_H
+/*
+ * The line
+ *  PPBACKGROUND_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPBACKGROUND_ERR_BASE = 17000,
+    PPBACKGROUND_ERR_${ErrorCode},
+    PPBACKGROUND_ERR_NERROR
+} ppBackgroundErrorCode;
+
+void ppBackgroundErrorRegister(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundExit.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundExit.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundExit.c	(revision 41114)
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "ppBackground.h"
+
+psExit ppBackgroundExitCode(psExit exitValue)
+{
+    if (exitValue != PS_EXIT_SUCCESS) {
+        return exitValue;
+    }
+
+    // gcc -Wswitch complains here if err is declared as type psErrorCode
+    // the collection of ps*ErrorCode values are enums defined separately for 
+    // each module (psphot, pswarp, etc).  the lowest type, psErrorCode is only the base set and does
+    // not include the possible psphot values
+
+    // for now, to get around this, we just use an int for the switch
+
+    // psErrorCode errorCode = psErrorCodeLast(); // Error code
+    int errorCode = psErrorCodeLast(); // Error code
+
+    if (errorCode != PS_ERR_NONE) {
+        pmFPAfileFreeSetStrict(false);
+        psErrorStackPrint(stderr, "Error in background restoration:");
+        switch (errorCode) {
+          case PS_ERR_UNKNOWN:
+            psLogMsg("ppBackground", PS_LOG_WARN, "Unknown error code: %x", errorCode);
+            exitValue = PS_EXIT_UNKNOWN_ERROR;
+            break;
+          case PS_ERR_IO:
+          case PS_ERR_DB_CLIENT:
+          case PS_ERR_DB_SERVER:
+          case PS_ERR_BAD_FITS:
+          case PS_ERR_OS_CALL_FAILED:
+          case PM_ERR_SYS:
+            psLogMsg("ppBackground", PS_LOG_WARN, "I/O error code: %x", errorCode);
+            exitValue = PS_EXIT_SYS_ERROR;
+            break;
+          case PS_ERR_BAD_PARAMETER_VALUE:
+          case PS_ERR_BAD_PARAMETER_TYPE:
+          case PS_ERR_BAD_PARAMETER_NULL:
+          case PS_ERR_BAD_PARAMETER_SIZE:
+          case PPBACKGROUND_ERR_ARGUMENTS:
+          case PPBACKGROUND_ERR_CONFIG:
+            psLogMsg("ppBackground", PS_LOG_WARN, "Configuration error code: %x", errorCode);
+            exitValue = PS_EXIT_CONFIG_ERROR;
+            break;
+          case PPBACKGROUND_ERR_DATA:
+            psLogMsg("ppBackground", PS_LOG_WARN, "Data error code: %x", errorCode);
+            exitValue = PS_EXIT_DATA_ERROR;
+            break;
+          case PS_ERR_UNEXPECTED_NULL:
+          case PS_ERR_PROGRAMMING:
+          case PPBACKGROUND_ERR_PROG:
+            psLogMsg("ppBackground", PS_LOG_WARN, "Programming error code: %x", errorCode);
+            exitValue = PS_EXIT_PROG_ERROR;
+            break;
+          default:
+            // It's a programming error if we're not dealing with the error correctly
+            psLogMsg("ppBackground", PS_LOG_WARN, "Unrecognised error code: %x", errorCode);
+            exitValue = PS_EXIT_PROG_ERROR;
+            break;
+        }
+    }
+
+    return exitValue;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundLoop.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundLoop.c	(revision 41114)
@@ -0,0 +1,179 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppBackground.h"
+
+bool ppBackgroundLoop(ppBackgroundData *data // Run-time data
+    )
+{
+    pmConfig *config = data->config;                                        // Configuration data
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPBACKGROUND.IMAGE", 0);
+    pmFPAfile *patternFile = pmFPAfileSelectSingle(config->files, "PPBACKGROUND.PATTERN", 0); // File with data
+    pmFPAfile *bgFile = pmFPAfileSelectSingle(config->files, "PPBACKGROUND.BACKGROUND", 0);   // File with bg
+    pmFPA *patternMosaic = pmFPAConstruct(file->camera, file->cameraName); // Mosaicked FPA for pattern
+
+    psImageMaskType maskBad = pmConfigMaskGet("DETECTOR", config); // Mask value to set
+
+    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        return NULL;
+    }
+    pmConceptsCopyFPA(patternMosaic, file->fpa, false, false);
+    pmChip *chip;                       // Chip from FPA
+    while ((chip = pmFPAviewNextChip(view, file->fpa, 1))) {
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            psError(psErrorCodeLast(), false, "Error loading data from files.");
+            return false;
+        }
+
+        if (chip->cells->n > 1) {
+            psError(PPBACKGROUND_ERR_CONFIG, true,
+                    "Input image and background model should be chip-mosaicked");
+            return false;
+        }
+
+        if (data->patternName) {
+            pmFPAfileActivate(config->files, false, NULL);
+            pmFPAfileActivate(config->files, true, "PPBACKGROUND.PATTERN");
+
+            pmCell *patternCell;               // Cell with pattern data
+            while ((patternCell = pmFPAviewNextCell(view, patternFile->fpa, 1))) {
+                if (!patternCell->process || !patternCell->file_exists) {
+                    continue;
+                }
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    psError(psErrorCodeLast(), false, "Error loading data from files.");
+                    return false;
+                }
+
+                pmReadout *readout;         // Readout with pattern data
+                while ((readout = pmFPAviewNextReadout(view, patternFile->fpa, 1))) {
+                    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                        psError(psErrorCodeLast(), false, "Error loading data from files.");
+                        return false;
+                    }
+                    if (!readout->data_exists) {
+                        continue;
+                    }
+
+                    int numCols = 0, numRows = 0; // Size of image
+
+                    // Get size of image from concepts
+                    if (numCols == 0 || numRows == 0) {
+                        numCols = psMetadataLookupS32(NULL, patternCell->concepts, "CELL.XSIZE");
+                        numRows = psMetadataLookupS32(NULL, patternCell->concepts, "CELL.YSIZE");
+                    }
+                    // Get size of image from TRIMSEC
+                    if (numCols == 0 || numRows == 0) {
+                        psRegion *trimsec = psMetadataLookupPtr(NULL, patternCell->concepts, "CELL.TRIMSEC");
+                        numCols = trimsec->x1 - trimsec->x0;
+                        numRows = trimsec->y1 - trimsec->y0;
+                    }
+                    if (numCols <= 0 || numRows <= 0) {
+                        psError(PS_ERR_UNKNOWN, true, "Image size isn't set: %dx%d.", numCols, numRows);
+                        return false;
+                    }
+
+                    readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+                    psImageInit(readout->image, 0.0);
+                    readout->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
+                    psImageInit(readout->mask, 0);
+
+                    if (!pmPatternRowApply(readout, maskBad) || !pmPatternCellApply(readout, maskBad)) {
+                        psError(psErrorCodeLast(), false, "Unable to apply pattern correction.");
+                        return false;
+                    }
+
+                    // Remove bias sections to avoid warnings
+                    psMetadataItem *biassec = psMetadataLookup(patternCell->concepts, "CELL.BIASSEC");
+                    if (psListLength(biassec->data.V)) {
+                        psFree(biassec->data.V);
+                        biassec->data.V = psListAlloc(NULL);
+                    }
+
+                    readout->data_exists = true;
+                    readout->parent->data_exists = true;
+                    readout->parent->parent->data_exists = true;
+
+                    // Readout
+                    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                        psError(psErrorCodeLast(), false, "Error saving data to files.");
+                        return false;
+                    }
+                }
+                // Cell
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                    psError(psErrorCodeLast(), false, "Error saving data to files.");
+                    return false;
+                }
+            }
+
+            if (data->patternName) {
+                pmChip *patternChip = pmFPAviewThisChip(view, patternFile->fpa); // Chip for pattern
+                pmChip *mosaic = pmFPAviewThisChip(view, patternMosaic); // Chip for mosaicked pattern
+                if (!mosaic->hdu && !mosaic->parent->hdu) {
+                    pmFPAAddSourceFromView(patternMosaic, view, file->format);
+                }
+                if (!pmChipMosaic(mosaic, patternChip, true, maskBad)) {
+                    psError(psErrorCodeLast(), false, "Unable to mosaic pattern correction");
+                    return false;
+                }
+                pmFPAfileActivate(config->files, true, NULL);
+            //            pmFPAfileActivate(config->files, false, "PPBACKGROUND.PATTERN");
+            }
+        }
+
+        pmChip *patternChip = patternFile ? pmFPAviewThisChip(view, patternMosaic) : NULL; // Chip with pattern
+        pmChip *bgChip = bgFile ? pmFPAviewThisChip(view, bgFile->fpa) : NULL; // Chip with background model
+        if (!ppBackgroundRestore(chip, bgChip, patternChip, view, config, maskBad)) {
+            psError(psErrorCodeLast(), false, "Unable to replace background");
+            return false;
+        }
+        pmChipFreeData(patternChip);
+
+        pmHDU *hdu = pmHDUGetLowest(file->fpa, chip, NULL); // HDU for chip
+        if (!hdu) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU for data.");
+            return false;
+        }
+        if (!hdu->header) {
+            hdu->header = psMetadataAlloc();
+        }
+        ppBackgroundVersionHeader(hdu->header);
+
+        // Chip
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psError(psErrorCodeLast(), false, "Error saving data to files.");
+            return false;
+        }
+    }
+    // FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psError(psErrorCodeLast(), false, "Error saving data to files.");
+        return false;
+    }
+
+    psFree(patternMosaic);
+
+    if (data->stats) {
+        psImageMaskType maskVal = 0;    // Bits to mask
+        if (!pmConfigMaskSetBits(&maskVal, NULL, config)) {
+            psError(psErrorCodeLast(), false, "Unable to find bits to mask");
+            return false;
+        }
+        ppStatsFPA(data->stats, file->fpa, view, maskVal, config);
+    }
+
+    psFree(view);
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundRestore.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundRestore.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundRestore.c	(revision 41114)
@@ -0,0 +1,149 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <psphot.h>
+
+#include "ppBackground.h"
+
+psImageBinning *ppBackgroundBinningByRecipe(const psImage *image, // Image for which to generate a bg model
+					    const pmConfig *config, // Configuration
+					    psString recipe_name,
+					    psString Xbin_name,
+					    psString Ybin_name
+					    )
+{
+  bool status = true;
+
+  psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, recipe_name);
+  assert (recipe);
+
+  // I have the fine image size, I know the binning factor, determine the ruff image size
+  psImageBinning *binning = psImageBinningAlloc();
+  binning->nXfine = image->numCols;
+  binning->nYfine = image->numRows;
+  binning->nXbin  = psMetadataLookupS32(&status, recipe, Xbin_name);
+  binning->nYbin  = psMetadataLookupS32(&status, recipe, Ybin_name);
+
+  psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+  psImageBinningSetSkip(binning, image);
+
+  return binning;
+}
+
+  
+
+
+bool ppBackgroundRestore(pmChip *chip, const pmChip *background, const pmChip *pattern,
+                         const pmFPAview *oldView, pmConfig *config, psImageMaskType maskBad)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(oldView, false);
+
+    pmFPAview *view = pmFPAviewAlloc(0); // View to readout
+    *view = *oldView;
+    view->cell = 0;
+    view->readout = 0;
+
+    pmReadout *ro = pmFPAviewThisReadout(view, chip->parent);
+    if (!ro || !ro->data_exists) {
+        psError(PPBACKGROUND_ERR_CONFIG, true, "Readout has no data");
+        return false;
+    }
+    const psImage *image = ro->image, *mask = ro->mask;   // Input image
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    if (background) {
+        pmReadout *bgRO = pmFPAviewThisReadout(view, background->parent); // Readout with background
+
+	psImageBinning *binning;
+	psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPBACKGROUND_RECIPE); // Recipe
+	if (!recipe) {
+	  binning = psphotBackgroundBinning(image, config);
+	}
+	else {
+	  binning = ppBackgroundBinningByRecipe(image,config,
+						psMetadataLookupStr(NULL,recipe,"BINNING_RECIPE"),
+						psMetadataLookupStr(NULL,recipe,"BINNING_XNAME"),
+						psMetadataLookupStr(NULL,recipe,"BINNING_YNAME"));
+	}
+						
+	fprintf(stderr,"%d %d %d %d\n",binning->nXfine,binning->nYfine,binning->nXbin,binning->nYbin);
+        if (!binning) {
+            psError(psErrorCodeLast(), false, "Unable to find background binning");
+            return false;
+        }
+        if (binning->nXfine != numCols || binning->nYfine != numRows) {
+            psError(PPBACKGROUND_ERR_CONFIG, true,
+                    "Unbinned background model and input don't match (%dx%d vs %dx%d)",
+                    binning->nXfine, binning->nYfine, numCols, numRows);
+            return false;
+        }
+
+        psImage *bgImage = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Background
+        if (!psImageUnbin(bgImage, bgRO->image, binning)) {
+            psError(psErrorCodeLast(), false, "Unable to unbin background model");
+            psFree(binning);
+            return false;
+        }
+        psFree(binning);
+
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+	      //	      if ((y % 250 == 0)&&(x % 250 == 0)) {
+	      //		printf("%d %d %g\n",x,y,bgImage->data.F32[y][x]);
+	      //	      }
+                image->data.F32[y][x] += bgImage->data.F32[y][x];
+		
+            }
+        }
+        psFree(bgImage);
+    }
+
+    if (pattern) {
+        pmReadout *patternRO = pmFPAviewThisReadout(view, pattern->parent); // Readout with pattern
+        psImage *patternImage = patternRO->image; // Image with pattern
+        psImage *patternMask = patternRO->mask;   // Mask for pattern
+        // The sign is flipped for the continuity correction.
+        bool mdok;
+        bool isContinuity = psMetadataLookupBool(&mdok, pattern->hdu->header, "PTRN_CON");
+        
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                if (isContinuity) {
+                    image->data.F32[y][x] += patternImage->data.F32[y][x];
+                } else {
+                    image->data.F32[y][x] -= patternImage->data.F32[y][x];
+                }
+                mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= patternMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x];
+            }
+        }
+    }
+
+    psImage *auxMask = psMetadataLookupPtr(NULL, ro->analysis, "EXPNUM");
+    if (auxMask) {
+        if (auxMask->numCols != mask->numCols || auxMask->numRows != mask->numRows) {
+            psError(PPBACKGROUND_ERR_DATA, true, "auxiliary mask size (%d x %d) does not match input size (%d x %d)",
+                auxMask->numCols, auxMask->numRows, numCols, numRows);
+            return false;
+        }
+        // Arno's masks are floating point, fix later and zero means bad
+        #define AUXMASK_DATA F32
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                if (auxMask->data.AUXMASK_DATA[y][x] == 0) {
+                    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= maskBad;
+                }
+            }
+        }
+    }
+
+
+    ro->data_exists = true;
+    ro->parent->data_exists = true;
+    ro->parent->parent->data_exists = true;
+
+    return true;
+}
+
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStack.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStack.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStack.c	(revision 41114)
@@ -0,0 +1,75 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <psphot.h>
+
+#include "ppBackgroundStack.h"
+
+int main(int argc, char *argv[])
+{
+    ppBackgroundVersionPrint();
+
+    pmErrorRegister();
+    psphotErrorRegister();
+    ppBackgroundErrorRegister();
+
+    printf("Data init\n");
+    ppBackgroundStackData *data = ppBackgroundStackDataInit(&argc, argv);
+    if (!data) {
+        goto DIE;
+    }
+    printf("Read arguments\n");
+    if (!ppBackgroundStackArguments(data, argc, argv)) {
+        goto DIE;
+    }
+
+    printf("Setup camera\n");
+    if (!ppBackgroundStackCamera(data)) {
+        goto DIE;
+    }
+
+    printf("Do Loop\n");
+    if (!ppBackgroundStackLoop(data)) {
+        goto DIE;
+    }
+
+ DIE:
+    ; // Empty statement to satisy compiler
+    psExit exitValue = ppBackgroundExitCode(PS_EXIT_SUCCESS); // Exit code
+
+/*     if (data && data->stats && data->statsFile) { */
+/*         psString stats = psMetadataConfigFormat(data->stats); // Statistics to output */
+/*         if (!stats || strlen(stats) == 0) { */
+/*             psError(PPBACKGROUND_ERR_IO, false, "Unable to format statistics file"); */
+/*         } else if (fprintf(data->statsFile, "%s", stats) != strlen(stats)) { */
+/*             psError(PPBACKGROUND_ERR_IO, true, "Unable to write statistics file"); */
+/*         } */
+/*         psFree(stats); */
+/*         if (fclose(data->statsFile) == EOF) { */
+/*             psError(PPBACKGROUND_ERR_IO, true, "Unable to close statistics file"); */
+/*         } */
+/*         data->statsFile = NULL; */
+/*         exitValue = ppBackgroundExitCode(exitValue); */
+/*     } */
+
+    if (data) {
+        psString dump_file = psMetadataLookupStr(NULL, data->config->arguments, "-dumpconfig");
+        if (dump_file) {
+            if (!pmConfigDump(data->config, dump_file)) {
+                psError(psErrorCodeLast(), false, "Unable to dump configuration.");
+                exitValue = ppBackgroundExitCode(exitValue);
+            }
+        }
+        psFree(data);
+    }
+
+    pmConfigDone();
+    psLibFinalize();
+
+    return ppBackgroundExitCode(exitValue);
+}
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStack.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStack.h	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStack.h	(revision 41114)
@@ -0,0 +1,80 @@
+#ifndef PP_BACKGROUNDSTACK_H
+#define PP_BACKGROUNDSTACK_H
+
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackgroundErrorCodes.h"
+
+#define PPBACKGROUND_RECIPE "PPBACKGROUND_STACK"      // Recipe name
+
+// Data for processing
+typedef struct {
+  psMetadata *contents;   // Metadata containing information about the data arrays
+  psArray *smfs;          // List of pointers to individual smf headers
+  
+  // data->models->name->XY__->{image/ra/dec/calibrated/offset/scale}
+  // data->models->counter->XY__->{image/ra/dec/calibrated/offset/scale}
+  psMetadata *models;     // Metadata containing the information about the model data
+  
+  psMetadata *OTA_solutions; // List of pointers to the OTA-specific solution images
+  bool fit_OTAS;          // Calculate OTA solutions based on these inputs.
+  psString OTApath;       // Location for pre-solved OTA solutions.
+
+  psImageMap *modelMap;
+  psS32 model_iteration;
+  // These are the full extent of the input data
+  psF32 ra_min;
+  psF32 ra_max;
+  psF32 dec_min;
+  psF32 dec_max;
+
+  // Because the binning code can't handle subsections sanely, these hold the working subsection.
+  psF32 x_min;
+  psF32 x_max;
+  psF32 y_min;
+  psF32 y_max;
+
+  psArray *stack_data;
+  psArray *stacks;        // List of stacks to be corrected.
+  psString outRoot;       // Output root name
+  pmConfig *config;       // Configuration
+} ppBackgroundStackData;
+
+/// Initialise data for processing
+ppBackgroundStackData *ppBackgroundStackDataInit(int *argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse command-line arguments
+bool ppBackgroundStackArguments(ppBackgroundStackData *data, // Data for processing
+				int argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse camera configurations
+bool ppBackgroundStackCamera(ppBackgroundStackData *data // Data for processing
+    );
+
+/// Loop over input data, processing
+bool ppBackgroundStackLoop(ppBackgroundStackData *data // Data for processing
+    );
+
+bool ppBackgroundStackModelFitOTASolution(ppBackgroundStackData *data);
+bool ppBackgroundStackDataModelFit(ppBackgroundStackData *data);
+bool ppBackgroundStackCalibApply(ppBackgroundStackData *data);
+bool ppBackgroundStackModelFit(ppBackgroundStackData *data);
+
+
+/// Determine exit code
+psExit ppBackgroundExitCode(
+    psExit exitValue                    // Current exit code
+    );
+
+/// Add version information to header
+bool ppBackgroundVersionHeader(
+    psMetadata *header                  // Header to supplement
+    );
+
+/// Print version information to stdout
+void ppBackgroundVersionPrint(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackArguments.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackArguments.c	(revision 41114)
@@ -0,0 +1,91 @@
+/** @file ppBackgroundStackArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppBackgroundStack
+ *
+ *  @author Paul Price
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackgroundStack.h"
+
+/// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  ppBackgroundStackData *data   // Run-time data
+    )
+{
+    fprintf(stderr, "\nPan-STARRS background replacement\n\n");
+    fprintf(stderr, "Usage: %s OUTPUT_ROOT\n\n", program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(data);
+
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+
+bool ppBackgroundStackArguments(ppBackgroundStackData *data, int argc, char *argv[])
+{
+    assert(data);
+    assert(data->config);
+
+    psMetadata *arguments = data->config->arguments;
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-input", 0, "Filename of input metadata", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-image", 0, "Filename of image (required)", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-mask", 0,  "Filename of mask", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-fitOTAs", 0, "", false);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-OTApath", 0, "", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-dumpconfig", 0, "", NULL);
+
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 2) {
+        usage(argv[0], arguments, data);
+    }
+
+    unsigned int numBad = 0; // Number of bad lines
+    data->contents = psMetadataConfigRead(NULL, &numBad,psMetadataLookupStr(NULL, arguments, "-input"),false);
+    if (!(data->contents) || numBad > 0) {
+      psError(PPBACKGROUND_ERR_CONFIG, false, "Unable to cleanly read MDC file with inputs.");
+      return(false);
+    }
+
+    // Add any images to generate for to the list
+    // If we're going to do this for a full projection cell, this probably needs to be a metadata object.
+    psArrayAdd(data->stacks, data->stacks->n, psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-image")));
+
+    // Determine what we're doing with the OTA solution
+    if ((!psMetadataLookupBool(NULL,arguments,"-fitOTAs"))&&
+	(!psMetadataLookupStr(NULL,arguments,"-OTApath"))) {
+      psError(PPBACKGROUND_ERR_CONFIG, false, "No OTA solution path (-OTApath) provided, and no request to model this (-fitOTAs).");
+      return(false);
+    }
+    data->fit_OTAS = psMetadataLookupBool(NULL, arguments, "-fitOTAs");
+    data->OTApath  = psMetadataLookupStr(NULL, arguments, "-OTApath");
+
+
+    // This is the output base
+    data->outRoot = psStringCopy(argv[1]);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "OUTPUT", 0, "Output root name", data->outRoot);
+
+    psTrace("ppBackgroundStack", 1, "Done reading command-line arguments\n");
+
+
+    PS_ASSERT_STRING_NON_EMPTY(data->outRoot, false);
+
+    return true;
+}
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackCamera.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackCamera.c	(revision 41114)
@@ -0,0 +1,345 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackgroundStack.h"
+
+/// Add a single filename to the arguments as an array, so that it can be used with pmFPAfileBindFromArgs, etc
+static void fileArguments(const char *file, // The symbolic name for the file
+                          const char *name, // The name of the file
+                          const char *comment, // Description of the file
+                          pmConfig *config // Configuration
+    )
+{
+    psArray *files = psArrayAlloc(1); // Array with file names
+    files->data[0] = psStringCopy(name);
+    if (psMetadataLookup(config->arguments, file)) {
+        psMetadataRemoveKey(config->arguments, file);
+    }
+    psMetadataAddArray(config->arguments, PS_LIST_TAIL, file, 0, comment, files);
+    psFree(files);
+    return;
+}
+
+
+bool ppBackgroundStackCamera(ppBackgroundStackData *data // Run-time data
+    )
+{
+    bool status;                        // Status of file definition
+    pmConfig *config = data->config;    // Because I'm reusing code.
+    int u,v;
+    //    size_t A,P;
+    double RR = -9999.0 ,DD = -99999.0,rr = 9999.0,dd = 99999.0;
+    // FIX Figure out what stacks we have to deal with.
+    psString stackName = data->stacks->data[0];
+    fileArguments("IMAGE", stackName, "Input image", data->config);
+    psFree(stackName);
+    pmFPAfile *stack = pmFPAfileDefineFromArgs(&status, data->config,
+					       "PPBACKGROUND.STACK", "IMAGE");
+    if (!status || !stack) {
+      psError(psErrorCodeLast(), false, "Failed to build file from PPBACKGROUND.STACK");
+      return false;
+    }
+
+    if (!pmAstromReadBilevelMosaic(stack->fpa,stack->fpa->hdu->header)) {
+      psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for input stack.");
+      return false;
+    }
+    psArrayAdd(data->stack_data,data->stack_data->n,psMemIncrRefCounter(stack));
+    pmFPAfileActivate(config->files, false, NULL);
+
+    // You know what? Let's just fucking lie to config->files.
+    psMetadata *config_files = config->files;
+    config->files = NULL;
+    config->files = psMetadataAlloc();
+    
+    // Read over the input background models.    
+    psMetadataIterator *iter = psMetadataIteratorAlloc(data->contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item; // Item from iteration
+    int i = 0;
+    while ((item = psMetadataGetAndIncrement(iter))) {
+      i++;
+      if (item->type != PS_DATA_METADATA) {
+	psError(PPBACKGROUND_ERR_ARGUMENTS, true,
+		"Component %s of the input metadata is not of type METADATA", item->name);
+	psFree(iter);
+	return(false);
+      }
+      
+      // Pull out the information for this exposure
+      psMetadata *input = item->data.md; // the input metadata of interest
+      psString smfFileName = psMetadataLookupStr(NULL, input, "astrom");
+      psMetadata *modelContent = psMetadataLookupMetadata(NULL, input, "models");
+
+      // Allocate the model metadata object
+      psMetadata *Bmodel = psMetadataAlloc();
+      
+      // Read the smf file from this item
+      fileArguments("astrom",smfFileName,"",config);
+      pmFPAfile *smfFile = pmFPAfileDefineFromArgs(&status, config, "PSWARP.ASTROM","astrom");
+      
+      // taking from pswarpLoadAstrometry.c
+      smfFile->type = PM_FPA_FILE_WCS;
+      // Read the SMF data
+      pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
+
+      if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	return NULL;
+      }
+      if (!pmFPAfileRead(smfFile,view,config)) {
+	psError(PS_ERR_IO, false, "failed READ at FPA %s", smfFile->name);
+	psFree(view);
+	return false;
+      }
+      
+      printf("CZW: Item %d\n",i);
+      // find the FPA phu
+      bool bilevelAstrometry = false;
+      pmHDU *phu = pmFPAviewThisPHU(view, smfFile->fpa);
+      if (phu) {
+	char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+	if (ctype) {
+	  bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+	}
+      }
+      if (bilevelAstrometry) {
+	if (!pmAstromReadBilevelMosaic(smfFile->fpa, phu->header)) {
+	  psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for input FPA.");
+	  psFree(view);
+	  return false;
+	}
+      }
+      
+      psF32 exptime = 1.0;
+      exptime = psMetadataLookupF32(NULL, phu->header, "EXPTIME");
+
+      pmChip *chip;                       // Chip from FPA
+      while ((chip = pmFPAviewNextChip(view, smfFile->fpa, 1))) {
+	if (!chip->process || !chip->file_exists) {
+	  continue;
+	}
+	const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+	if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	  psError(psErrorCodeLast(), false, "Error loading data from files.");
+	  return false;
+	}
+	if (chip->cells->n != 1) {
+	  psWarning("More than one cell present for chip %d", view->chip);
+	}
+
+/* 	psMemStats(0,&A,&P); */
+/* 	fprintf(stderr,"chip %ld %ld\n",A,P); */
+
+	// read WCS data from the corresponding header
+	pmHDU *hdu = pmFPAviewThisHDU (view, smfFile->fpa);
+	if (bilevelAstrometry) {
+	  if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+	    psWarning("Unable to read bilevel chip astrometry for chip %s.", chipName);
+	    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+	      psError(psErrorCodeLast(), false, "Error saving data to files.");
+	      return false;
+	    }
+	    continue;
+	  }
+	} else {
+	  // we use a default FPA pixel scale of 1.0
+	  psWarning("Reading WCS astrometry for chip %s.", chipName);
+	  if (!pmAstromReadWCS(smfFile->fpa, chip, hdu->header, 1.0)) {
+	    psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for input FPA.");
+	    psFree(view);
+	    return false;
+	  }
+	}
+
+	//  Load model data for this chip
+	psString modelFileName = pmConfigConvertFilename(psMetadataLookupStr(NULL, modelContent, chipName),
+							 config, PM_FPA_MODE_READ, false);
+	psFits *modelFits = psFitsOpen(modelFileName,"r");
+	psImage *image = psFitsReadImage(modelFits,psRegionSet(0,0,0,0),0);
+	psMetadata *header = psFitsReadHeader(NULL,modelFits);
+	
+	// Allocate the data structures for this chip
+	psImage *raim = psImageAlloc(image->numCols,image->numRows,PS_TYPE_F32);
+	psImage *decim = psImageAlloc(image->numCols,image->numRows,PS_TYPE_F32);
+	psImage *model = psImageAlloc(image->numCols,image->numRows,PS_TYPE_F32);
+
+	// Read header and construct original positions
+	psS32 naxis1 = psMetadataLookupS32(NULL, header, "NAXIS1");
+	psS32 naxis2 = psMetadataLookupS32(NULL, header, "NAXIS2");
+	psS32 imnaxis1 = psMetadataLookupS32(NULL, header, "IMNAXIS1");
+	psS32 imnaxis2 = psMetadataLookupS32(NULL, header, "IMNAXIS2");
+	psString ccdsum = psMetadataLookupStr(NULL, header, "CCDSUM");
+	psS32 xbin = atoi(strtok(ccdsum," "));
+	psS32 ybin = atoi(strtok(NULL, " "));
+	psS32 xoffset = (naxis1 * xbin - imnaxis1) / (2 * xbin);
+	psS32 yoffset = (naxis2 * ybin - imnaxis2) / (2 * ybin);
+
+	psPlane *pix = psPlaneAlloc();   // Pixel coordinates on chip
+	psPlane *fp = psPlaneAlloc();    // Focal plane coordinates
+	psPlane *tp = psPlaneAlloc();    // Tangent plane coordinates
+	psSphere *sky = psSphereAlloc(); // Sky coordinates
+	
+	for (v = 0; v < image->numRows; v++) {
+	  pix->y = (v - yoffset) * ybin;
+	  for (u = 0; u < image->numCols; u++) {
+	    pix->x = (u - xoffset) * xbin;
+	    psPlaneTransformApply(fp, chip->toFPA, pix);
+	    psPlaneTransformApply(tp, smfFile->fpa->toTPA, fp);
+	    psDeproject(sky, tp, smfFile->fpa->toSky);
+
+	    psProject(tp,sky,stack->fpa->toSky);
+
+	    raim->data.F32[v][u] = tp->x;
+	    decim->data.F32[v][u] = tp->y;
+	    model->data.F32[v][u] = 0.0;
+	    image->data.F32[v][u] /= exptime;
+	    // Check the bounds so we'll know how large of an area to model in the map
+	    if (tp->x < data->ra_min) { data->ra_min = tp->x; }
+	    else if (tp->x > data->ra_max) { data->ra_max = tp->x; }
+	    if (tp->y < data->dec_min) { data->dec_min = tp->y; }
+	    else if (tp->y > data->dec_max) { data->dec_max = tp->y; }
+
+	    if (sky->r < rr) { rr = sky->r; }
+	    else if (sky->r > RR) { RR = sky->r; }
+	    if (sky->r < dd) { dd = sky->d; }
+	    else if (sky->d > DD) { DD = sky->d; }
+	    
+	  }
+	}
+
+	// Allocate the model data for this chip
+	psMetadata *this_model = psMetadataAlloc();
+		
+	// Attach vectors to teh structure of the chip
+	psMetadataAddImage(this_model,PS_LIST_TAIL,
+			   "bkg image", 0,
+			   "ota space X vector", image);
+	psMetadataAddImage(this_model,PS_LIST_TAIL,
+			   "bkg ra", 0,
+			   "ota space ra vector", raim);
+	psMetadataAddImage(this_model,PS_LIST_TAIL,
+			   "bkg dec", 0,
+			   "ota space dec vector", decim);
+	psMetadataAddImage(this_model,PS_LIST_TAIL,
+			   "bkg calibrated data", 0,
+			   "ota space corrected data", model);
+
+	// Define default background model parameters, using the assumption:
+	// observed = camera + offset + scale * astrophysical
+	psMetadataAddF32(this_model,PS_LIST_TAIL,
+			 "bkg offset", PS_META_REPLACE,
+			 "background offset for this exposure/ota pair", 0.0);
+	psMetadataAddF32(this_model,PS_LIST_TAIL,
+			 "bkg scale", PS_META_REPLACE,
+			 "background scale parameter for this exposure/ota pair", 1.0);
+	// Add this model to the current model
+	psMetadataAddMetadata(Bmodel,PS_LIST_TAIL,
+			      chipName, PS_META_REPLACE,
+			      "model data for this exposure/ota", this_model);
+	// Free model data for this chip
+	psFree(modelFileName);
+	psFree(modelFits);
+	psFree(header);
+	psFree(pix);
+	psFree(fp);
+	psFree(tp);
+	psFree(sky);
+	psFree(image);
+	psFree(raim);
+	psFree(decim);
+	psFree(model);
+	psFree(this_model);
+	
+	// Check to see if we've loaded or allocated an OTA solution container for this chip
+	if (!psMetadataLookupPtr(NULL,data->OTA_solutions,chipName)) { // No solution metadata entry exists for this chipName
+	  // FIX this should try to find the imagefile. 
+	  if (data->fit_OTAS) { // We are fitting OTAs, so allocate a new image
+	    psImage *solution = psImageAlloc(image->numCols,image->numRows,PS_TYPE_F32);
+	    psMetadataAddPtr(data->OTA_solutions,PS_LIST_TAIL,
+			     chipName, PS_DATA_UNKNOWN | PS_META_REPLACE,
+			     "OTA solution element", solution);
+	  }
+	  else { // We are not fitting OTAs, so read the one that should be saved on OTApath.
+	    psString solutionFileName = psStringCopy(data->OTApath);
+	    psStringAppend(&solutionFileName, ".%s.fits",chipName);
+	    psString resolvedFileName = pmConfigConvertFilename(solutionFileName,config,false,false);
+	    psFits *solutionFits = psFitsOpen(resolvedFileName,"r");
+	    psImage *solution = psFitsReadImage(solutionFits,psRegionSet(0,0,0,0),0);
+	    psMetadataAddImage(data->OTA_solutions,PS_LIST_TAIL,
+			       chipName, 0,
+			       "OTA solution element", solution);
+	    psFree(solutionFits);
+	    psFree(solutionFileName);
+	    psFree(solution);
+	  }
+	}
+      } // end chip loop
+
+      // Add the set of models to the datastructure
+      psMetadataAddMetadata(data->models, PS_LIST_TAIL,
+			    smfFile->origname, PS_META_REPLACE,
+			    "model data for this exposure", Bmodel);
+      psMetadataAddS16(data->models,PS_LIST_TAIL,
+		       "N", PS_META_REPLACE,
+		       "counter", i);
+      
+      if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+	psError(psErrorCodeLast(), false, "Error saving data to files.");
+	return false;
+      }
+      pmFPAfileActivate(config->files, false, NULL);
+      psFree(modelContent);
+      //       psFree(smfFile);       // This gets freed when we trash the config->files structure.  I think.  
+      psFree(smfFileName);
+      psFree(view);
+
+      psFree(config->files);
+      config->files = psMetadataAlloc();
+      // 
+
+      //
+    } // end smf loop
+
+    // Unlie now.
+    psFree(config->files);
+    config->files = config_files;
+    
+    // remove the {ra|dec}_min values from everything so we don't confuse the binning code.
+/*     psMetadataIterator *expIter = psMetadataIteratorAlloc(data->models, PS_LIST_HEAD, NULL); */
+/*     psMetadataItem *expItem; */
+/*     while ((expItem = psMetadataGetAndIncrement(expIter))) { */
+/*       if (expItem->type != PS_DATA_METADATA) { */
+/* 	continue; // This is the N counter */
+/*       } */
+/*       psMetadataIterator *chipIter = psMetadataIteratorAlloc(expItem->data.md, PS_LIST_HEAD, NULL); */
+/*       psMetadataItem *chipItem; */
+/*       while ((chipItem = psMetadataGetAndIncrement(chipIter))) { */
+/* 	psImage *ra    = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg ra"); */
+/* 	psImage *dec   = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg dec"); */
+/* 	for (v = 0; v < ra->numRows; v++) { */
+/* 	  for (u = 0; u < ra->numCols; u++) { */
+/* 	    ra->data.F32[v][u] -= data->ra_min; */
+/* 	    dec->data.F32[v][u] -= data->dec_min; */
+/* 	  } */
+/* 	} */
+/*       } // End chip */
+/*       psFree(chipIter); */
+/*     } // End smfs */
+/*     psFree(expIter); */
+/*     // And from the structure objects. */
+/*     data->x_min -= data->ra_min; */
+/*     data->y_min -= data->dec_min; */
+/*     data->x_max -= data->ra_min; */
+/*     data->y_max -= data->dec_min; */
+/* /\*     data->ra_max -= data->ra_min; *\/ */
+/* /\*     data->dec_max -= data->dec_min; *\/ */
+/* /\*     data->ra_min = 0.0; *\/ */
+/* /\*     data->dec_min = 0.0; *\/ */
+
+    
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackData.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackData.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackData.c	(revision 41114)
@@ -0,0 +1,79 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackgroundStack.h"
+
+// Destructor
+static void backgroundDataFree(ppBackgroundStackData *data // Data to free
+    )
+{
+  // CZW 2014-03-24
+  // The things that are commented out here, and therefore don't get freed are in that state because
+  // I'd far rather spend time sorting out actual math issues than dealing with data structures.
+  
+  //  psFree(data->contents);
+  
+//  psFree(data->smfs);
+
+  psFree(data->models);
+  psFree(data->OTA_solutions);
+  psFree(data->OTApath);
+
+  psFree(data->modelMap);
+
+
+  psFree(data->stack_data);
+  psFree(data->stacks);
+  psFree(data->outRoot);
+  //psFree(data->config);
+  return;
+}
+
+
+ppBackgroundStackData *ppBackgroundStackDataAlloc(void)
+{
+    ppBackgroundStackData *data = psAlloc(sizeof(ppBackgroundStackData)); // Processing data, to return
+    psMemSetDeallocator(data, (psFreeFunc)backgroundDataFree);
+
+    data->contents = NULL;
+    data->smfs = psArrayAlloc(0);
+
+    data->models = psMetadataAlloc();
+    data->OTA_solutions = psMetadataAlloc();
+    data->fit_OTAS = false;
+    data->OTApath = NULL;
+
+    data->modelMap = NULL;
+    data->model_iteration = 0;
+    data->ra_min   = 1e9;
+    data->ra_max   = -1e9;
+    data->dec_min  = 1e9;
+    data->dec_max  = -1e9;
+
+    data->stack_data = psArrayAlloc(0);
+    data->stacks = psArrayAlloc(0);
+    data->outRoot = NULL;
+    data->config = NULL;
+
+    return data;
+}
+
+
+ppBackgroundStackData *ppBackgroundStackDataInit(int *argc, char **argv)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    ppBackgroundStackData *data = ppBackgroundStackDataAlloc(); // Processing data, to return
+    data->config = pmConfigRead(argc, argv, PPBACKGROUND_RECIPE);
+    if (!data->config) {
+        psError(psErrorCodeLast(), false, "Unable to read configuration.");
+        return NULL;
+    }
+    return data;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackLoop.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackLoop.c	(revision 41114)
@@ -0,0 +1,377 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppBackgroundStack.h"
+
+#define WCS_TOLERANCE 0.001             // Tolerance for WCS
+
+
+bool ppBackgroundStackLoop(ppBackgroundStackData *data // Run-time data
+    )
+{
+  pmConfig *config = data->config;                                        // Configuration data
+    int i;
+    //
+    // Solve the data into a consistent model.
+    { // This block does the initialization
+      // If we didn't load the OTA solution from an external source, we need to build one.
+      if (data->fit_OTAS) {
+	printf("Fitting OTAs!\n");
+	if (!ppBackgroundStackModelFitOTASolution(data)) {
+	  // Currently can't fail.
+	  psError(psErrorCodeLast(), false, "Error calculating the OTA solution");
+	  return(false);
+	}
+
+	if (data->OTApath) {
+	  // This will write the solutions out.
+	  psMetadataIterator *iter = psMetadataIteratorAlloc(data->OTA_solutions, PS_LIST_HEAD, NULL); // Iterator
+	  psMetadataItem *item; // Item from iteration
+	  int i = 0;
+	  while ((item = psMetadataGetAndIncrement(iter))) {
+	    i++;
+	    if (item->type != PS_DATA_IMAGE) {
+	      psString chipName = item->name;
+	      psImage  *image    = item->data.V;
+	      psMetadata *header = psMetadataAlloc();
+	      psString solutionFileName = psStringCopy(data->OTApath);
+	      psStringAppend(&solutionFileName, ".%s.fits",chipName);
+	      psString resolved = pmConfigConvertFilename(solutionFileName,config,true,true);
+	      psFits *solutionFits = psFitsOpen(resolved,"w");
+	      if (!solutionFits) {
+		psError(2, false, "Unable to open FITS file %s to write model %s.", resolved, chipName);
+		psFitsClose(solutionFits);
+		psFree(resolved);
+		return(false);
+	      }
+	      if (!psFitsWriteImage(solutionFits, header, image, 0, NULL)) {
+		psError(2, false, "Unable to write FITS image %s.", resolved);
+		psFitsClose(solutionFits);
+		psFree(resolved);
+		return false;
+	      }
+	      if (!psFitsClose(solutionFits)) {
+		psError(2, false, "Unable to close FITS image %s.", resolved);
+		psFree(resolved);
+		return false;
+	      }
+	      psFree(resolved);
+	      psFree(solutionFileName);
+	    }
+	  }
+	  psFree(item);
+	  psFree(iter);
+	}
+	
+      }
+    } // End initialization block.      
+
+
+    // Loop over the input images, and apply the models to construct the restored versions.
+    for (i = 0; i < data->stack_data->n; i++) {
+      pmFPAfile *stack = data->stack_data->data[i];
+      pmFPAview *view = pmFPAviewAlloc(0);
+
+      //      pmHDU *phu = pmFPAviewThisPHU(view, stack->fpa);
+      psF32 exptime = 1.0;
+      exptime = psMetadataLookupF32(NULL, stack->fpa->hdu->header, "EXPTIME");
+      
+      // PART 1:
+      // Determine the extent of the model map for this stack
+      data->x_min = 99e99; data->x_max = -99e99;
+      data->y_min = 99e99; data->y_max = -99e99;
+      // Allocate the modelMap for the region we're covering.
+      psPlane *pix = psPlaneAlloc();   // Pixel coordinates on chip
+      psPlane *tp = psPlaneAlloc();    // Focal plane coordinates
+      
+      pix->x = 0; pix->y = 0;
+      psPlaneTransformApply(tp, stack->fpa->toTPA, pix);
+      printf("%f %f -> %f %f\n",pix->x,pix->y,tp->x,tp->y);
+      if (tp->x < data->x_min) { data->x_min = tp->x; }
+      if (tp->x > data->x_max) { data->x_max = tp->x; }
+      if (tp->y < data->y_min) { data->y_min = tp->y; }
+      if (tp->y > data->y_max) { data->y_max = tp->y; }
+
+      pix->x = 6240; pix->y = 0;
+      psPlaneTransformApply(tp, stack->fpa->toTPA, pix);
+      printf("%f %f -> %f %f\n",pix->x,pix->y,tp->x,tp->y);
+      if (tp->x < data->x_min) { data->x_min = tp->x; }
+      if (tp->x > data->x_max) { data->x_max = tp->x; }
+      if (tp->y < data->y_min) { data->y_min = tp->y; }
+      if (tp->y > data->y_max) { data->y_max = tp->y; }
+
+      pix->x = 6240; pix->y = 6243;
+      psPlaneTransformApply(tp, stack->fpa->toTPA, pix);
+      printf("%f %f -> %f %f\n",pix->x,pix->y,tp->x,tp->y);
+      if (tp->x < data->x_min) { data->x_min = tp->x; }
+      if (tp->x > data->x_max) { data->x_max = tp->x; }
+      if (tp->y < data->y_min) { data->y_min = tp->y; }
+      if (tp->y > data->y_max) { data->y_max = tp->y; }
+
+      pix->x = 0; pix->y = 6243;
+      psPlaneTransformApply(tp, stack->fpa->toTPA, pix);
+      printf("%f %f -> %f %f\n",pix->x,pix->y,tp->x,tp->y);
+      if (tp->x < data->x_min) { data->x_min = tp->x; }
+      if (tp->x > data->x_max) { data->x_max = tp->x; }
+      if (tp->y < data->y_min) { data->y_min = tp->y; }
+      if (tp->y > data->y_max) { data->y_max = tp->y; }
+
+      psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+      psImageBinning *binning = psImageBinningAlloc();
+      binning->nXruff = 13; // Number of samples
+      binning->nYruff = 13; 
+      binning->nXfine = ceil(data->x_max - data->x_min) + 1;
+      binning->nYfine = ceil(data->y_max - data->y_min) + 1;
+      binning->nXskip = floor(data->x_min - data->ra_min) + 1;
+      binning->nYskip = floor(data->y_min - data->dec_min) + 1;
+      psImageBinningSetScale(binning,PS_IMAGE_BINNING_CENTER);
+      printf("Sizes: sky: %f %f -> %f %f :: tp: %f %f -> %f %f :: map: %d %d\n",
+	     0.0,0.0,0.0,0.0,data->ra_min,data->dec_min,data->ra_max,data->dec_max,binning->nXfine,binning->nYfine);
+      printf("Sizes: corners: %f %f -> %f %f map: %d %d\n",
+	     data->x_min,data->y_min,data->x_max,data->y_max,
+	     binning->nXfine,binning->nYfine);
+      psImage *sizeImage = psImageAlloc(binning->nXfine,binning->nYfine,PS_TYPE_F32);
+      P_PSIMAGE_SET_COL0(sizeImage, data->x_min);
+      P_PSIMAGE_SET_ROW0(sizeImage, data->y_min);
+      data->modelMap = psImageMapAlloc(sizeImage,binning,stats);
+      
+      // PART 2:
+      // Solve the data into a model for this region of the sky.
+
+      // This seems wrong, but I need a blank modelMap object, so we fit the zero-data we've stored in the calib objects
+      printf("Determining blank modelMap!\n");
+      if (!ppBackgroundStackModelFit(data)) {
+	psError(psErrorCodeLast(), false, "Error determining the blank modelMap object.");
+	return(false);
+      }
+      
+      // Apply OTA solution
+      printf("Calib apply!\n");
+      if (!ppBackgroundStackCalibApply(data)) {
+	psError(psErrorCodeLast(), false, "Error applying the calibration models.");
+	return(false);
+      }
+
+      // This is where an iterative solution loop would likely start.
+      for (int iterator = 0; iterator < 4; iterator++) {
+	// Construct the offset information
+	printf("Model fit!\n");
+	if (!ppBackgroundStackDataModelFit(data)) {
+	  psError(psErrorCodeLast(), false, "Error determining the exposure/OTA scaling.");
+	  return(false);
+	}
+	
+	// Apply full correction
+	printf("Calib apply!\n");
+	if (!ppBackgroundStackCalibApply(data)) {
+	  psError(psErrorCodeLast(), false, "Error applying the calibration models.");
+	  return(false);
+	}      
+	
+	// Fit the new model
+	printf("Determining model!\n");
+	if (!ppBackgroundStackModelFit(data)) {
+	  psError(psErrorCodeLast(), false, "Error determining the modelMap object.");
+	  return(false);
+	}
+      } // End loop
+
+      // PART 3:
+      // Define output products
+      // Define output image.  Why is this always so hard to do?
+      pmFPA *tmp_fpa1,*tmp_fpa2;
+      tmp_fpa1 = pmFPAConstruct(config->camera,config->cameraName);
+      tmp_fpa2 = pmFPAConstruct(config->camera,config->cameraName);
+      
+      pmFPAfile *stack_model = pmFPAfileDefineOutput(config,tmp_fpa1,"PPBACKGROUND.STACK.MODEL");
+      
+      if (!stack_model) {
+	psError(psErrorCodeLast(), false, "Unable to generate output model");
+	return (false);
+      }
+      
+      pmFPAfile *stack_corr  = pmFPAfileDefineOutput(config,tmp_fpa2,"PPBACKGROUND.STACK.OUTPUT");
+      if (!stack_corr) {
+	psError(psErrorCodeLast(), false, "Unable to generate output result");
+	return (false);
+      }
+      stack_model->save = true;
+      stack_corr->save = true;
+      
+      printf("I'm about to loop over the parts of this stack: %d\n",i);
+      // Iterate over the images.
+      pmFPAfileActivate(config->files,true,"PPBACKGROUND.STACK");
+      pmFPAfileActivate(config->files,true,"PPBACKGROUND.STACK.MODEL");
+      pmFPAfileActivate(config->files,true,"PPBACKGROUND.STACK.OUTPUT");
+      if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	psError(psErrorCodeLast(), false, "load failure for Chip");
+	return(false);
+      }
+      
+      pmChip *chip;
+      while ((chip = pmFPAviewNextChip(view, stack->fpa, 1))) {
+	if (!chip->process || !chip->file_exists) {
+	  continue;
+	}
+	
+	if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	  psError(psErrorCodeLast(), false, "load failure for Chip");
+	  return(false);
+	}
+	printf("  I'm in a chip\n");
+	pmCell *cell;
+	
+	while ((cell = pmFPAviewNextCell(view, stack->fpa, 1)) != NULL) {
+	  psLogMsg ("ppImageLoop", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+	  if (!cell->process || !cell->file_exists) {
+	    continue;
+	  }
+	  if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	    psError(psErrorCodeLast(), false, "load failure for Cell");
+	    return(false);
+	  }
+	  if (cell->readouts->n > 1) {
+	    psWarning ("Skipping Video Cell for ppImageDetrendReadout");
+	    continue;
+	  }
+	  printf("    I'm in a cell\n");
+
+
+	  // process each of the readouts
+	  pmReadout *readout;         // Readout from cell
+	  while ((readout = pmFPAviewNextReadout (view, stack->fpa, 1)) != NULL) {
+	    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	      	psError(psErrorCodeLast(), false, "load failure for Readout");
+		return(false);
+	    }
+	    if (!readout->data_exists) {
+	      continue;
+	    }
+	    printf("      I'm in a readout\n");
+
+	    // Futz with things to get an acceptable output product.
+	    pmCell *model_cell = pmFPAviewThisCell(view,stack_model->fpa);
+	    pmCell *corr_cell  = pmFPAviewThisCell(view,stack_corr->fpa);
+	    
+	    pmReadout *model = pmReadoutAlloc(model_cell);
+	    pmReadout *corr  = pmReadoutAlloc(corr_cell);
+	    model->data_exists = true;
+	    corr->data_exists = true;
+	    model->parent->data_exists = true;
+	    corr->parent->data_exists = true;
+	    model->parent->parent->data_exists = true;
+	    corr->parent->parent->data_exists = true;
+	    model->image = psImageAlloc(readout->image->numCols,readout->image->numRows,PS_TYPE_F32);
+	    corr->image = psImageAlloc(readout->image->numCols,readout->image->numRows,PS_TYPE_F32);
+
+	    model_cell->concepts = psMemIncrRefCounter(cell->concepts);
+	    model_cell->conceptsRead = cell->conceptsRead;
+	    corr_cell->concepts = psMemIncrRefCounter(cell->concepts);
+	    corr_cell->conceptsRead = cell->conceptsRead;
+	    
+	    psPlane *pix = psPlaneAlloc();   // Pixel coordinates on chip
+	    psPlane *fp = psPlaneAlloc();    // Focal plane coordinates
+	    psPlane *tp = psPlaneAlloc();    // Tangent plane coordinates
+	    
+	    int x,y;
+	    for (y = 0; y < readout->image->numRows; y++) {
+	      pix->y = y;
+	      for (x = 0; x < readout->image->numCols; x++) {
+		pix->x = x;
+		// Calculate model for each pixel of output
+		//		psPlaneTransformApply(fp, chip->toFPA, pix);
+		psPlaneTransformApply(tp, stack->fpa->toTPA, pix);
+		
+		model->image->data.F32[y][x] = exptime * psImageMapEval(data->modelMap,tp->x,tp->y);
+		corr->image->data.F32[y][x] = readout->image->data.F32[y][x] + model->image->data.F32[y][x];
+		
+	      }
+	    }
+	    psFree(pix);
+	    psFree(fp);
+	    psFree(tp);
+
+	    // Copy WCS (from ppStackUpdateHeader)
+	    pmHDU *inHDU = pmHDUFromCell(readout->parent);
+	    model->parent->hdu = pmHDUAlloc(NULL);
+	    corr->parent->hdu = pmHDUAlloc(NULL);
+	    pmHDU *modHDU= pmHDUFromCell(model->parent);
+	    pmHDU *corHDU= pmHDUFromCell(corr->parent);
+
+	    if (!modHDU || !inHDU) {
+	      psWarning("Unable to find HDU at FPA level to copy wcs!");
+	    }
+	    else {
+	      if (!pmAstromReadWCS(stack_model->fpa,model_cell->parent,inHDU->header,1.0)) {
+		psErrorClear();
+		psWarning("Unable to read WCS astrometry from input FPA!");
+	      }
+	      else {
+		if (!modHDU->header) {
+		  modHDU->header = psMetadataAlloc();
+		}
+		if (!pmAstromWriteWCS(modHDU->header, stack_model->fpa,model_cell->parent, WCS_TOLERANCE)) {
+		  psErrorClear();
+		  psWarning("Unable to read WCS astrometry from input FPA!");
+		}
+	      }
+	      if (!pmAstromReadWCS(stack_corr->fpa,corr_cell->parent,inHDU->header,1.0)) {
+		psErrorClear();
+		psWarning("Unable to read WCS astrometry from input FPA!");
+	      }
+	      else {
+		if (!corHDU->header) {
+		  corHDU->header = psMetadataAlloc();
+		}
+		if (!pmAstromWriteWCS(corHDU->header, stack_corr->fpa,corr_cell->parent, WCS_TOLERANCE)) {
+		  psErrorClear();
+		  psWarning("Unable to read WCS astrometry from input FPA!");
+		}
+	      }
+	    } // End WCS saving.
+
+	    
+	  } // Close readout
+	  printf("    I'm done with that readout\n");
+	  // Close output image
+	} // Close Cell
+	// Close cells (XXX shouldn't pmFPAfileClose iterate down as needed?)
+	view->cell = -1;
+	while ((cell = pmFPAviewNextCell(view, stack->fpa, 1)) != NULL) {
+	  if (!cell->process || !cell->file_exists) {
+	    continue;
+	  }
+	  if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+	    psError(psErrorCodeLast(), false, "save failure for Cell");
+	    return(false);
+	  }
+	}
+
+        // Close chip
+	if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+	  psError(psErrorCodeLast(), false, "save failure for Chip");
+	  return(false);
+	}
+      } // Close chip.
+      // Output and Close FPA
+      if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+	psError(psErrorCodeLast(), false, "save failure for FPA");
+	return(false);
+      }
+      psFree(view);
+      psFree(data->modelMap);
+      psFree(sizeImage);
+    }
+		
+    
+    return(true);
+}
+
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackMath.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackMath.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundStackMath.c	(revision 41114)
@@ -0,0 +1,273 @@
+/** @file ppBackgroundStackArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppBackgroundStack
+ *
+ *  @author Paul Price
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackgroundStack.h"
+
+// data->models->name->XY__->{image/ra/dec/calibrated/offset/scale}
+// data->models->counter->XY__->{image/ra/dec/calibrated/offset/scale}
+
+//
+// Determine the best OTA-to-OTA solution for this set of exposures/chips.
+// This is done by finding the median value of the set of (ota,u,v) values for
+// all exposures.
+// solution_{OTA}(u,v) = < data_{OTA}(u,v) >_{exposures}
+bool ppBackgroundStackModelFitOTASolution(ppBackgroundStackData *data) {
+  int u,v;
+  psMetadataIterator *iter = psMetadataIteratorAlloc(data->OTA_solutions, PS_LIST_HEAD, NULL); // Iterate over all chips.
+  psMetadataItem *item;
+  psStats *stats = psStatsAlloc( PS_STAT_ROBUST_MEDIAN );
+  psS16 N = psMetadataLookupS16(NULL, data->models, "N");
+  
+  while ((item = psMetadataGetAndIncrement(iter))) {
+    if (item->type != PS_DATA_UNKNOWN) {
+      psWarning("This seems like I have a not-data, when I expect a data.");
+      continue;
+    }
+    psString workingChip = item->name;
+    psImage  *solution    = item->data.V;
+
+    for (v = 0; v < solution->numRows; v++) {
+      for (u = 0; u < solution->numCols; u++) {
+
+	psVector *tmp = psVectorAllocEmpty(N,PS_TYPE_F32);	
+
+	psMetadataIterator *expIter = psMetadataIteratorAlloc(data->models, PS_LIST_HEAD, NULL);
+	psMetadataItem *expItem;
+	while ((expItem = psMetadataGetAndIncrement(expIter))) {
+	  if (expItem->type != PS_DATA_METADATA) {
+	    continue; // This is the N counter
+	  }
+	  psMetadata *chipData = psMetadataLookupPtr(NULL, expItem->data.md, workingChip);
+	  if (!chipData) { continue; }
+	  psImage *image      = psMetadataLookupPtr(NULL, chipData, "bkg image");
+	  if (!image) { continue; }
+	  psVectorAppend(tmp,image->data.F32[v][u]);
+	} // End loop over exposures
+	psStatsInit(stats);
+	psVectorStats(stats,tmp,NULL,NULL,0);
+	solution->data.F32[v][u] = stats->robustMedian;
+
+	psFree(expIter);
+	psFree(tmp);
+      } // End u
+    } // End v
+    // Remove the median value from this data.  We just want the tilts, not the offsets
+    psStatsInit(stats);
+    psImageStats(stats,solution,NULL,0);
+    for (v = 0; v < solution->numRows; v++) {
+      for (u = 0; u < solution->numCols; u++) {
+	solution->data.F32[v][u] -= stats->robustMedian;
+      }
+    }
+    
+    
+  } // End working chip scan
+
+  psFree(iter);
+  psFree(stats);
+  return(true);
+}
+
+//
+// Determine the relative offset of this exposure/ota relative to the current estimate of the sky at this point.
+// Note: the initial assumption of the model is a flat grid of zero, "the sky contains nothing real."
+// SOLVE: model(r,d) = scale * data(r,d) + offset + solution_{OTA}(u,v)
+// FOR: scale, offset
+bool ppBackgroundStackDataModelFit(ppBackgroundStackData *data) {
+  int u,v;
+
+  psMetadataIterator *expIter = psMetadataIteratorAlloc(data->models, PS_LIST_HEAD, NULL);
+  psMetadataItem *expItem;
+  while ((expItem = psMetadataGetAndIncrement(expIter))) {
+    if (expItem->type != PS_DATA_METADATA) {
+      continue; // This is the N counter
+    }
+    
+    psMetadataIterator *chipIter = psMetadataIteratorAlloc(expItem->data.md, PS_LIST_HEAD, NULL);
+    psMetadataItem *chipItem;
+    while ((chipItem = psMetadataGetAndIncrement(chipIter))) {
+      //      const char *chipName = chipItem->name;
+      
+      psImage *image = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg image");
+      psImage *ra    = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg ra");
+      psImage *dec   = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg dec");
+      //      psImage *camera= psMetadataLookupPtr(NULL, data->OTA_solutions, chipName);      
+      psVector *obs  = psVectorAllocEmpty(image->numRows, PS_TYPE_F32);
+      psVector *model= psVectorAllocEmpty(image->numRows, PS_TYPE_F32);
+
+      int j = 0;
+      int used = 0;
+      for (v = 0; v < image->numRows; v++) {
+	for (u = 0; u < image->numCols; u++) {
+	  if ((ra->data.F32[v][u] < data->x_min)||(ra->data.F32[v][u] > data->x_max)||
+	      (dec->data.F32[v][u] < data->y_min)||(dec->data.F32[v][u] > data->y_max)) { j++; continue; }
+	  psVectorAppend(obs,image->data.F32[v][u]);// - camera->data.F32[v][u]);
+	  psVectorAppend(model, psImageMapEval(data->modelMap,ra->data.F32[v][u],dec->data.F32[v][u]));
+	  j++;
+	  used++;
+	}
+      }
+
+      if (used > 0) {
+
+	psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD,1);
+	int status = psVectorFitPolynomial1D(poly,NULL,0,model,NULL,obs);
+	printf("in model fit loop: %d %d %d %f %f\n",status,j,used,poly->coeff[0],poly->coeff[1]);
+	if (status && (poly->coeff[1] != 0.0)) {
+	  psMetadataAddF32(chipItem->data.md,PS_LIST_TAIL,"bkg offset", PS_META_REPLACE, "background offset for this exposure/ota pair", poly->coeff[0]);
+	  psMetadataAddF32(chipItem->data.md,PS_LIST_TAIL,"bkg scale", PS_META_REPLACE, "background scale for this exposure/ota pair", poly->coeff[1]);
+	}
+	psFree(poly);
+      }
+    } // End OTA loop
+    psFree(chipIter);
+  } // End smf/exp loop
+  psFree(expIter);
+  return(true);
+}
+
+
+//
+// Apply the corrections to attempt to put all exposures, and all OTAs onto a common level,
+//  such that they should all match the true sky
+// calib_{exposure,OTA}(r,d) = scale * data_{exposure,OTA}(r,d) + offset + solution_{OTA}(u,v)
+bool ppBackgroundStackCalibApply(ppBackgroundStackData *data) {
+  int u,v;
+
+  psMetadataIterator *expIter = psMetadataIteratorAlloc(data->models, PS_LIST_HEAD, NULL);
+  psMetadataItem *expItem;
+  while ((expItem = psMetadataGetAndIncrement(expIter))) {
+    if (expItem->type != PS_DATA_METADATA) {
+      continue; // This is the N counter
+    }
+    psMetadataIterator *chipIter = psMetadataIteratorAlloc(expItem->data.md, PS_LIST_HEAD, NULL);
+    psMetadataItem *chipItem;
+    while ((chipItem = psMetadataGetAndIncrement(chipIter))) {
+      const char *chipName = chipItem->name;
+      
+      psImage *image = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg image");
+      psImage *model = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg calibrated data");
+      psImage *camera= psMetadataLookupPtr(NULL, data->OTA_solutions, chipName);
+      psImage *ra    = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg ra");
+      psImage *dec   = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg dec");
+      
+      psF32 offset = psMetadataLookupF32(NULL,chipItem->data.md,"bkg offset");
+      psF32 scale  = psMetadataLookupF32(NULL,chipItem->data.md,"bkg scale");
+
+      for (v = 0; v < image->numRows; v++) {
+	for (u = 0; u < image->numCols; u++) {
+	  if ((ra->data.F32[v][u] < data->x_min)||(ra->data.F32[v][u] > data->x_max)||
+	      (dec->data.F32[v][u] < data->y_min)||(dec->data.F32[v][u] > data->y_max)) { continue; }
+
+	  model->data.F32[v][u] = scale * image->data.F32[v][u] + offset - camera->data.F32[v][u];
+	}
+      }
+    } // End chip
+    psFree(chipIter);
+  } // End smf
+  psFree(expIter);
+  return(true);
+}
+
+// 
+// Determine the best estimate of the true sky from the ensemble of calibrated samples:
+//  model(r,d) = < calib(r,d) >_{exposures,OTAs}
+// This "averaging" is done using the psImageMapClipFit.
+bool ppBackgroundStackModelFit(ppBackgroundStackData *data) {
+  long j = 0;
+  int u,v;
+
+  long used = 0;
+  psS16 N = psMetadataLookupS16(NULL, data->models, "N");
+  psVector *X = psVectorAllocEmpty(N * 13 * 13,PS_TYPE_F32);
+  psVector *Y = psVectorAllocEmpty(N * 13 * 13,PS_TYPE_F32);
+  psVector *Z = psVectorAllocEmpty(N * 13 * 13,PS_TYPE_F32);
+  psVector *E = psVectorAllocEmpty(N * 13 * 13,PS_TYPE_F32);
+  psVector *mask = psVectorAllocEmpty(N * 13 * 13,PS_TYPE_VECTOR_MASK);
+  j = 0;
+  
+  pmFPAview *view    = pmFPAviewAlloc(0);
+  psStats *stats     = psStatsAlloc( PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV );
+
+  psMetadataIterator *expIter = psMetadataIteratorAlloc(data->models, PS_LIST_HEAD, NULL);
+  psMetadataItem *expItem;
+  while ((expItem = psMetadataGetAndIncrement(expIter))) {
+    if (expItem->type != PS_DATA_METADATA) {
+      continue; // This is the N counter
+    }
+    psMetadataIterator *chipIter = psMetadataIteratorAlloc(expItem->data.md, PS_LIST_HEAD, NULL);
+    psMetadataItem *chipItem;
+    while ((chipItem = psMetadataGetAndIncrement(chipIter))) {
+      psImage *calib = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg calibrated data");
+      psImage *ra    = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg ra");
+      psImage *dec   = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg dec");
+#define DUMP_DATA 0
+#if DUMP_DATA
+      psImage *model = psMetadataLookupPtr(NULL, chipItem->data.md, "bkg image");
+
+      psF32 offset = psMetadataLookupF32(NULL,chipItem->data.md,"bkg offset");
+      psF32 scale  = psMetadataLookupF32(NULL,chipItem->data.md,"bkg scale");
+
+#endif
+      for (v = 0; v < calib->numRows; v++) {
+	for (u = 0; u < calib->numCols; u++) {
+	  if ((ra->data.F32[v][u] < data->x_min)||(ra->data.F32[v][u] > data->x_max)||
+	      (dec->data.F32[v][u] < data->y_min)||(dec->data.F32[v][u] > data->y_max)) {
+	    j++;	    
+	    continue; }
+	  psVectorAppend(X,ra->data.F32[v][u]);
+	  psVectorAppend(Y,dec->data.F32[v][u]);
+	  psVectorAppend(Z,calib->data.F32[v][u]);
+	  psVectorAppend(E,1.0);
+	  psVectorAppend(mask,0);
+/* 	  X->data.F32[j] = ra->data.F32[v][u]; */
+/* 	  Y->data.F32[j] = dec->data.F32[v][u]; */
+/* 	  Z->data.F32[j] = calib->data.F32[v][u]; */
+/* 	  E->data.F32[j] = 1.0; */
+/* 	  mask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 0; */
+	  used++;
+	  j++;
+#if DUMP_DATA
+	  printf("DATA %d %ld %ld %f %f %f %f %f %f\n",
+		 data->model_iteration,j,used,
+		 offset,scale,
+		 ra->data.F32[v][u],
+		 dec->data.F32[v][u],
+		 calib->data.F32[v][u],
+		 model->data.F32[v][u]);
+#endif 
+	}
+      }
+    } // End chip
+    psFree(chipIter);
+  } // End smfs
+  printf("%ld %ld\n", j,used);
+  bool fitStatus;
+  bool status = psImageMapClipFit(&fitStatus,data->modelMap,stats, mask, 1, X, Y, Z, E);
+  data->model_iteration++;
+  psFree(expIter);
+  psFree(X);
+  psFree(Y);
+  psFree(Z);
+  psFree(E);
+  psFree(mask);
+  psFree(stats);
+  psFree(view);
+  return(status);
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundVersion.c	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundVersion.c	(revision 41114)
@@ -0,0 +1,118 @@
+/** @file ppBackgroundVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppBackground
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 19:45:30 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppBackground.h"
+#include "ppBackgroundVersionDefinitions.h"
+
+#ifndef PPBACKGROUND_VERSION
+#error "PPBACKGROUND_VERSION is not set"
+#endif
+#ifndef PPBACKGROUND_BRANCH
+#error "PPBACKGROUND_BRANCH is not set"
+#endif
+#ifndef PPBACKGROUND_SOURCE
+#error "PPBACKGROUND_SOURCE is not set"
+#endif
+
+psString ppBackgroundVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PPBACKGROUND_BRANCH, PPBACKGROUND_VERSION);
+    return value;
+}
+
+psString ppBackgroundSource(void)
+{
+    return psStringCopy(PPBACKGROUND_SOURCE);
+}
+
+psString ppBackgroundVersionLong(void)
+{
+    psString version = ppBackgroundVersion();  // Version, to return
+    psString source = ppBackgroundSource();    // Source
+
+    psStringPrepend(&version, "ppBackground ");
+    psStringAppend(&version, " from %s, built %s, %s", source, __DATE__, __TIME__);
+    psFree(source);
+
+#ifdef __OPTIMIZE__
+    psStringAppend(&version, " optimised");
+#else
+    psStringAppend(&version, " unoptimised");
+#endif
+
+    return version;
+};
+
+bool ppBackgroundVersionHeader(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psString history = NULL;               // History string
+    psStringAppend(&history, "ppBackground at %s", timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+    psModulesVersionHeader(header);
+
+    psString version = ppBackgroundVersion(); // Software version
+    psString source  = ppBackgroundSource();  // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "VIZPAT_V", PS_META_REPLACE, NULL, PPBACKGROUND_VERSION);
+    
+    psStringPrepend(&version, "ppBackground version: ");
+    psStringPrepend(&version, "ppBackground source: ");
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, version);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, source);
+
+    psFree(version);
+    psFree(source);
+
+    return true;
+}
+
+void ppBackgroundVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("ppBackground", PS_LOG_INFO, "ppBackground at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppBackground = ppBackgroundVersionLong(); // ppBackground version
+
+    psLogMsg("ppBackground", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("ppBackground", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("ppBackground", PS_LOG_INFO, "%s", ppBackground);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppBackground);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundVersionDefinitions.h.in	(revision 41114)
+++ /branches/ccl_branches/ipponly-20191108/ppBackground/src/ppBackgroundVersionDefinitions.h.in	(revision 41114)
@@ -0,0 +1,8 @@
+#ifndef PPBACKGROUND_VERSION_DEFINITIONS_H
+#define PPBACKGROUND_VERSION_DEFINITIONS_H
+
+#define PPBACKGROUND_VERSION @PPBACKGROUND_VERSION@ // SVN version
+#define PPBACKGROUND_BRANCH  @PPBACKGROUND_BRANCH@  // SVN branch
+#define PPBACKGROUND_SOURCE  @PPBACKGROUND_SOURCE@  // SVN source
+
+#endif
