Index: /branches/ccl_branches/ipponly-20191108/ppViz/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/Makefile.am	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/Makefile.am	(revision 41113)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
Index: /branches/ccl_branches/ipponly-20191108/ppViz/autogen.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/autogen.sh	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/autogen.sh	(revision 41113)
@@ -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=ppViz
+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/ppViz/configure.ac
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/configure.ac	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/configure.ac	(revision 41113)
@@ -0,0 +1,40 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([ppViz], [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])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+IPP_STDCFLAGS
+
+IPP_VERSION
+
+AC_SUBST([PPVIZ_CFLAGS])
+AC_SUBST([PPVIZ_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  src/ppVizPSF/Makefile
+  src/ppCoord/Makefile
+  src/ppVizPattern/Makefile
+])
+
+AC_OUTPUT
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/Makefile.am	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/Makefile.am	(revision 41113)
@@ -0,0 +1,3 @@
+SUBDIRS = ppVizPSF ppCoord ppVizPattern
+
+CLEANFILES = *.pyc *~ core core.*
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/Makefile.am	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/Makefile.am	(revision 41113)
@@ -0,0 +1,32 @@
+bin_PROGRAMS = ppCoord
+
+# Force recompilation of ppCoordVersion.c, since it gets the version information
+ppCoordVersion.c: ppCoordVersionDefinitions.h
+ppCoordVersionDefinitions.h: ppCoordVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl PPCOORD ppCoordVersionDefinitions.h.in ppCoordVersionDefinitions.h
+FORCE: ;
+
+BUILT_SOURCES = ppCoordVersionDefinitions.h
+
+
+ppCoord_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPCOORD_CFLAGS)
+ppCoord_LDFLAGS  = $(PSLIB_LIBS)   $(PSMODULE_LIBS)   $(PPCOORD_LIBS)
+
+ppCoord_SOURCES =		\
+	ppCoord.c		\
+	ppCoordArguments.c	\
+	ppCoordCamera.c	\
+	ppCoordData.c		\
+	ppCoordLoop.c		\
+	ppCoordVersion.c            
+
+noinst_HEADERS = \
+	ppCoord.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoord.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoord.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoord.c	(revision 41113)
@@ -0,0 +1,43 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppCoord.h"
+
+int main(int argc, char *argv[])
+{
+    ppCoordVersionPrint();
+
+    ppCoordData *data = ppCoordDataInit(&argc, argv);
+    if (!data) {
+        psErrorStackPrint(stderr, "Unable to initialise.");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppCoordArguments(data, argc, argv)) {
+        psErrorStackPrint(stderr, "Unable to parse arguments.");
+        psFree(data);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppCoordCamera(data)) {
+        psErrorStackPrint(stderr, "Unable to parse camera configuration.");
+        psFree(data);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppCoordLoop(data)) {
+        psErrorStackPrint(stderr, "Unable to process data.");
+        psFree(data);
+        return PS_EXIT_DATA_ERROR;
+    }
+
+    psFree(data);
+
+    return PS_EXIT_SUCCESS;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoord.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoord.h	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoord.h	(revision 41113)
@@ -0,0 +1,54 @@
+#ifndef PP_VIZPSF_H
+#define PP_VIZPSF_H
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+
+#define PPVIZPSF_RECIPE "PPVIZPSF"      // Recipe name
+
+// Data for processing
+typedef struct {
+    psString astromName;                // Filename with astrometry
+    psString rawName;                   // Filename with raw image (or NULL)
+    psString pixelsName;                // Filename with pixel coordinates
+    psString chipName;                  // Name of chip of interest
+    psString radecName;                 // Filename with sky coordinates
+    psString streaksName;               // Filename with streaks (sky coordinates)
+    psString clustersName;              // Filename with clusters (sky coordinates)
+    pmConfig *config;                   // Configuration
+    bool radians;                       // RA,Dec are in radians?
+    bool all;                           // Only all coordinates?
+    psString ds9name;                   // Name of ds9 region file
+    FILE *ds9;                          // ds9 output file handle
+    float ds9radius;                    // Radius of ds9 regions
+    psString ds9color;                  // Color of ds9 regions
+} ppCoordData;
+
+/// Initialise data for processing
+ppCoordData *ppCoordDataInit(int *argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse command-line arguments
+bool ppCoordArguments(ppCoordData *data, // Data for processing
+                        int argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse camera configurations
+bool ppCoordCamera(ppCoordData *data // Data for processing
+    );
+
+/// Loop over input data, processing
+bool ppCoordLoop(ppCoordData *data // Data for processing
+    );
+
+/// Add version information to header
+bool ppCoordVersionHeader(
+    psMetadata *header                  // Header to supplement
+    );
+
+/// Print version information to stdout
+void ppCoordVersionPrint(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordArguments.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordArguments.c	(revision 41113)
@@ -0,0 +1,102 @@
+/** @file ppCoordArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppCoord
+ *
+ *  @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 "ppCoord.h"
+
+/// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  ppCoordData *data   // Run-time data
+    )
+{
+    fprintf(stderr, "\nPan-STARRS coordinate transformation\n\n");
+    fprintf(stderr, "Usage: %s \n\n", program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(arguments);
+    psFree(data);
+
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+
+bool ppCoordArguments(ppCoordData *data, int argc, char *argv[])
+{
+    assert(data);
+    assert(data->config);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-astrom", 0, "Filename with astrometry", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-raw", 0, "Filename with raw data", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-pixels", 0, "Filename with pixel coordinates", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-chip", 0, "Chip for pixel coordinates", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-radec", 0, "Filename with RA,Dec (default decimal degrees)", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-streaks", 0, "Filename with streaks", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-clusters", 0, "Filename with clusters", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-radians", 0, "RA,Dec in radians?", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-all", 0, "Output all coordinates?", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-ds9", 0, "Output ds9 region file", NULL);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-ds9-radius", 0, "ds9 region radii", 5.0);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-ds9-color", 0, "ds9 region color", "red");
+    if (!psArgumentParse(arguments, &argc, argv) || argc != 1) {
+        usage(argv[0], arguments, data);
+    }
+
+    data->astromName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-astrom"));
+    data->rawName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-raw"));
+    data->pixelsName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-pixels"));
+    data->chipName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-chip"));
+    data->radecName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-radec"));
+    data->streaksName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-streaks"));
+    data->clustersName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-clusters"));
+    data->radians = psMetadataLookupBool(NULL, arguments, "-radians");
+    data->all = psMetadataLookupBool(NULL, arguments, "-all");
+//    psMetadataAddStr(data->config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Output root name", data->outRoot);
+
+    data->ds9name = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-ds9"));
+    if (data->ds9name) {
+        data->ds9 = fopen(data->ds9name, "w");
+        if (!data->ds9) {
+            psError(PS_ERR_IO, true, "Unable to open region file %s", data->ds9name);
+            return false;
+        }
+    }
+    data->ds9radius = psMetadataLookupF32(NULL, arguments, "-ds9-radius");
+    data->ds9color = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-ds9-color"));
+
+    psTrace("ppCoord", 1, "Done reading command-line arguments\n");
+    psFree(arguments);
+
+    if (!data->astromName) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "No astrometry file specified via -astrom");
+        return false;
+    }
+
+    if (!data->pixelsName && !data->radecName && !data->streaksName && !data->clustersName) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Neither -pixels nor -radec provided.");
+        return false;
+    }
+
+    return true;
+}
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordCamera.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordCamera.c	(revision 41113)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppCoord.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 ppCoordCamera(ppCoordData *data // Run-time data
+    )
+{
+    bool status;                        // Status of file definition
+
+    fileArguments("ASTROM", data->astromName, "Input astrometry", data->config);
+    pmFPAfile *astrom = pmFPAfileDefineFromArgs(&status, data->config, "PPCOORD.ASTROM", "ASTROM"); // File
+    if (!status || !astrom) {
+        psError(PS_ERR_IO, false, "Failed to build file from PPCOORD.ASTROM");
+        return false;
+    }
+
+    if (data->rawName) {
+        fileArguments("RAW", data->rawName, "Input raw image", data->config);
+        pmFPAfile *raw = pmFPAfileDefineFromArgs(&status, data->config, "PPCOORD.RAW", "RAW"); // File
+        if (!status || !raw) {
+            psError(PS_ERR_IO, false, "Failed to build file from PPCOORD.RAW");
+            return false;
+        }
+    }
+
+#if 0
+    // Now the camera has been determined, we can read the recipe
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, data->config->recipes, PPVIZPSF_RECIPE); // Recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPVIZPSF_RECIPE);
+        return false;
+    }
+#endif
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordData.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordData.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordData.c	(revision 41113)
@@ -0,0 +1,66 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppCoord.h"
+
+// Destructor
+static void coordDataFree(ppCoordData *data // Data to free
+    )
+{
+    psFree(data->astromName);
+    psFree(data->rawName);
+    psFree(data->pixelsName);
+    psFree(data->chipName);
+    psFree(data->radecName);
+    psFree(data->streaksName);
+    psFree(data->config);
+    psFree(data->ds9name);
+    if (data->ds9) {
+        fclose(data->ds9);
+    }
+    psFree(data->ds9color);
+    return;
+}
+
+
+ppCoordData *ppCoordDataAlloc(void)
+{
+    ppCoordData *data = psAlloc(sizeof(ppCoordData)); // Processing data, to return
+    psMemSetDeallocator(data, (psFreeFunc)coordDataFree);
+
+    data->astromName = NULL;
+    data->rawName = NULL;
+    data->pixelsName = NULL;
+    data->chipName = NULL;
+    data->radecName = NULL;
+    data->streaksName = NULL;
+    data->config = NULL;
+    data->radians = false;
+    data->all = false;
+    data->ds9name = NULL;
+    data->ds9 = NULL;
+    data->ds9radius = NAN;
+    data->ds9color = NULL;
+
+    return data;
+}
+
+
+ppCoordData *ppCoordDataInit(int *argc, char **argv)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    ppCoordData *data = ppCoordDataAlloc(); // Processing data, to return
+    data->config = pmConfigRead(argc, argv, NULL);
+    if (!data->config) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read configuration.");
+        return NULL;
+    }
+    return data;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordLoop.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordLoop.c	(revision 41113)
@@ -0,0 +1,614 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppCoord.h"
+
+// Convert sky coordinates to chip coordinates
+static void coordSky2Chip(float *x, float *y, // Chip coordinates (output)
+                          double ra, double dec, // Sky coordinates
+                          bool radians,          // Coordinates are in radians?
+                          const pmFPA *astromFPA,  // Astrometry FPA
+                          const pmChip *astromChip // Astrometry chip
+    )
+{
+    psPlane pix;                        // Pixel coordinates on chip
+    psPlane fp;                         // Focal plane coordinates
+    psPlane tp;                         // Tangent plane coordinates
+    psSphere sky;                       // Sky coordinates
+
+    sky.r = ra;
+    sky.d = dec;
+
+    if (!radians) {
+        sky.r *= M_PI / 180.0;
+        sky.d *= M_PI / 180.0;
+    }
+
+    psProject(&tp, &sky, astromFPA->toSky);
+    psTrace("ppCoord",2,"sky2pix tr1: %f %f\n",tp.x,tp.y);
+    psPlaneTransformApply(&fp, astromFPA->fromTPA, &tp);
+    psTrace("ppCoord",2,"sky2pix tr2: %f %f\n",fp.x,fp.y);
+    psPlaneTransformApply(&pix, astromChip->fromFPA, &fp);
+    psTrace("ppCoord",2,"sky2pix tr3: %f %f\n",pix.x,pix.y);
+
+    *x = pix.x;
+    *y = pix.y;
+
+    return;
+}
+
+// Convert chip coordinates to cell coordinates
+static void coordChip2Cell(psString *cellName,       // Cell name (output)
+                           float *xCell, float *yCell, // Pixel coordinates (output)
+                           float xChip, float yChip, // Sky coordinates
+                           const psArray *cellNames, // Names of cells
+                           const psArray *cellBounds,                                // Bounds of cells
+                           const psVector *cellX0, const psVector *cellY0, // Cell offsets
+                           const psVector *cellParityX, const psVector *cellParityY, // Cell parities
+                           const psVector *cellBinX, const psVector *cellBinY        // Cell binnings
+
+
+    )
+{
+    int numCells = cellNames->n; // Number of cells
+    for (int i = 0; i < numCells && !*cellName; i++) {
+        psRegion *region = cellBounds->data[i]; // Bounds of cell
+        if (xChip >= region->x0 && xChip < region->x1 && yChip >= region->y0 && yChip < region->y1) {
+            *cellName = psStringCopy(cellNames->data[i]);
+            // Transform chip coordinates to cell coordinates
+            *xCell = (xChip - cellX0->data.S32[i]) / (float)(cellParityX->data.S32[i] * cellBinX->data.S32[i]);
+            *yCell = (yChip - cellY0->data.S32[i]) / (float)(cellParityY->data.S32[i] * cellBinY->data.S32[i]);
+        }
+    }
+
+    return;
+}
+
+bool ppCoordLoop(ppCoordData *data // Run-time data
+    )
+{
+    pmConfig *config = data->config;                                        // Configuration data
+    pmFPAfile *astromFile = pmFPAfileSelectSingle(config->files, "PPCOORD.ASTROM", 0); // File with astrometry
+
+    if (astromFile->fpa->chips->n > 0 && data->pixelsName && !data->chipName) {
+        psWarning("Pixel coordinates supplied, but no chip name provided.");
+    }
+
+    pmFPAfile *rawFile = data->rawName ? pmFPAfileSelectSingle(config->files, "PPCOORD.RAW", 0) :
+        NULL; // File with raw image
+
+    psArray *pixels = NULL, *radec = NULL, *streaks = NULL, *clusters = NULL; // Array of coordinate vectors
+    psArray *radecOut = NULL, *streaksOut = NULL, *clustersOut = NULL;        // Output for sky coordinates
+    if (data->pixelsName) {
+        pixels = psVectorsReadFromFile(data->pixelsName, "%f %f");
+        if (!pixels || pixels->n != 2) {
+            psError(psErrorCodeLast(), false, "Unable to read pixel coordinates");
+            return false;
+        }
+    }
+    if (data->radecName) {
+        radec = psVectorsReadFromFile(data->radecName, "%lf %lf");
+        if (!radec || radec->n != 2) {
+            psError(psErrorCodeLast(), false, "Unable to read sky coordinates");
+            return false;
+        }
+        psVector *ra = radec->data[0];  // RA coordinates
+        long num = ra->n;               // Number of coordinates
+        radecOut = psArrayAlloc(4);
+        radecOut->data[0] = psArrayAlloc(num);
+        radecOut->data[1] = psVectorAlloc(num, PS_TYPE_F32);
+        radecOut->data[2] = psVectorAlloc(num, PS_TYPE_F32);
+        radecOut->data[3] = psArrayAlloc(num);
+        psVectorInit(radecOut->data[1], NAN);
+        psVectorInit(radecOut->data[2], NAN);
+    }
+
+    if (data->streaksName) {
+        FILE *streaksFile = fopen(data->streaksName, "r"); // File handle for streaks
+        if (!streaksFile) {
+            psError(PS_ERR_IO, true, "Unable to open streaks file %s", data->streaksName);
+            return false;
+        }
+        int numStreaks = 0;             // Number of streaks
+        if (fscanf(streaksFile, "%d", &numStreaks) != 1) {
+            psError(PS_ERR_IO, true, "Unable to read number of streaks from %s", data->streaksName);
+            return false;
+        }
+
+        streaks = psArrayAlloc(4);
+        psVector *ra1 = streaks->data[0] = psVectorAlloc(numStreaks, PS_TYPE_F64);
+        psVector *dec1 = streaks->data[1] = psVectorAlloc(numStreaks, PS_TYPE_F64);
+        psVector *ra2 = streaks->data[2] = psVectorAlloc(numStreaks, PS_TYPE_F64);
+        psVector *dec2 = streaks->data[3] = psVectorAlloc(numStreaks, PS_TYPE_F64);
+
+        for (int i = 0; i < numStreaks; i++) {
+            if (fscanf(streaksFile, "%lf %lf %lf %lf %*d",
+                       &ra1->data.F64[i], &dec1->data.F64[i], &ra2->data.F64[i], &dec2->data.F64[i]) != 4) {
+                psError(PS_ERR_IO, true, "Unable to read streak %d of %d from %s",
+                        i, numStreaks, data->streaksName);
+                return false;
+            }
+        }
+        streaksOut = psArrayAlloc(8);
+        streaksOut->data[0] = psArrayAlloc(numStreaks);
+        streaksOut->data[1] = psArrayAlloc(numStreaks);
+        streaksOut->data[2] = psVectorAlloc(numStreaks, PS_TYPE_F32);
+        streaksOut->data[3] = psVectorAlloc(numStreaks, PS_TYPE_F32);
+        streaksOut->data[4] = psArrayAlloc(numStreaks);
+        streaksOut->data[5] = psArrayAlloc(numStreaks);
+        streaksOut->data[6] = psVectorAlloc(numStreaks, PS_TYPE_F32);
+        streaksOut->data[7] = psVectorAlloc(numStreaks, PS_TYPE_F32);
+        psVectorInit(streaksOut->data[2], NAN);
+        psVectorInit(streaksOut->data[3], NAN);
+        psVectorInit(streaksOut->data[6], NAN);
+        psVectorInit(streaksOut->data[7], NAN);
+    }
+
+    if (data->clustersName) {
+        psString file = psSlurpFilename(data->clustersName); // Contents of clusters file
+        if (!file) {
+            psError(psErrorCodeLast(), false, "Unable to read clusters file %s", data->clustersName);
+            return false;
+        }
+        psArray *lines = psStringSplitArray(file, "\n", false); // Lines of clusters
+        psFree(file);
+
+        int num = lines->n - 1;         // Number of clusters
+        clusters = psArrayAlloc(2);
+        psVector *ra = clusters->data[0] = psVectorAlloc(num, PS_TYPE_F64);
+        psVector *dec = clusters->data[1] = psVectorAlloc(num, PS_TYPE_F64);
+
+        // Skip the first line
+        for (int i = 1; i < lines->n; i++) {
+            const char *line = lines->data[i]; // Line of interest
+            if (sscanf(line, "%lf %lf", &ra->data.F64[i-1], &dec->data.F64[i-1]) != 2) {
+                psError(PS_ERR_IO, true, "Unable to read line %d of %s", i, data->clustersName);
+                return false;
+            }
+        }
+        psFree(lines);
+
+        clustersOut = psArrayAlloc(4);
+        clustersOut->data[0] = psArrayAlloc(num);
+        clustersOut->data[1] = psVectorAlloc(num, PS_TYPE_F32);
+        clustersOut->data[2] = psVectorAlloc(num, PS_TYPE_F32);
+        clustersOut->data[3] = psArrayAlloc(num);
+        psVectorInit(clustersOut->data[1], NAN);
+        psVectorInit(clustersOut->data[2], NAN);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        return NULL;
+    }
+
+    // find the FPA phu
+    bool bilevelAstrometry = false;
+    pmHDU *phu = pmFPAviewThisPHU(view, astromFile->fpa);
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+        }
+    }
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(astromFile->fpa, phu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for input FPA.");
+            psFree(view);
+            return false;
+        }
+    }
+
+    pmChip *chip;                       // Chip from FPA
+    while ((chip = pmFPAviewNextChip(view, astromFile->fpa, 1))) {
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (rawFile) {
+            pmChip *rawChip = pmFPAviewThisChip(view, rawFile->fpa);                   // Chip with raw data
+            if (!rawChip || !rawChip->file_exists) {
+                continue;
+            }
+        }
+        const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+        if (data->chipName && strcmp(chipName, data->chipName) != 0) {
+            continue;
+        }
+        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);
+        }
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, astromFile->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(astromFile->fpa, chip, hdu->header, 1.0)) {
+                psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for input FPA.");
+                psFree(view);
+                return false;
+            }
+        }
+
+        if (pixels) {
+            psVector *x = pixels->data[0], *y = pixels->data[1]; // Pixel coordinates
+            long num = x->n;                                     // Number of coordinates
+
+            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 (long i = 0; i < num; i++) {
+                pix->x = x->data.F32[i];
+                pix->y = y->data.F32[i];
+
+                psPlaneTransformApply(fp, chip->toFPA, pix);
+		psTrace("ppCoord",2,"pix2sky tr1: %f %f\n",fp->x,fp->y);
+                psPlaneTransformApply(tp, astromFile->fpa->toTPA, fp);
+		psTrace("ppCoord",2,"pix2sky tr2: %f %f\n",tp->x,tp->y);
+                psDeproject(sky, tp, astromFile->fpa->toSky);
+		psTrace("ppCoord",2,"pix2sky tr3: %f %f\n",sky->r,sky->d);
+                if (!data->radians) {
+		  sky->r *= 180.0 / M_PI;
+		  sky->d *= 180.0 / M_PI;
+                }
+
+                fprintf(stdout, "%s %.3f %.3f --> %.10lf %.10lf\n", chipName, pix->x, pix->y, sky->r, sky->d);
+            }
+            psFree(pix);
+            psFree(fp);
+            psFree(tp);
+            psFree(sky);
+        }
+
+
+        pmChip *rawChip = rawFile ? pmFPAviewThisChip(view, rawFile->fpa) : NULL; // Chip with raw
+        psArray *cellBounds = NULL;                                               // Bounds of each cell
+        psArray *cellNames = NULL;                                                // Names of each cell
+        psVector *cellParityX = NULL, *cellParityY = NULL;                        // Parity for each cell
+        psVector *cellX0 = NULL, *cellY0 = NULL;                                  // Offset for each cell
+        psVector *cellBinX = NULL, *cellBinY = NULL;                              // Binning for each cell
+        if (rawChip) {
+            if (!rawChip->data_exists) {
+                // Not interested in this chip
+                continue;
+            }
+            int numCells = rawChip->cells->n; // Number of cells
+
+            cellBounds = psArrayAlloc(numCells);
+            cellNames = psArrayAlloc(numCells);
+            cellParityX = psVectorAlloc(numCells, PS_TYPE_S32);
+            cellParityY = psVectorAlloc(numCells, PS_TYPE_S32);
+            cellX0 = psVectorAlloc(numCells, PS_TYPE_S32);
+            cellY0 = psVectorAlloc(numCells, PS_TYPE_S32);
+            cellBinX = psVectorAlloc(numCells, PS_TYPE_S32);
+            cellBinY = psVectorAlloc(numCells, PS_TYPE_S32);
+
+            pmCell *rawCell;        // Cell with raw data
+            while ((rawCell = pmFPAviewNextCell(view, rawFile->fpa, 1))) {
+                if (!rawCell->data_exists) {
+                    continue;
+                }
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    psError(psErrorCodeLast(), false, "Error loading data from files.");
+                    return false;
+                }
+                psRegion *region = pmCellExtent(rawCell); // Bounds of cell
+                if (!region) {
+                    psError(psErrorCodeLast(), false, "Unable to determine extent of cell.");
+                    return false;
+                }
+                cellBounds->data[view->cell] = region;
+                cellNames->data[view->cell] = psMemIncrRefCounter(psMetadataLookupStr(NULL, rawCell->concepts, "CELL.NAME"));
+                cellParityX->data.S32[view->cell] = psMetadataLookupS32(NULL, rawCell->concepts, "CELL.XPARITY");
+                cellParityY->data.S32[view->cell] = psMetadataLookupS32(NULL, rawCell->concepts, "CELL.YPARITY");
+                cellX0->data.S32[view->cell] = psMetadataLookupS32(NULL, rawCell->concepts, "CELL.X0");
+                cellY0->data.S32[view->cell] = psMetadataLookupS32(NULL, rawCell->concepts, "CELL.Y0");
+                cellBinX->data.S32[view->cell] = psMetadataLookupS32(NULL, rawCell->concepts, "CELL.XBIN");
+                cellBinY->data.S32[view->cell] = psMetadataLookupS32(NULL, rawCell->concepts, "CELL.YBIN");
+                if (cellParityX->data.S32[view->cell] == 0 || cellParityY->data.S32[view->cell] == 0 ||
+                    cellBinX->data.S32[view->cell] == 0 || cellBinY->data.S32[view->cell] == 0) {
+                    psError(PM_ERR_CONCEPTS, true, "Concepts aren't set: %d %d %d %d %d %d\n",
+                            cellParityX->data.S32[view->cell], cellParityY->data.S32[view->cell],
+                            cellX0->data.S32[view->cell], cellY0->data.S32[view->cell],
+                            cellBinX->data.S32[view->cell], cellBinY->data.S32[view->cell]);
+                    return false;
+                }
+
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                    psError(psErrorCodeLast(), false, "Error freeing data from files.");
+                    return false;
+                }
+            }
+        }
+
+        if (radec) {
+            psVector *ra = radec->data[0], *dec = radec->data[1]; // Pixel coordinates
+            long num = ra->n;                                     // Number of coordinates
+
+            int numCols = psMetadataLookupS32(NULL, hdu->header, "IMNAXIS1"); // Number of columns
+            int numRows = psMetadataLookupS32(NULL, hdu->header, "IMNAXIS2"); // Number of rows
+            if (numCols <= 0 || numRows <= 0) {
+                psError(psErrorCodeLast(), false, "Unable to read size of chip.");
+                return false;
+            }
+
+            psArray *chipPix = radecOut->data[0]; // Chip for pixels
+            psVector *xPix = radecOut->data[1];   // x coordinate for pixels
+            psVector *yPix = radecOut->data[2];   // y coordinate for pixels
+            psArray *cellPix = radecOut->data[3]; // Cell for pixels
+
+            for (long i = 0; i < num; i++) {
+                float x, y;             // Pixel coordinates
+                coordSky2Chip(&x, &y, ra->data.F64[i], dec->data.F64[i],
+                                data->radians, astromFile->fpa, chip);
+                if ((x < 0 || x > numCols || y < 0 || y > numRows)) {
+                    // Not on this chip
+                    continue;
+                }
+
+                if (rawChip) {
+                    psString cellName = NULL; // Name of cell
+                    coordChip2Cell(&cellName, &x, &y, x, y, cellNames, cellBounds,
+                                   cellX0, cellY0, cellParityX, cellParityY, cellBinX, cellBinY);
+                    cellPix->data[i] = cellName;
+                }
+
+                chipPix->data[i] = psStringCopy(chipName);
+                xPix->data.F32[i] = x;
+                yPix->data.F32[i] = y;
+            }
+        }
+
+        if (streaks) {
+            psArray *chipPix1 = streaksOut->data[0]; // Chip for point 1
+            psArray *cellPix1 = streaksOut->data[1]; // Cell for point 1
+            psVector *xPix1 = streaksOut->data[2]; // x coordinate for point 1
+            psVector *yPix1 = streaksOut->data[3]; // y coordinate for point 1
+
+            psArray *chipPix2 = streaksOut->data[4]; // Chip for point 2
+            psArray *cellPix2 = streaksOut->data[5]; // Cell for point 2
+            psVector *xPix2 = streaksOut->data[6]; // x coordinate for point 2
+            psVector *yPix2 = streaksOut->data[7]; // y coordinate for point 2
+
+            psVector *ra1 = streaks->data[0]; // RA coordinate for point 1
+            psVector *dec1 = streaks->data[1]; // Dec coordinate for point 1
+            psVector *ra2 = streaks->data[2]; // RA coordinate for point 2
+            psVector *dec2 = streaks->data[3]; // Dec coordinate for point 2
+
+            int numCols = psMetadataLookupS32(NULL, hdu->header, "IMNAXIS1"); // Number of columns
+            int numRows = psMetadataLookupS32(NULL, hdu->header, "IMNAXIS2"); // Number of rows
+            if (numCols <= 0 || numRows <= 0) {
+                psError(psErrorCodeLast(), false, "Unable to read size of chip.");
+                return false;
+            }
+
+            for (long i = 0; i < xPix1->n; i++) {
+                float x1, y1;   // Coordinates of point 1
+                coordSky2Chip(&x1, &y1, ra1->data.F64[i], dec1->data.F64[i],
+                              true, astromFile->fpa, chip);
+                if ((x1 >= 0 && x1 < numCols && y1 >= 0 && y1 < numRows)) {
+                    if (rawChip) {
+                        psString cellName = NULL; // Name of cell
+                        coordChip2Cell(&cellName, &x1, &y1, x1, y1, cellNames, cellBounds,
+                                       cellX0, cellY0, cellParityX, cellParityY, cellBinX, cellBinY);
+                        cellPix1->data[i] = cellName;
+                    }
+                    chipPix1->data[i] = psStringCopy(chipName);
+                    xPix1->data.F32[i] = x1;
+                    yPix1->data.F32[i] = y1;
+                }
+
+                float x2, y2;   // Coordinates of point 2
+                coordSky2Chip(&x2, &y2, ra2->data.F64[i], dec2->data.F64[i],
+                              true, astromFile->fpa, chip);
+                if ((x2 >= 0 && x2 < numCols && y2 >= 0 && y2 < numRows)) {
+                    if (rawChip) {
+                        psString cellName = NULL; // Name of cell
+                        coordChip2Cell(&cellName, &x2, &y2, x2, y2, cellNames, cellBounds,
+                                       cellX0, cellY0, cellParityX, cellParityY, cellBinX, cellBinY);
+                        cellPix2->data[i] = cellName;
+                    }
+                    chipPix2->data[i] = psStringCopy(chipName);
+                    xPix2->data.F32[i] = x2;
+                    yPix2->data.F32[i] = y2;
+                }
+            }
+        }
+
+        if (clusters) {
+            psVector *ra = clusters->data[0], *dec = clusters->data[1]; // Pixel coordinates
+            long num = ra->n;                                     // Number of coordinates
+
+            int numCols = psMetadataLookupS32(NULL, hdu->header, "IMNAXIS1"); // Number of columns
+            int numRows = psMetadataLookupS32(NULL, hdu->header, "IMNAXIS2"); // Number of rows
+            if (numCols <= 0 || numRows <= 0) {
+                psError(psErrorCodeLast(), false, "Unable to read size of chip.");
+                return false;
+            }
+
+            psArray *chipPix = clustersOut->data[0]; // Chip for pixels
+            psVector *xPix = clustersOut->data[1];   // x coordinate for pixels
+            psVector *yPix = clustersOut->data[2];   // y coordinate for pixels
+            psArray *cellPix = clustersOut->data[3]; // Cell for pixels
+
+            for (long i = 0; i < num; i++) {
+                float x, y;             // Pixel coordinates
+                coordSky2Chip(&x, &y, ra->data.F64[i], dec->data.F64[i],
+                                false, astromFile->fpa, chip);
+                if ((x < 0 || x > numCols || y < 0 || y > numRows)) {
+                    // Not on this chip
+                    continue;
+                }
+
+                if (rawChip) {
+                    psString cellName = NULL; // Name of cell
+                    coordChip2Cell(&cellName, &x, &y, x, y, cellNames, cellBounds,
+                                   cellX0, cellY0, cellParityX, cellParityY, cellBinX, cellBinY);
+                    cellPix->data[i] = cellName;
+                }
+
+                chipPix->data[i] = psStringCopy(chipName);
+                xPix->data.F32[i] = x;
+                yPix->data.F32[i] = y;
+            }
+        }
+
+        psFree(cellNames);
+        psFree(cellBounds);
+        psFree(cellParityX);
+        psFree(cellParityY);
+        psFree(cellBinX);
+        psFree(cellBinY);
+        psFree(cellX0);
+        psFree(cellY0);
+
+        // 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;
+    }
+
+    if (radecOut) {
+        psArray *chipPix = radecOut->data[0]; // Chip for pixels
+        psVector *xPix = radecOut->data[1];   // x coordinate for pixels
+        psVector *yPix = radecOut->data[2];   // y coordinate for pixels
+        psArray *cellPix = radecOut->data[3]; // Cell for pixels
+        psVector *ra = radec->data[0];        // RA coordinate
+        psVector *dec = radec->data[1];       // Dec coordinate
+
+        for (long i = 0; i < chipPix->n; i++) {
+            const char *chipName = chipPix->data[i]; // Name of chip
+            const char *cellName = cellPix->data[i]; // Name of cell, or NULL
+            if (!data->all && (!isfinite(xPix->data.F32[i]) || !isfinite(yPix->data.F32[i]) ||
+                               !chipName || (rawFile && !cellName))) {
+                continue;
+            }
+            if (!rawFile && data->ds9) {
+                // Region file is only appropriate if we're not mapping all the way back to cell coordinates
+                fprintf(data->ds9, "image;circle(%f,%f,%f) # color=%s\n",
+                        xPix->data.F32[i], yPix->data.F32[i], data->ds9radius, data->ds9color);
+            } else {
+                fprintf(stdout, "%.10lf %.10lf --> %.3f %.3f %s%s%s\n",
+                        ra->data.F64[i], dec->data.F64[i], xPix->data.F32[i], yPix->data.F32[i],
+                        chipName ? chipName : "UNKNOWN",
+                        rawFile ? " " : "",
+                        rawFile && cellName ? cellName : (rawFile ? "UNKNOWN" : ""));
+            }
+        }
+    }
+
+    if (streaksOut) {
+        psArray *chipPix1 = streaksOut->data[0]; // Chip for point 1
+        psArray *cellPix1 = streaksOut->data[1]; // Cell for point 1
+        psVector *xPix1 = streaksOut->data[2]; // x coordinate for point 1
+        psVector *yPix1 = streaksOut->data[3]; // y coordinate for point 1
+        psArray *chipPix2 = streaksOut->data[4]; // Chip for point 2
+        psArray *cellPix2 = streaksOut->data[5]; // Cell for point 2
+        psVector *xPix2 = streaksOut->data[6]; // x coordinate for point 2
+        psVector *yPix2 = streaksOut->data[7]; // y coordinate for point 2
+
+        psVector *ra1 = streaks->data[0]; // RA coordinate for point 1
+        psVector *dec1 = streaks->data[1]; // Dec coordinate for point 1
+        psVector *ra2 = streaks->data[2]; // RA coordinate for point 2
+        psVector *dec2 = streaks->data[3]; // Dec coordinate for point 2
+
+        for (long i = 0; i < xPix1->n; i++) {
+            const char *chipName1 = chipPix1->data[i]; // Name of chip for point 1
+            const char *cellName1 = cellPix1->data[i]; // Name of cell for point 1
+            const char *chipName2 = chipPix2->data[i]; // Name of chip for point 2
+            const char *cellName2 = cellPix2->data[i]; // Name of cell for point 2
+            if (!data->all &&
+                (!isfinite(xPix1->data.F32[i]) || !isfinite(yPix1->data.F32[i]) ||
+                 !chipName1 || (rawFile && !cellName1) ||
+                 !isfinite(xPix2->data.F32[i]) || !isfinite(yPix2->data.F32[i]) ||
+                 !chipName2 || (rawFile && !cellName2))) {
+                continue;
+            }
+            if (!rawFile && data->ds9) {
+                // Region file is only appropriate if we're not mapping all the way back to cell coordinates
+                fprintf(data->ds9, "image;line(%f,%f,%f,%f) # line= 0 0 color=%s\n",
+                        xPix1->data.F32[i], yPix1->data.F32[i], xPix2->data.F32[i], yPix2->data.F32[i],
+                        data->ds9color);
+            } else {
+                fprintf(stdout, "Streak %.10lf %.10lf %.10lf %.10lf --> %.3f %.3f %s%s%s %.3f %.3f %s%s%s\n",
+                        ra1->data.F64[i], dec1->data.F64[i],
+                        ra2->data.F64[i], dec2->data.F64[i],
+                        xPix1->data.F32[i], yPix1->data.F32[i],
+                        chipName1 ? chipName1 : "UNKNOWN",
+                        rawFile ? " " : "",
+                        rawFile && cellName1 ? cellName1 : (rawFile ? "UNKNOWN" : ""),
+                        xPix2->data.F32[i], yPix2->data.F32[i],
+                        chipName2 ? chipName2 : "UNKNOWN",
+                        rawFile ? " " : "",
+                        rawFile && cellName2 ? cellName2 : (rawFile ? "UNKNOWN" : "")
+                    );
+            }
+        }
+    }
+
+    if (clustersOut) {
+        psArray *chipPix = clustersOut->data[0]; // Chip for pixels
+        psVector *xPix = clustersOut->data[1];   // x coordinate for pixels
+        psVector *yPix = clustersOut->data[2];   // y coordinate for pixels
+        psArray *cellPix = clustersOut->data[3]; // Cell for pixels
+        psVector *ra = clusters->data[0];        // RA coordinate
+        psVector *dec = clusters->data[1];       // Dec coordinate
+
+        for (long i = 0; i < chipPix->n; i++) {
+            const char *chipName = chipPix->data[i]; // Name of chip
+            const char *cellName = cellPix->data[i]; // Name of cell, or NULL
+            if (!data->all && (!isfinite(xPix->data.F32[i]) || !isfinite(yPix->data.F32[i]) ||
+                               !chipName || (rawFile && !cellName))) {
+                continue;
+            }
+            if (!rawFile && data->ds9) {
+                // Region file is only appropriate if we're not mapping all the way back to cell coordinates
+                fprintf(data->ds9, "image;circle(%f,%f,%f) # color=%s\n",
+                        xPix->data.F32[i], yPix->data.F32[i], data->ds9radius, data->ds9color);
+            } else {
+                fprintf(stdout, "Cluster %.10lf %.10lf --> %.3f %.3f %s%s%s\n",
+                        ra->data.F64[i], dec->data.F64[i], xPix->data.F32[i], yPix->data.F32[i],
+                        chipName ? chipName : "UNKNOWN",
+                        rawFile ? " " : "",
+                        rawFile && cellName ? cellName : (rawFile ? "UNKNOWN" : ""));
+            }
+        }
+    }
+
+    psFree(pixels);
+    psFree(radec);
+    psFree(radecOut);
+    psFree(streaks);
+    psFree(streaksOut);
+    psFree(clusters);
+    psFree(clustersOut);
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordVersion.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordVersion.c	(revision 41113)
@@ -0,0 +1,118 @@
+/** @file ppCoordVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppCoord
+ *
+ *  @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 "ppCoord.h"
+#include "ppCoordVersionDefinitions.h"
+
+#ifndef PPCOORD_VERSION
+#error "PPCOORD_VERSION is not set"
+#endif
+#ifndef PPCOORD_BRANCH
+#error "PPCOORD_BRANCH is not set"
+#endif
+#ifndef PPCOORD_SOURCE
+#error "PPCOORD_SOURCE is not set"
+#endif
+
+psString ppCoordVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PPCOORD_BRANCH, PPCOORD_VERSION);
+    return value;
+}
+
+psString ppCoordSource(void)
+{
+    return psStringCopy(PPCOORD_SOURCE);
+}
+
+psString ppCoordVersionLong(void)
+{
+    psString version = ppCoordVersion();  // Version, to return
+    psString source = ppCoordSource();    // Source
+
+    psStringPrepend(&version, "ppCoord ");
+    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 ppCoordVersionHeader(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, "ppCoord 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 = ppCoordVersion(); // Software version
+    psString source  = ppCoordSource();  // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD_V", PS_META_REPLACE, NULL, PPCOORD_VERSION);
+    
+    psStringPrepend(&version, "ppCoord version: ");
+    psStringPrepend(&version, "ppCoord 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 ppCoordVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("ppCoord", PS_LOG_INFO, "ppCoord at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppCoord = ppCoordVersionLong(); // ppCoord version
+
+    psLogMsg("ppCoord", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("ppCoord", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("ppCoord", PS_LOG_INFO, "%s", ppCoord);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppCoord);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordVersionDefinitions.h.in	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppCoord/ppCoordVersionDefinitions.h.in	(revision 41113)
@@ -0,0 +1,8 @@
+#ifndef PPCOORD_VERSION_DEFINITIONS_H
+#define PPCOORD_VERSION_DEFINITIONS_H
+
+#define PPCOORD_VERSION @PPCOORD_VERSION@ // SVN version
+#define PPCOORD_BRANCH  @PPCOORD_BRANCH@  // SVN branch
+#define PPCOORD_SOURCE  @PPCOORD_SOURCE@  // SVN source
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/Makefile.am	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/Makefile.am	(revision 41113)
@@ -0,0 +1,32 @@
+bin_PROGRAMS = ppVizPSF
+
+# Force recompilation of ppVizPSFVersion.c, since it gets the version information
+ppVizPSFVersion.c: ppVizPSFVersionDefinitions.h
+ppVizPSFVersionDefinitions.h: ppVizPSFVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl PPVIZPSF ppVizPSFVersionDefinitions.h.in ppVizPSFVersionDefinitions.h
+FORCE: ;
+
+BUILT_SOURCES = ppVizPSFVersionDefinitions.h
+
+
+ppVizPSF_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPVIZPSF_CFLAGS)
+ppVizPSF_LDFLAGS  = $(PSLIB_LIBS)   $(PSMODULE_LIBS)   $(PPVIZPSF_LIBS)
+
+ppVizPSF_SOURCES =		\
+	ppVizPSF.c		\
+	ppVizPSFArguments.c	\
+	ppVizPSFCamera.c	\
+	ppVizPSFData.c		\
+	ppVizPSFLoop.c		\
+	ppVizPSFVersion.c            
+
+noinst_HEADERS = \
+	ppVizPSF.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSF.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSF.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSF.c	(revision 41113)
@@ -0,0 +1,43 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPSF.h"
+
+int main(int argc, char *argv[])
+{
+    ppVizPSFVersionPrint();
+
+    ppVizPSFData *data = ppVizPSFDataInit(&argc, argv);
+    if (!data) {
+        psErrorStackPrint(stderr, "Unable to initialise.");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppVizPSFArguments(data, argc, argv)) {
+        psErrorStackPrint(stderr, "Unable to parse arguments.");
+        psFree(data);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppVizPSFCamera(data)) {
+        psErrorStackPrint(stderr, "Unable to parse camera configuration.");
+        psFree(data);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppVizPSFLoop(data)) {
+        psErrorStackPrint(stderr, "Unable to process data.");
+        psFree(data);
+        return PS_EXIT_DATA_ERROR;
+    }
+
+    psFree(data);
+
+    return PS_EXIT_SUCCESS;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSF.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSF.h	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSF.h	(revision 41113)
@@ -0,0 +1,48 @@
+#ifndef PP_VIZPSF_H
+#define PP_VIZPSF_H
+
+#include <pslib.h>
+#include <psmodules.h>
+
+#define PPVIZPSF_RECIPE "PPVIZPSF"      // Recipe name
+
+// Data for processing
+typedef struct {
+    psString psfName;                   // Filename with PSF
+    psString sourcesName;               // Filename with sources
+    int fakeNum;                        // Number of fake sources
+    float fakeMag;                      // Magnitude of fake sources
+    psString outRoot;                   // Output root name
+    float minFlux;                      // Minimum flux for sources
+    int size;                           // Size of PSF image
+    float x, y;                         // Position of fake source
+    psArray *input;                     // Input positions and magnitudes
+    pmConfig *config;                   // Configuration
+} ppVizPSFData;
+
+/// Initialise data for processing
+ppVizPSFData *ppVizPSFDataInit(int *argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse command-line arguments
+bool ppVizPSFArguments(ppVizPSFData *data, // Data for processing
+                        int argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse camera configurations
+bool ppVizPSFCamera(ppVizPSFData *data // Data for processing
+    );
+
+/// Loop over input data, processing
+bool ppVizPSFLoop(ppVizPSFData *data // Data for processing
+    );
+
+/// Add version information to header
+bool ppVizPSFVersionHeader(
+    psMetadata *header                  // Header to supplement
+    );
+
+/// Print version information to stdout
+void ppVizPSFVersionPrint(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFArguments.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFArguments.c	(revision 41113)
@@ -0,0 +1,89 @@
+/** @file ppVizPSFArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppVizPSF
+ *
+ *  @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 "ppVizPSF.h"
+
+/// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  ppVizPSFData *data   // Run-time data
+    )
+{
+    fprintf(stderr, "\nPan-STARRS PSF visualisation\n\n");
+    fprintf(stderr,
+            "Usage: %s -psf PSF.psf -sources SOURCES.cmf OUTPUT_ROOT\n\n",
+            program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(arguments);
+    psFree(data);
+
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+
+bool ppVizPSFArguments(ppVizPSFData *data, int argc, char *argv[])
+{
+    assert(data);
+    assert(data->config);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-psf", 0, "Filename of PSF", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-sources", 0, "Filename of CMF sources", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-input", 0, "Filename with x,y,mag", NULL);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-fake-num", 0, "Number of fake sources", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-fake-mag", 0, "Magnitude of fake sources", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-x", 0, "x position of fake source [0..1]", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-y", 0, "y position of fake source [0..1]", NAN);
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 2) {
+        usage(argv[0], arguments, data);
+    }
+
+    bool mdok;                          // Status of MD lookup
+    data->psfName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-psf"));
+    data->sourcesName = psMemIncrRefCounter(psMetadataLookupStr(&mdok, arguments, "-sources"));
+    data->fakeNum = psMetadataLookupS32(NULL, arguments, "-fake-num");
+    data->fakeMag = psMetadataLookupF32(NULL, arguments, "-fake-mag");
+    data->outRoot = psStringCopy(argv[1]);
+    data->x = psMetadataLookupF32(NULL, arguments, "-x");
+    data->y = psMetadataLookupF32(NULL, arguments, "-y");
+    psMetadataAddStr(data->config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Output root name", data->outRoot);
+
+    const char *inputName = psMetadataLookupStr(NULL, arguments, "-input"); // Name of input file
+    if (inputName) {
+        data->input = psVectorsReadFromFile(inputName, "%f %f %f");
+        if (!data->input) {
+            psError(psErrorCodeLast(), false, "Unable to read input file %s", inputName);
+            return false;
+        }
+    }
+
+    psTrace("ppVizPSF", 1, "Done reading command-line arguments\n");
+    psFree(arguments);
+
+    PS_ASSERT_STRING_NON_EMPTY(data->psfName, false);
+    PS_ASSERT_STRING_NON_EMPTY(data->outRoot, false);
+
+    return true;
+}
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFCamera.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFCamera.c	(revision 41113)
@@ -0,0 +1,82 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPSF.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 ppVizPSFCamera(ppVizPSFData *data // Run-time data
+    )
+{
+    bool status;                        // Status of file definition
+
+    fileArguments("PSF", data->psfName, "Input PSF", data->config);
+    pmFPAfile *psf = pmFPAfileDefineFromArgs(&status, data->config, "PSPHOT.PSF.LOAD", "PSF"); // File
+    if (!status || !psf) {
+        psError(PS_ERR_IO, false, "Failed to build file from PSPHOT.LOAD.PSF");
+        return false;
+    }
+
+    if (data->sourcesName) {
+        fileArguments("SOURCES", data->sourcesName, "Input sources", data->config);
+        pmFPAfile *srcs = pmFPAfileBindFromArgs(&status, psf, data->config,
+						"PSPHOT.INPUT.CMF", "SOURCES"); // File
+	fprintf(stderr,"%ld %d\n",(long) srcs, status);
+        if (!status || !srcs) {
+            psError(PS_ERR_IO, false, "Failed to build file from PSPHOT.INPUT.CMF");
+            return false;
+        }
+    }
+
+    pmFPAfile *output = pmFPAfileDefineOutput(data->config, psf->fpa, "PPVIZPSF.OUTPUT");
+    if (!output) {
+        psError(psErrorCodeLast(), false, "Unable to define output.");
+        return false;
+    }
+    output->save = true;
+
+    // Now the camera has been determined, we can read the recipe
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, data->config->recipes, PPVIZPSF_RECIPE); // Recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPVIZPSF_RECIPE);
+        return false;
+    }
+
+    data->minFlux = psMetadataLookupF32(NULL, recipe, "MINFLUX"); // Minimum flux
+    if (!isfinite(data->minFlux) || data->minFlux <= 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find legitimate value for MINFLUX");
+        return false;
+    }
+
+    if (!data->sourcesName) {
+        data->size = psMetadataLookupS32(NULL, recipe, "SIZE"); // Size of PSF
+        if (data->size <= 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find legitimate value for SIZE");
+            return false;
+        }
+    }
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFData.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFData.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFData.c	(revision 41113)
@@ -0,0 +1,57 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPSF.h"
+
+// Destructor
+static void vizPSFDataFree(ppVizPSFData *data // Data to free
+    )
+{
+    psFree(data->psfName);
+    psFree(data->sourcesName);
+    psFree(data->outRoot);
+    psFree(data->input);
+    psFree(data->config);
+    return;
+}
+
+
+ppVizPSFData *ppVizPSFDataAlloc(void)
+{
+    ppVizPSFData *data = psAlloc(sizeof(ppVizPSFData)); // Processing data, to return
+    psMemSetDeallocator(data, (psFreeFunc)vizPSFDataFree);
+
+    data->psfName = NULL;
+    data->sourcesName = NULL;
+    data->fakeNum = 0;
+    data->fakeMag = NAN;
+    data->outRoot = NULL;
+    data->config = NULL;
+    data->minFlux = NAN;
+    data->size = 0;
+    data->x = NAN;
+    data->y = NAN;
+    data->input = NULL;
+
+    return data;
+}
+
+
+ppVizPSFData *ppVizPSFDataInit(int *argc, char **argv)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    ppVizPSFData *data = ppVizPSFDataAlloc(); // Processing data, to return
+    data->config = pmConfigRead(argc, argv, PPVIZPSF_RECIPE);
+    if (!data->config) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read configuration.");
+        return NULL;
+    }
+    return data;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFLoop.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFLoop.c	(revision 41113)
@@ -0,0 +1,222 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPSF.h"
+
+
+bool ppVizPSFLoop(ppVizPSFData *data // Run-time data
+    )
+{
+    pmConfig *config = data->config;                                        // Configuration data
+    pmFPAfile *psfFile = pmFPAfileSelectSingle(config->files, "PSPHOT.PSF.LOAD", 0); // File with PSF
+
+    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        return NULL;
+    }
+    fprintf(stderr,"Woo!\n");
+    pmChip *chip;                       // Chip from FPA
+    while ((chip = pmFPAviewNextChip(view, psfFile->fpa, 1))) {
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            psError(PS_ERR_UNKNOWN, false, "Error loading data from files.");
+            return false;
+        }
+
+        if (chip->cells->n != 1) {
+            psWarning("More than one cell present for chip %d", view->chip);
+        }
+
+        pmCell *cell;                   // Cell from chip
+        while ((cell = pmFPAviewNextCell(view, psfFile->fpa, 1))) {
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                psError(PS_ERR_UNKNOWN, false, "Error loading data from files.");
+                return false;
+            }
+
+            if (cell->readouts->n == 0) {
+                pmReadout *ro = pmReadoutAlloc(cell);
+                ro->data_exists = true;
+                cell->data_exists = true;
+                chip->data_exists = true;
+                psFree(ro);             // Drop reference
+            }
+
+            if (cell->readouts->n > 1) {
+                psWarning("More than one readout present for chip %d, cell %d", view->chip, view->cell);
+            }
+	    fprintf(stderr,"Woo!\n");
+            pmReadout *readout;         // Readout from cell
+            while ((readout = pmFPAviewNextReadout(view, psfFile->fpa, 1))) {
+	      fprintf(stderr,"Woo?\n");
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    psError(PS_ERR_UNKNOWN, false, "Error loading data from files.");
+                    return false;
+                }
+		fprintf(stderr,"Woo2?\n");
+                pmPSF *psf = psMetadataLookupPtr(NULL, chip->analysis, "PSPHOT.PSF");              // PSF
+                assert(psf);
+
+                bool mdok;              // Status of MD lookup
+                psArray *sources = psMetadataLookupPtr(&mdok, readout->analysis, "PSPHOT.SOURCES"); // Sources
+
+		if (sources && !readout->data_exists) {  // This fails if -sources not specified
+                    continue;
+                }
+		readout->data_exists = true;
+		fprintf(stderr,"Woo! %ld\n", sources ? sources->n : -1);
+
+		fprintf(stderr,"%d\n",mdok);
+                int numCols = 0, numRows = 0;              // Size of image
+                psVector *xOffset = NULL, *yOffset = NULL; // Offset from source to true position
+
+                if (sources || data->input || (data->fakeNum > 0 && isfinite(data->fakeMag))) {
+                    numCols = psf->fieldNx;
+                    numRows = psf->fieldNy;
+                    psLogMsg("ppVizPSF", PS_LOG_INFO, "Generating %dx%d image", numCols, numRows);
+                }
+                if (sources && !sources->n) {
+                    psMemIncrRefCounter(sources);
+                    psLogMsg("ppVizPSF", PS_LOG_INFO, "Using %ld input sources from CMF file", sources->n);
+                } else if (data->input) {
+                    psVector *x = data->input->data[0]; // x coordinates
+                    psLogMsg("ppVizPSF", PS_LOG_INFO, "Using %ld input sources from text file", x->n);
+                }
+
+                if (data->fakeNum > 0 && isfinite(data->fakeMag)) {
+		  fprintf(stderr,"Here! fakes\n");
+                    long numOld = 0; // Old number of sources
+                    long numNew = -1; // New number of sources
+                    psLogMsg("ppVizPSF", PS_LOG_INFO, "Adding %d fake sources", data->fakeNum);
+                    if (sources) {
+                        numOld = sources->n;
+                        numNew = numOld + data->fakeNum;
+                        sources = psArrayRealloc(sources, numNew);
+                        sources->n = numNew;
+                    } else {
+                        numNew = data->fakeNum;
+                        sources = psArrayAlloc(numNew);
+                    }
+
+                    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+
+                    for (int i = numOld; i < numNew; i++) {
+                        pmSource *source = pmSourceAlloc(); // Fake source
+                        sources->data[i] = source;
+                        float xSrc = psRandomUniform(rng) * numCols, ySrc = psRandomUniform(rng) * numRows; // Position of source
+
+                        pmModel *model = pmModelFromPSFforXY(psf, xSrc, ySrc, 1.0); // Model for normalisation
+                        float fluxNorm = model->class->modelFlux(model->params);               // Flux for peak=1
+                        float fluxPeak = powf(10.0, -0.4 * data->fakeMag) / fluxNorm; // Peak flux
+                        source->peak = pmPeakAlloc(xSrc, ySrc, fluxPeak, PM_PEAK_LONE);
+                        source->psfMag = data->fakeMag;
+                        psFree(model);
+                    }
+                }
+                if (!sources && !data->input) {
+		  fprintf(stderr,"Here! default\n");
+                    // Generate fake image with only a single realisation of the PSF
+                    sources = psArrayAlloc(1);
+                    pmSource *source = pmSourceAlloc(); // Fake source
+                    sources->data[0] = source;
+                    float xRel = isfinite(data->x) ? data->x : 0.5; // Relative position in x
+                    float yRel = isfinite(data->y) ? data->y : 0.5; // Relative position in y
+                    float xSrc = xRel * psf->fieldNx, ySrc = yRel * psf->fieldNy; // Position of source
+                    source->peak = pmPeakAlloc(xSrc, ySrc, 1.0, PM_PEAK_LONE);
+                    pmModel *model = pmModelFromPSFforXY(psf, xSrc, ySrc, 1.0); // Model for normalisation
+                    float flux = model->class->modelFlux(model->params);               // Flux for peak=1
+                    psFree(model);
+                    source->psfMag = -2.5 * log10(flux);
+                    xOffset = psVectorAlloc(1, PS_TYPE_S32);
+                    yOffset = psVectorAlloc(1, PS_TYPE_S32);
+                    xOffset->data.S32[0] = data->size - xSrc;
+                    yOffset->data.S32[0] = data->size - ySrc;
+                    numCols = 2 * data->size + 1;
+                    numRows = 2 * data->size + 1;
+                    psLogMsg("ppVizPSF", PS_LOG_INFO, "Generating %dx%d image with single PSF",
+                             numCols, numRows);
+
+                    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+                    *trimsec = psRegionSet(0, numCols, 0, numRows);
+                }
+
+                if (numCols <= 0 || numRows <= 0) {
+                    psError(PS_ERR_UNKNOWN, true, "Image size isn't set: %dx%d.", numCols, numRows);
+                    return false;
+                }
+
+                // We have trouble with PSF residuals, so don't use them
+                psFree(psf->residuals);
+                psf->residuals = NULL;
+		fprintf(stderr,"still here");
+                if (sources) {
+		  fprintf(stderr,"Here! sources?\n");
+                    if (!pmReadoutFakeFromSources(readout, numCols, numRows, sources, 0, xOffset, yOffset,
+                                                  psf, data->minFlux, 0, false, true)) {
+                        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake readout.");
+                        return false;
+                    }
+                } else if (data->input) {
+
+                    psVector *x = data->input->data[0]; // x coordinates
+                    psVector *y = data->input->data[1]; // y coordinates
+                    psVector *mag = data->input->data[2]; // Magnitudes
+		    fprintf(stderr,"Here! input? %d %d %g %d %ld\n",numCols,numRows,data->minFlux,1,x->n);
+                    if (!pmReadoutFakeFromVectors(readout, numCols, numRows, x, y, mag, xOffset, yOffset,
+                                                  psf, data->minFlux, 0, false, true)) {
+                        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake readout.");
+                        return false;
+                    }
+                }
+		
+                psFree(sources);
+                psFree(xOffset);
+                psFree(yOffset);
+
+                pmHDU *hdu = pmHDUGetLowest(psfFile->fpa, chip, cell); // HDU for readout
+                if (!hdu) {
+                    psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU for data.");
+                    return false;
+                }
+                if (!hdu->header) {
+                    hdu->header = psMetadataAlloc();
+                }
+                ppVizPSFVersionHeader(hdu->header);
+
+
+                // Readout
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                    psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+                    return false;
+                }
+            }
+            // Cell
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+                return false;
+            }
+        }
+        // Chip
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+            return false;
+        }
+    }
+    // FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFVersion.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFVersion.c	(revision 41113)
@@ -0,0 +1,118 @@
+/** @file ppVizPSFVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppVizPSF
+ *
+ *  @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 "ppVizPSF.h"
+#include "ppVizPSFVersionDefinitions.h"
+
+#ifndef PPVIZPSF_VERSION
+#error "PPVIZPSF_VERSION is not set"
+#endif
+#ifndef PPVIZPSF_BRANCH
+#error "PPVIZPSF_BRANCH is not set"
+#endif
+#ifndef PPVIZPSF_SOURCE
+#error "PPVIZPSF_SOURCE is not set"
+#endif
+
+psString ppVizPSFVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PPVIZPSF_BRANCH, PPVIZPSF_VERSION);
+    return value;
+}
+
+psString ppVizPSFSource(void)
+{
+    return psStringCopy(PPVIZPSF_SOURCE);
+}
+
+psString ppVizPSFVersionLong(void)
+{
+    psString version = ppVizPSFVersion();  // Version, to return
+    psString source = ppVizPSFSource();    // Source
+
+    psStringPrepend(&version, "ppVizPSF ");
+    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 ppVizPSFVersionHeader(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, "ppVizPSF 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 = ppVizPSFVersion(); // Software version
+    psString source  = ppVizPSFSource();  // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "VIZPSF_V", PS_META_REPLACE, NULL, PPVIZPSF_VERSION);
+    
+    psStringPrepend(&version, "ppVizPSF version: ");
+    psStringPrepend(&version, "ppVizPSF 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 ppVizPSFVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("ppVizPSF", PS_LOG_INFO, "ppVizPSF at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppVizPSF = ppVizPSFVersionLong(); // ppVizPSF version
+
+    psLogMsg("ppVizPSF", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("ppVizPSF", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("ppVizPSF", PS_LOG_INFO, "%s", ppVizPSF);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppVizPSF);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFVersionDefinitions.h.in	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPSF/ppVizPSFVersionDefinitions.h.in	(revision 41113)
@@ -0,0 +1,8 @@
+#ifndef PPVIZPSF_VERSION_DEFINITIONS_H
+#define PPVIZPSF_VERSION_DEFINITIONS_H
+
+#define PPVIZPSF_VERSION @PPVIZPSF_VERSION@ // SVN version
+#define PPVIZPSF_BRANCH  @PPVIZPSF_BRANCH@  // SVN branch
+#define PPVIZPSF_SOURCE  @PPVIZPSF_SOURCE@  // SVN source
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/Makefile.am	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/Makefile.am	(revision 41113)
@@ -0,0 +1,32 @@
+bin_PROGRAMS = ppVizPattern
+
+# Force recompilation of ppVizPatternVersion.c, since it gets the version information
+ppVizPatternVersion.c: ppVizPatternVersionDefinitions.h
+ppVizPatternVersionDefinitions.h: ppVizPatternVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl PPVIZPATTERN ppVizPatternVersionDefinitions.h.in ppVizPatternVersionDefinitions.h
+FORCE: ;
+
+BUILT_SOURCES = ppVizPatternVersionDefinitions.h
+
+
+ppVizPattern_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPVIZPATTERN_CFLAGS)
+ppVizPattern_LDFLAGS  = $(PSLIB_LIBS)   $(PSMODULE_LIBS)   $(PPVIZPATTERN_LIBS)
+
+ppVizPattern_SOURCES =		\
+	ppVizPattern.c		\
+	ppVizPatternArguments.c	\
+	ppVizPatternCamera.c	\
+	ppVizPatternData.c		\
+	ppVizPatternLoop.c		\
+	ppVizPatternVersion.c            
+
+noinst_HEADERS = \
+	ppVizPattern.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPattern.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPattern.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPattern.c	(revision 41113)
@@ -0,0 +1,43 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPattern.h"
+
+int main(int argc, char *argv[])
+{
+    ppVizPatternVersionPrint();
+
+    ppVizPatternData *data = ppVizPatternDataInit(&argc, argv);
+    if (!data) {
+        psErrorStackPrint(stderr, "Unable to initialise.");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppVizPatternArguments(data, argc, argv)) {
+        psErrorStackPrint(stderr, "Unable to parse arguments.");
+        psFree(data);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppVizPatternCamera(data)) {
+        psErrorStackPrint(stderr, "Unable to parse camera configuration.");
+        psFree(data);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    if (!ppVizPatternLoop(data)) {
+        psErrorStackPrint(stderr, "Unable to process data.");
+        psFree(data);
+        return PS_EXIT_DATA_ERROR;
+    }
+
+    psFree(data);
+
+    return PS_EXIT_SUCCESS;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPattern.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPattern.h	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPattern.h	(revision 41113)
@@ -0,0 +1,41 @@
+#ifndef PP_VIZPSF_H
+#define PP_VIZPSF_H
+
+#include <pslib.h>
+#include <psmodules.h>
+
+#define PPVIZPATTERN_RECIPE "PPVIZPATTERN"      // Recipe name
+
+// Data for processing
+typedef struct {
+    psString patternName;               // Filename with pattern
+    psString outRoot;                   // Output root name
+    pmConfig *config;                   // Configuration
+} ppVizPatternData;
+
+/// Initialise data for processing
+ppVizPatternData *ppVizPatternDataInit(int *argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse command-line arguments
+bool ppVizPatternArguments(ppVizPatternData *data, // Data for processing
+                        int argc, char *argv[] // Command-line arguments
+    );
+
+/// Parse camera configurations
+bool ppVizPatternCamera(ppVizPatternData *data // Data for processing
+    );
+
+/// Loop over input data, processing
+bool ppVizPatternLoop(ppVizPatternData *data // Data for processing
+    );
+
+/// Add version information to header
+bool ppVizPatternVersionHeader(
+    psMetadata *header                  // Header to supplement
+    );
+
+/// Print version information to stdout
+void ppVizPatternVersionPrint(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternArguments.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternArguments.c	(revision 41113)
@@ -0,0 +1,68 @@
+/** @file ppVizPatternArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppVizPattern
+ *
+ *  @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 "ppVizPattern.h"
+
+/// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  ppVizPatternData *data   // Run-time data
+    )
+{
+    fprintf(stderr, "\nPan-STARRS pattern visualisation\n\n");
+    fprintf(stderr,
+            "Usage: %s -pattern PATTERN.ptn OUTPUT_ROOT\n\n",
+            program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(arguments);
+    psFree(data);
+
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+
+bool ppVizPatternArguments(ppVizPatternData *data, int argc, char *argv[])
+{
+    assert(data);
+    assert(data->config);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-pattern", 0, "Filename of pattern correction", NULL);
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 2) {
+        usage(argv[0], arguments, data);
+    }
+
+    data->patternName = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-pattern"));
+    data->outRoot = psStringCopy(argv[1]);
+    psMetadataAddStr(data->config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Output root name", data->outRoot);
+
+    psTrace("ppVizPattern", 1, "Done reading command-line arguments\n");
+    psFree(arguments);
+
+    PS_ASSERT_STRING_NON_EMPTY(data->patternName, false);
+    PS_ASSERT_STRING_NON_EMPTY(data->outRoot, false);
+
+    return true;
+}
+
+
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternCamera.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternCamera.c	(revision 41113)
@@ -0,0 +1,59 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPattern.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 ppVizPatternCamera(ppVizPatternData *data // Run-time data
+    )
+{
+    bool status;                        // Status of file definition
+
+    fileArguments("PATTERN", data->patternName, "Input pattern", data->config);
+    pmFPAfile *input = pmFPAfileDefineFromArgs(&status, data->config, "PPVIZPATTERN.INPUT",
+                                               "PATTERN"); // File
+    if (!status || !input) {
+        psError(PS_ERR_IO, false, "Failed to build file from PPVIZPATTERN.INPUT");
+        return false;
+    }
+
+    pmFPAfile *output = pmFPAfileDefineOutput(data->config, input->fpa, "PPVIZPATTERN.OUTPUT");
+    if (!output) {
+        psError(psErrorCodeLast(), false, "Unable to define output.");
+        return false;
+    }
+    output->save = true;
+
+#if 0
+    // Now the camera has been determined, we can read the recipe
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, data->config->recipes, PPVIZPATTERN_RECIPE); // Recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPVIZPATTERN_RECIPE);
+        return false;
+    }
+#endif
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternData.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternData.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternData.c	(revision 41113)
@@ -0,0 +1,47 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPattern.h"
+
+// Destructor
+static void vizPatternDataFree(ppVizPatternData *data // Data to free
+    )
+{
+    psFree(data->patternName);
+    psFree(data->outRoot);
+    psFree(data->config);
+    return;
+}
+
+
+ppVizPatternData *ppVizPatternDataAlloc(void)
+{
+    ppVizPatternData *data = psAlloc(sizeof(ppVizPatternData)); // Processing data, to return
+    psMemSetDeallocator(data, (psFreeFunc)vizPatternDataFree);
+
+    data->patternName = NULL;
+    data->outRoot = NULL;
+    data->config = NULL;
+
+    return data;
+}
+
+
+ppVizPatternData *ppVizPatternDataInit(int *argc, char **argv)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    ppVizPatternData *data = ppVizPatternDataAlloc(); // Processing data, to return
+    data->config = pmConfigRead(argc, argv, PPVIZPATTERN_RECIPE);
+    if (!data->config) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read configuration.");
+        return NULL;
+    }
+    return data;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternLoop.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternLoop.c	(revision 41113)
@@ -0,0 +1,129 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppVizPattern.h"
+
+#define MASK_BAD 0xFFFF                 // Mask for bad pixels
+
+bool ppVizPatternLoop(ppVizPatternData *data // Run-time data
+    )
+{
+    pmConfig *config = data->config;                                        // Configuration data
+    pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPVIZPATTERN.INPUT", 0); // File with PSF
+
+    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        return NULL;
+    }
+
+    pmChip *chip;                       // Chip from FPA
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1))) {
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            psError(PS_ERR_UNKNOWN, false, "Error loading data from files.");
+            return false;
+        }
+
+        pmCell *cell;                   // Cell from chip
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1))) {
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                psError(PS_ERR_UNKNOWN, false, "Error loading data from files.");
+                return false;
+            }
+
+            pmReadout *readout;         // Readout from cell
+            while ((readout = pmFPAviewNextReadout(view, input->fpa, 1))) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    psError(PS_ERR_UNKNOWN, 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, cell->concepts, "CELL.XSIZE");
+                    numRows = psMetadataLookupS32(NULL, cell->concepts, "CELL.YSIZE");
+                }
+
+                // Get size of image from TRIMSEC
+                if (numCols == 0 || numRows == 0) {
+                    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->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);
+
+                if (!pmPatternRowApply(readout, MASK_BAD) || !pmPatternCellApply(readout, MASK_BAD)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to apply pattern correction.");
+                    return false;
+                }
+
+                // Remove bias sections to avoid warnings
+                psMetadataItem *biassec = psMetadataLookup(readout->parent->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;
+
+                pmHDU *hdu = pmHDUGetLowest(input->fpa, chip, cell); // HDU for readout
+                if (!hdu) {
+                    psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU for data.");
+                    return false;
+                }
+                if (!hdu->header) {
+                    hdu->header = psMetadataAlloc();
+                }
+                ppVizPatternVersionHeader(hdu->header);
+
+
+                // Readout
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                    psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+                    return false;
+                }
+            }
+            // Cell
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+                return false;
+            }
+        }
+        // Chip
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+            return false;
+        }
+    }
+    // FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psError(PS_ERR_UNKNOWN, false, "Error saving data to files.");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternVersion.c	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternVersion.c	(revision 41113)
@@ -0,0 +1,118 @@
+/** @file ppVizPatternVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup ppVizPattern
+ *
+ *  @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 "ppVizPattern.h"
+#include "ppVizPatternVersionDefinitions.h"
+
+#ifndef PPVIZPATTERN_VERSION
+#error "PPVIZPATTERN_VERSION is not set"
+#endif
+#ifndef PPVIZPATTERN_BRANCH
+#error "PPVIZPATTERN_BRANCH is not set"
+#endif
+#ifndef PPVIZPATTERN_SOURCE
+#error "PPVIZPATTERN_SOURCE is not set"
+#endif
+
+psString ppVizPatternVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PPVIZPATTERN_BRANCH, PPVIZPATTERN_VERSION);
+    return value;
+}
+
+psString ppVizPatternSource(void)
+{
+    return psStringCopy(PPVIZPATTERN_SOURCE);
+}
+
+psString ppVizPatternVersionLong(void)
+{
+    psString version = ppVizPatternVersion();  // Version, to return
+    psString source = ppVizPatternSource();    // Source
+
+    psStringPrepend(&version, "ppVizPattern ");
+    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 ppVizPatternVersionHeader(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, "ppVizPattern 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 = ppVizPatternVersion(); // Software version
+    psString source  = ppVizPatternSource();  // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "VIZPAT_V", PS_META_REPLACE, NULL, PPVIZPATTERN_VERSION);
+    
+    psStringPrepend(&version, "ppVizPattern version: ");
+    psStringPrepend(&version, "ppVizPattern 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 ppVizPatternVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("ppVizPattern", PS_LOG_INFO, "ppVizPattern at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppVizPattern = ppVizPatternVersionLong(); // ppVizPattern version
+
+    psLogMsg("ppVizPattern", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("ppVizPattern", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("ppVizPattern", PS_LOG_INFO, "%s", ppVizPattern);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppVizPattern);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternVersionDefinitions.h.in	(revision 41113)
+++ /branches/ccl_branches/ipponly-20191108/ppViz/src/ppVizPattern/ppVizPatternVersionDefinitions.h.in	(revision 41113)
@@ -0,0 +1,8 @@
+#ifndef PPVIZPATTERN_VERSION_DEFINITIONS_H
+#define PPVIZPATTERN_VERSION_DEFINITIONS_H
+
+#define PPVIZPATTERN_VERSION @PPVIZPATTERN_VERSION@ // SVN version
+#define PPVIZPATTERN_BRANCH  @PPVIZPATTERN_BRANCH@  // SVN branch
+#define PPVIZPATTERN_SOURCE  @PPVIZPATTERN_SOURCE@  // SVN source
+
+#endif
