Index: trunk/ppTranslate/src/Makefile.am
===================================================================
--- trunk/ppMops/src/Makefile.am	(revision 25430)
+++ trunk/ppTranslate/src/Makefile.am	(revision 25923)
@@ -1,23 +1,23 @@
-bin_PROGRAMS = ppMops
+bin_PROGRAMS = ppMops ppMonet
 
 if HAVE_SVNVERSION
-PPMOPS_VERSION=`$(SVNVERSION) ..`
+PPTRANSLATE_VERSION=`$(SVNVERSION) ..`
 else
-PPMOPS_VERSION="UNKNOWN"
+PPTRANSLATE_VERSION="UNKNOWN"
 endif
 
 if HAVE_SVN
-PPMOPS_BRANCH=`$(SVN) info .. | $(SED) -n -e '/URL:/ h' -e '/Repository Root:/ { x; H; x; s|Repository Root: \(.*\)\nURL: \1\(.*\)|\2| ; s|^/|| ; s|/[a-zA-Z]*/src.*|| ; p }'`
-PPMOPS_SOURCE=`$(SVN) info | $(SED) -n -e 's/Repository UUID: // p'`
+PPTRANSLATE_BRANCH=`$(SVN) info .. | $(SED) -n -e '/URL:/ h' -e '/Repository Root:/ { x; H; x; s|Repository Root: \(.*\)\nURL: \1\(.*\)|\2| ; s|^/|| ; s|/[a-zA-Z]*/src.*|| ; p }'`
+PPTRANSLATE_SOURCE=`$(SVN) info | $(SED) -n -e 's/Repository UUID: // p'`
 else
-PPMOPS_BRANCH="UNKNOWN"
-PPMOPS_SOURCE="UNKNOWN"
+PPTRANSLATE_BRANCH="UNKNOWN"
+PPTRANSLATE_SOURCE="UNKNOWN"
 endif
 
 # Force recompilation of ppMopsVersion.c, since it gets the version information
-ppMopsVersion.c: ppMopsVersionDefinitions.h
-ppMopsVersionDefinitions.h: ppMopsVersionDefinitions.h.in FORCE
-	-$(RM) ppMopsVersionDefinitions.h
-	$(SED) -e "s|@PPMOPS_VERSION@|\"$(PPMOPS_VERSION)\"|" -e "s|@PPMOPS_BRANCH@|\"$(PPMOPS_BRANCH)\"|" -e "s|@PPMOPS_SOURCE@|\"$(PPMOPS_SOURCE)\"|" ppMopsVersionDefinitions.h.in > ppMopsVersionDefinitions.h
+ppTranslateVersion.c: ppTranslateVersionDefinitions.h
+ppTranslateVersionDefinitions.h: ppTranslateVersionDefinitions.h.in FORCE
+	-$(RM) ppTranslateVersionDefinitions.h
+	$(SED) -e "s|@PPTRANSLATE_VERSION@|\"$(PPTRANSLATE_VERSION)\"|" -e "s|@PPTRANSLATE_BRANCH@|\"$(PPTRANSLATE_BRANCH)\"|" -e "s|@PPTRANSLATE_SOURCE@|\"$(PPTRANSLATE_SOURCE)\"|" ppTranslateVersionDefinitions.h.in > ppTranslateVersionDefinitions.h
 FORCE: ;
 
@@ -27,5 +27,5 @@
 ppMops_SOURCES =		\
 	ppMops.c		\
-	ppMopsVersion.c		\
+	ppTranslateVersion.c	\
 	ppMopsArguments.c	\
 	ppMopsDetections.c	\
@@ -34,6 +34,17 @@
 	ppMopsMerge.c
 
+ppMonet_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(PPMONET_CFLAGS)
+ppMonet_LDFLAGS  = $(PSLIB_LIBS) $(PPMONET_LIBS)
+
+ppMonet_SOURCES =		\
+	ppMonet.c		\
+	ppTranslateVersion.c	\
+	ppMonetArguments.c
+
+
 noinst_HEADERS = \
-	ppMops.h
+	ppTranslateVersion.h \
+	ppMops.h \
+	ppMonet.h
 
 
Index: trunk/ppTranslate/src/ppMonet.c
===================================================================
--- trunk/ppTranslate/src/ppMonet.c	(revision 25923)
+++ trunk/ppTranslate/src/ppMonet.c	(revision 25923)
@@ -0,0 +1,165 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <zlib.h>
+#include <pslib.h>
+
+#include "ppTranslateVersion.h"
+#include "ppMonet.h"
+
+int main(int argc, char *argv[])
+{
+    psLibInit(NULL);
+
+    ppMonetArguments *args = ppMonetArgumentsParse(argc, argv); // Parsed arguments
+    if (!args) {
+        psErrorStackPrint(stderr, "Error parsing arguments");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    psTrace("ppMonet.read", 1, "Reading input detections\n");
+
+    psFits *fits = psFitsOpen(args->input, "r"); // FITS file
+    if (!fits) {
+        psError(PS_ERR_IO, false, "Unable to open input %s", args->input);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    // Set up output file
+    FILE *file = fopen(args->output, "w"); // File handle
+    if (!file) {
+        psErrorStackPrint(stderr, "Error opening file %s", args->output);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+    gzFile gz = gzdopen(fileno(file), "w"); // Gzip file handle
+    gzprintf(gz, "# Pan-STARRS IPP detections for Dave Monet\n");
+    gzprintf(gz, "#\n");
+    gzprintf(gz, "# Translated from %s\n", args->input);
+    psString source = ppTranslateSource(), version = ppTranslateVersion();
+    gzprintf(gz, "# S/W source = %s\n", source);
+    gzprintf(gz, "# S/W version = %s\n", version);
+    gzprintf(gz, "#\n");
+    psFree(source);
+    psFree(version);
+    gzprintf(gz, "# exp_name = %s\n", args->exp_name);
+    gzprintf(gz, "# exp_id = %" PRId64 "\n", args->exp_id);
+    gzprintf(gz, "# chip_id = %" PRId64 "\n", args->chip_id);
+    gzprintf(gz, "# cam_id = %" PRId64 "\n", args->cam_id);
+    gzprintf(gz, "# ZP = %f\n", args->zp);
+    gzprintf(gz, "# ZP error = %f\n", args->zpErr);
+    gzprintf(gz, "# Astrometry rms = %f\n", args->rmsAstrom);
+
+    psFree(args);
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Primary header
+    if (!header) {
+        psError(PS_ERR_IO, false, "Unable to read header");
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    double raBoresight = psMetadataLookupF64(NULL, header, "RA");
+    double decBoresight = psMetadataLookupF64(NULL, header, "DEC");
+    psString filter = psMemIncrRefCounter(psMetadataLookupStr(NULL, header, "FILTERID"));
+    float airmass = psMetadataLookupF32(NULL, header, "AIRMASS");
+    float exptime = psMetadataLookupF32(NULL, header, "EXPTIME");
+    double posangle = psMetadataLookupF64(NULL, header, "POSANGLE");
+    double rotangle = psMetadataLookupF64(NULL, header, "ROTANGLE");
+    double alt = psMetadataLookupF64(NULL, header, "ALT");
+    double az = psMetadataLookupF64(NULL, header, "AZ");
+    double mjd = psMetadataLookupF64(NULL, header, "MJD-OBS");
+
+    gzprintf(gz, "# RA = %.8lf\n", raBoresight);
+    gzprintf(gz, "# Dec = %.8lf\n", decBoresight);
+    gzprintf(gz, "# Filter = %s\n", filter);
+    gzprintf(gz, "# Airmass = %f\n", airmass);
+    gzprintf(gz, "# ExpTime = %f\n", exptime);
+    gzprintf(gz, "# PA = %lf\n", posangle);
+    gzprintf(gz, "# Rotator = %lf\n", rotangle);
+    gzprintf(gz, "# Alt = %.8lf\n", alt);
+    gzprintf(gz, "# Az = %.8lf\n", az);
+    gzprintf(gz, "# MJD = %.6lf\n", mjd);
+    gzprintf(gz, "#\n");
+    gzprintf(gz, "# chip , x , y , RA , Dec , Mag , MagErr , Width, Flags\n");
+
+    psFree(header);
+
+    int numHDU = psFitsGetSize(fits);   // Number of HDUs
+    for (int i = 1; i < numHDU; i++) {
+        if (!psFitsMoveExtNum(fits, i, false)) {
+            psError(PS_ERR_IO, false, "Unable to move to HDU %d", i);
+            exit(PS_EXIT_SYS_ERROR);
+        }
+
+        if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE) {
+            psTrace("ppMonet", 1, "Skipping extension %d: not a binary table", i);
+            continue;
+        }
+
+        psMetadata *header = psFitsReadHeader(NULL, fits); // Primary header
+        if (!header) {
+            psError(PS_ERR_IO, false, "Unable to read header %d\n", i);
+            exit(PS_EXIT_SYS_ERROR);
+            return false;
+        }
+
+        const char *extname = psMetadataLookupStr(NULL, header, "EXTNAME");
+        const char *exttype = extname + strlen(extname) - strlen(EXT_TYPE);
+        if (strcmp(exttype, EXT_TYPE) != 0) {
+            psTrace("ppMonet", 1, "Skipping extension %d (%s): not correct type", i, extname);
+            psFree(header);
+            continue;
+        }
+
+        long size = psFitsTableSize(fits); // Size of table
+        if (size <= 0) {
+            psErrorStackPrint(stderr, "Unable to determine size of table %d", i);
+            psFree(header);
+            exit(PS_EXIT_SYS_ERROR);
+        }
+
+        psString chipName = psStringNCopy(extname, strlen(extname) - strlen(EXT_TYPE)); // Name of chip
+        psFree(header);
+
+        psTrace("ppMonet.read", 3, "Reading %ld rows from %s\n", size, extname);
+        psArray *table = psFitsReadTable(fits); // Table of interest
+        if (!table) {
+            psError(PS_ERR_IO, false, "Unable to read table %d", i);
+            psFree(chipName);
+            exit(PS_EXIT_SYS_ERROR);
+        }
+
+        psTrace("ppMonet.read", 3, "Writing %ld rows for %s\n", size, chipName);
+        for (long j = 0; j < size; j++) {
+            psMetadata *row = table->data[j]; // Row of interest
+
+            float x = psMetadataLookupF32(NULL, row, "X_PSF");
+            float y = psMetadataLookupF32(NULL, row, "Y_PSF");
+            double ra = DEG_TO_RAD(psMetadataLookupF64(NULL, row, "RA_PSF"));
+            double dec = DEG_TO_RAD(psMetadataLookupF64(NULL, row, "DEC_PSF"));
+            float mag = psMetadataLookupF32(NULL, row, "PSF_INST_MAG");
+            float magErr = psMetadataLookupF32(NULL, row, "PSF_INST_MAG_SIG");
+            float major = psMetadataLookupF32(NULL, row, "PSF_MAJOR");
+            float minor = psMetadataLookupF32(NULL, row, "PSF_MINOR");
+            psU32 flags = psMetadataLookupU32(NULL, row, "FLAGS");
+
+            float width = 0.5 * (major + minor);
+
+            gzprintf(gz, "%s , %.2f , %.2f , %.8lf , %.8lf , %.3f , %.3f , %.2f , %#08x\n",
+                     chipName, x, y, ra, dec, mag, magErr, width, flags);
+        }
+        psFree(table);
+        psFree(chipName);
+    }
+    gzclose(gz);
+    psFitsClose(fits);
+    psFree(filter);
+
+    psTrace("ppMonet.read", 1, "Done reading input detections\n");
+
+    psLibFinalize();
+
+    return PS_EXIT_SUCCESS;
+}
Index: trunk/ppTranslate/src/ppMonet.h
===================================================================
--- trunk/ppTranslate/src/ppMonet.h	(revision 25923)
+++ trunk/ppTranslate/src/ppMonet.h	(revision 25923)
@@ -0,0 +1,23 @@
+#ifndef PP_MOPS_H
+#define PP_MOPS_H
+
+#include <pslib.h>
+
+#define EXT_TYPE ".psf"                 // Extension type to consider
+
+// Configuration data
+typedef struct {
+    psString input;                     // Input filenames
+    psString exp_name;                  // Exposure name
+    psS64 exp_id;                       // Exposure identifier
+    psS64 chip_id;                      // Chip stage identifier
+    psS64 cam_id;                       // Camera stage identifier
+    float zp, zpErr;                    // Magnitude zero point and error
+    float rmsAstrom;                    // Astrometric solution RMS
+    psString output;                    // Output filename
+} ppMonetArguments;
+
+/// Parse arguments
+ppMonetArguments *ppMonetArgumentsParse(int argc, char *argv[]);
+
+#endif
Index: trunk/ppTranslate/src/ppMonetArguments.c
===================================================================
--- trunk/ppTranslate/src/ppMonetArguments.c	(revision 25923)
+++ trunk/ppTranslate/src/ppMonetArguments.c	(revision 25923)
@@ -0,0 +1,88 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMonet.h"
+
+// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments // Command-line arguments
+                  )
+{
+    fprintf(stderr, "\nPan-STARRS IPP-Monet detection translator\n\n");
+    fprintf(stderr, "Usage: %s INPUT_NAME OUTPUT_NAME\n", program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psLibFinalize();
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+static void monetArgumentsFree(ppMonetArguments *args)
+{
+    psFree(args->input);
+    psFree(args->exp_name);
+    psFree(args->output);
+    return;
+}
+
+ppMonetArguments *ppMonetArgumentsAlloc(void)
+{
+    ppMonetArguments *args = psAlloc(sizeof(ppMonetArguments)); // Data to return
+    psMemSetDeallocator(args, (psFreeFunc)monetArgumentsFree);
+
+    args->input = NULL;
+    args->exp_name = NULL;
+    args->exp_id = 0;
+    args->chip_id = 0;
+    args->cam_id = 0;
+    args->zp = NAN;
+    args->zpErr = NAN;
+    args->rmsAstrom = NAN;
+    args->output = NULL;
+
+    return args;
+}
+
+
+ppMonetArguments *ppMonetArgumentsParse(int argc, char *argv[])
+{
+    assert(argv);
+
+    psTrace("ppMonet.args", 1, "Parsing command-line arguments\n");
+
+    psArgumentVerbosity(&argc, argv);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-exp_name", 0, "Exposure name", NULL);
+    psMetadataAddS64(arguments, PS_LIST_TAIL, "-exp_id", 0, "Exposure identifier", 0);
+    psMetadataAddS64(arguments, PS_LIST_TAIL, "-chip_id", 0, "Chip stage identifier", 0);
+    psMetadataAddS64(arguments, PS_LIST_TAIL, "-cam_id", 0, "Camera stage identifier", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-zp", 0, "Magnitude zero point", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-zp_error", 0, "Error in magnitude zero point", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-astrom_rms", 0, "Astrometric solution RMS", NAN);
+
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 3) {
+        usage(argv[0], arguments);
+    }
+
+    ppMonetArguments *args = ppMonetArgumentsAlloc(); // Arguments, to return
+
+    args->input = psStringCopy(argv[1]);
+    args->output = psStringCopy(argv[2]);
+
+    args->exp_name = psMetadataLookupStr(NULL, arguments, "-exp_name");
+    args->exp_id = psMetadataLookupS64(NULL, arguments, "-exp_id");
+    args->chip_id = psMetadataLookupS64(NULL, arguments, "-chip_id");
+    args->cam_id = psMetadataLookupS64(NULL, arguments, "-cam_id");
+    args->zp = psMetadataLookupF32(NULL, arguments, "-zp");
+    args->zpErr = psMetadataLookupF32(NULL, arguments, "-zp_error");
+    args->rmsAstrom = psMetadataLookupF32(NULL, arguments, "-astrom_rms");
+
+    psTrace("ppMonet.args", 1, "Done parsing command-line arguments\n");
+
+    return args;
+}
Index: trunk/ppTranslate/src/ppMonetMerge.c
===================================================================
--- trunk/ppTranslate/src/ppMonetMerge.c	(revision 25923)
+++ trunk/ppTranslate/src/ppMonetMerge.c	(revision 25923)
@@ -0,0 +1,171 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "ppMonet.h"
+
+#define LEAF_SIZE 4                     // Size of leaf
+#define MATCH_RADIUS SEC_TO_RAD(1.0)    // Matching radius
+#define MJD_TOL 1.0/3600.0/24.0         // Tolerance for MJD matching
+#define BORESIGHT_TOL SEC_TO_RAD(1.0)   // Tolerance for boresight matching
+#define EXPTIME_TOL 1.0e-3              // Tolerance for exposure time matching
+#define POSANGLE_TOL SEC_TO_RAD(1.0)    // Tolerance for position angle matching
+#define AIRMASS_TOL 1.0e-3              // Tolerance for airmass matching
+
+// Get distance from detection to centre of image
+static float mergeDistance(const ppMonetDetections *detections, // Detections of interest
+                           long index                          // Index for source of interest
+    )
+{
+    float dx = detections->x->data.F32[index] - detections->naxis1->data.S32[index] / 2.0;
+    float dy = detections->y->data.F32[index] - detections->naxis2->data.S32[index] / 2.0;
+    return PS_SQR(dx) + PS_SQR(dy);
+}
+
+
+ppMonetDetections *ppMonetMerge(const psArray *detections)
+{
+    PS_ASSERT_ARRAY_NON_NULL(detections, NULL);
+
+    psTrace("ppMonet.merge", 1, "Merging detections from %ld inputs\n", detections->n);
+
+    ppMonetDetections *merged = NULL;    // Merged list
+    int num = 1;                                                         // Number of merged files
+    for (int i = 0; i < detections->n; i++) {
+        ppMonetDetections *det = detections->data[i]; // Detections of interest
+        if (!det) {
+            psTrace("ppMonet.merge", 3, "Ignoring NULL input %d\n", i);
+            continue;
+        } else if (det->num == 0) {
+            psTrace("ppMonet.merge", 3, "Ignoring empty input %d\n", i);
+            continue;
+        }
+        num++;
+        if (!merged) {
+            psTrace("ppMonet.merge", 3, "Accepting %ld detections from input %d\n", det->num, i);
+            merged = psMemIncrRefCounter(det);
+            continue;
+        }
+        psTrace("ppMonet.merge", 3, "Merging %ld detections from input %d\n", det->num, i);
+
+        // XXX compare exposure properties
+        if (strcmp(merged->raBoresight, det->raBoresight) != 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure RA values differ: %s vs %s",
+                    merged->raBoresight, det->raBoresight);
+            return NULL;
+        }
+        if (strcmp(merged->decBoresight, det->decBoresight) != 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure Dec values differ: %s vs %s",
+                    merged->decBoresight, det->decBoresight);
+            return NULL;
+        }
+        if (strcmp(merged->filter, det->filter) != 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure filter values differ: %s vs %s",
+                    merged->filter, det->filter);
+            return NULL;
+        }
+
+        if (fabsf(merged->airmass - det->airmass) > AIRMASS_TOL) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure airmass values differ: %f vs %f",
+                    merged->airmass, det->airmass);
+            return NULL;
+        }
+        if (fabsf(merged->exptime - det->exptime) > EXPTIME_TOL) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure exposure time values differ: %f vs %f",
+                    merged->exptime, det->exptime);
+            return NULL;
+        }
+        if (fabs(merged->posangle - det->posangle) > POSANGLE_TOL) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure position angle values differ: %f vs %f",
+                    merged->posangle, det->posangle);
+            return NULL;
+        }
+        if (fabs(merged->alt - det->alt) > BORESIGHT_TOL) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure altitude values differ: %lf vs %lf",
+                    merged->alt, det->alt);
+            return NULL;
+        }
+        if (fabs(merged->az - det->az) > BORESIGHT_TOL) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure azimuth values differ: %lf vs %lf",
+                    merged->az, det->az);
+            return NULL;
+        }
+        if (fabs(merged->mjd - det->mjd) > MJD_TOL) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure MJD values differ: %lf vs %lf",
+                    merged->mjd, det->mjd);
+            return NULL;
+        }
+
+        merged->seeing += det->seeing;  // Taking average
+
+        psTree *tree = psTreePlant(2, LEAF_SIZE, PS_TREE_SPHERICAL, merged->ra, merged->dec); // kd tree
+        if (!tree) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate kd tree");
+            psFree(merged);
+            return NULL;
+        }
+
+        psVector *coords = psVectorAlloc(2, PS_TYPE_F64); // Coordinates of interest
+        for (int j = 0; j < det->num; j++) {
+            coords->data.F64[0] = det->ra->data.F64[j];
+            coords->data.F64[1] = det->dec->data.F64[j];
+            psVector *indices = psTreeAllWithin(tree, coords, MATCH_RADIUS); // Indices for matching sources
+            if (!indices) {
+                psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to search for matches");
+                psFree(coords);
+                psFree(tree);
+                psFree(merged);
+                return NULL;
+            }
+            if (indices->n == 0) {
+                psTrace("ppMonet.merge", 9, "No matches for source %d in input %d\n", j, i);
+                psFree(indices);
+                ppMonetDetectionsCopySingle(merged, det, j);
+                continue;
+            }
+            psTrace("ppMonet.merge", 5, "%ld matches for source %d from input %d\n", indices->n, j, i);
+
+            // Which one do we keep?
+            float bestDistance = INFINITY; // Best distance to centre
+            long bestIndex = -1;           // Index with best distance
+            for (int k = 0; k < indices->n; k++) {
+                long index = indices->data.S64[k]; // Index of point
+                float distance = mergeDistance(merged, index); // Distance to centre of image
+                if (distance < bestDistance) {
+                    bestDistance = distance;
+                    bestIndex = index;
+                }
+            }
+
+            float distance = mergeDistance(det, j); // Distance to centre of image
+            if (distance < bestDistance) {
+                psTrace("ppMonet.merge", 6, "New source clobbers old sources\n");
+                // Blow away existing sources
+                for (int k = 0; k < indices->n; k++) {
+                    long index = indices->data.S64[k]; // Index of point
+                    merged->mask->data.U8[index] = 0xFF;
+                }
+                ppMonetDetectionsCopySingle(merged, det, j);
+            } else {
+                psTrace("ppMonet.merge", 6, "Old sources clobber new source\n");
+            }
+            psFree(indices);
+        }
+
+        psTrace("ppMonet.merge", 3, "Done merging input %d, %ld merged sources\n", i, merged->num);
+
+        psFree(tree);
+        ppMonetDetectionsPurge(merged);
+    }
+
+    psTrace("ppMonet.merge", 2, "%ld sources in merged detections list\n", merged->num);
+
+    merged->seeing /= num;
+
+    return merged;
+}
+
Index: trunk/ppTranslate/src/ppMops.h
===================================================================
--- trunk/ppMops/src/ppMops.h	(revision 25430)
+++ trunk/ppTranslate/src/ppMops.h	(revision 25923)
@@ -71,20 +71,3 @@
 bool ppMopsWrite(const ppMopsDetections *detections, const ppMopsArguments *args);
 
-
-
-/// Return version
-psString ppMopsVersion(void);
-
-/// Return source
-psString ppMopsSource(void);
-
-/// Return detailed version information
-psString ppMopsVersionLong(void);
-
-/// Put version into header
-bool ppMopsVersionHeader(psMetadata *header);
-
-/// Print version information
-void ppMopsVersionPrint(void);
-
 #endif
Index: trunk/ppTranslate/src/ppMopsVersion.c
===================================================================
--- trunk/ppMops/src/ppMopsVersion.c	(revision 25430)
+++ 	(revision )
@@ -1,100 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <pslib.h>
-
-#include "ppMops.h"
-#include "ppMopsVersionDefinitions.h"
-
-#ifndef PPMOPS_VERSION
-#error "PPMOPS_VERSION is not set"
-#endif
-#ifndef PPMOPS_BRANCH
-#error "PPMOPS_BRANCH is not set"
-#endif
-#ifndef PPMOPS_SOURCE
-#error "PPMOPS_SOURCE is not set"
-#endif
-
-psString ppMopsVersion(void)
-{
-    char *value = NULL;
-    psStringAppend(&value, "%s@%s", PPMOPS_BRANCH, PPMOPS_VERSION);
-    return value;
-}
-
-psString ppMopsSource(void)
-{
-    return psStringCopy(PPMOPS_SOURCE);
-}
-
-psString ppMopsVersionLong(void)
-{
-    psString version = ppMopsVersion();  // Version, to return
-    psString source = ppMopsSource();    // Source
-
-    psStringPrepend(&version, "ppMops ");
-    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 ppMopsVersionHeader(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, "ppMops at %s", timeString);
-    psFree(timeString);
-    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
-    psFree(history);
-
-    psLibVersionHeader(header);
-
-    psString version = ppMopsVersion(); // Software version
-    psString source  = ppMopsSource();  // Software source
-
-    psStringPrepend(&version, "ppMops version: ");
-    psStringPrepend(&source, "ppMops 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 ppMopsVersionPrint(void)
-{
-    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
-    psString timeString = psTimeToISO(time); // The time in an ISO string
-    psFree(time);
-    psLogMsg("ppMops", PS_LOG_INFO, "ppMops at %s", timeString);
-    psFree(timeString);
-
-    psString pslib = psLibVersionLong();// psLib version
-    psString ppMops = ppMopsVersionLong(); // ppMops version
-
-    psLogMsg("ppMops", PS_LOG_INFO, "%s", pslib);
-    psLogMsg("ppMops", PS_LOG_INFO, "%s", ppMops);
-
-    psFree(pslib);
-    psFree(ppMops);
-
-    return;
-}
Index: trunk/ppTranslate/src/ppMopsVersionDefinitions.h.in
===================================================================
--- trunk/ppMops/src/ppMopsVersionDefinitions.h.in	(revision 25430)
+++ 	(revision )
@@ -1,8 +1,0 @@
-#ifndef PPMOPS_VERSION_DEFINITIONS_H
-#define PPMOPS_VERSION_DEFINITIONS_H
-
-#define PPMOPS_VERSION @PPMOPS_VERSION@ // SVN version
-#define PPMOPS_BRANCH  @PPMOPS_BRANCH@  // SVN branch
-#define PPMOPS_SOURCE  @PPMOPS_SOURCE@  // SVN source
-
-#endif
Index: trunk/ppTranslate/src/ppMopsWrite.c
===================================================================
--- trunk/ppMops/src/ppMopsWrite.c	(revision 25430)
+++ trunk/ppTranslate/src/ppMopsWrite.c	(revision 25923)
@@ -7,4 +7,5 @@
 
 #include "ppMops.h"
+#include "ppTranslateVersion.h"
 
 bool ppMopsWrite(const ppMopsDetections *det, const ppMopsArguments *args)
@@ -20,8 +21,8 @@
 
     psMetadata *header = psMetadataAlloc(); // Header to write
-    psString source = ppMopsSource(), version = ppMopsVersion();
+    psString source = ppTranslateSource(), version = ppTranslateVersion();
     psMetadataAddStr(header, PS_LIST_TAIL, "SWSOURCE", 0, "Software source", source);
     psMetadataAddStr(header, PS_LIST_TAIL, "SWVERSN", 0, "Software version", version);
-    ppMopsVersionHeader(header);
+    ppTranslateVersionHeader(header);
     psFree(source);
     psFree(version);
Index: trunk/ppTranslate/src/ppTranslateVersion.c
===================================================================
--- trunk/ppTranslate/src/ppTranslateVersion.c	(revision 25923)
+++ trunk/ppTranslate/src/ppTranslateVersion.c	(revision 25923)
@@ -0,0 +1,99 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "ppTranslateVersionDefinitions.h"
+
+#ifndef PPTRANSLATE_VERSION
+#error "PPTRANSLATE_VERSION is not set"
+#endif
+#ifndef PPTRANSLATE_BRANCH
+#error "PPTRANSLATE_BRANCH is not set"
+#endif
+#ifndef PPTRANSLATE_SOURCE
+#error "PPTRANSLATE_SOURCE is not set"
+#endif
+
+psString ppTranslateVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PPTRANSLATE_BRANCH, PPTRANSLATE_VERSION);
+    return value;
+}
+
+psString ppTranslateSource(void)
+{
+    return psStringCopy(PPTRANSLATE_SOURCE);
+}
+
+psString ppTranslateVersionLong(void)
+{
+    psString version = ppTranslateVersion();  // Version, to return
+    psString source = ppTranslateSource();    // Source
+
+    psStringPrepend(&version, "ppTranslate ");
+    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 ppTranslateVersionHeader(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, "ppTranslate at %s", timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+
+    psString version = ppTranslateVersion(); // Software version
+    psString source  = ppTranslateSource();  // Software source
+
+    psStringPrepend(&version, "ppTranslate version: ");
+    psStringPrepend(&source, "ppTranslate 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 ppTranslateVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("ppTranslate", PS_LOG_INFO, "ppTranslate at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString ppTranslate = ppTranslateVersionLong(); // ppTranslate version
+
+    psLogMsg("ppTranslate", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("ppTranslate", PS_LOG_INFO, "%s", ppTranslate);
+
+    psFree(pslib);
+    psFree(ppTranslate);
+
+    return;
+}
Index: trunk/ppTranslate/src/ppTranslateVersion.h
===================================================================
--- trunk/ppTranslate/src/ppTranslateVersion.h	(revision 25923)
+++ trunk/ppTranslate/src/ppTranslateVersion.h	(revision 25923)
@@ -0,0 +1,21 @@
+#ifndef PP_TRANSLATE_VERSION
+#define PP_TRANSLATE_VERSION
+
+#include <pslib.h>
+
+/// Return version
+psString ppTranslateVersion(void);
+
+/// Return source
+psString ppTranslateSource(void);
+
+/// Return detailed version information
+psString ppTranslateVersionLong(void);
+
+/// Put version into header
+bool ppTranslateVersionHeader(psMetadata *header);
+
+/// Print version information
+void ppTranslateVersionPrint(void);
+
+#endif
Index: trunk/ppTranslate/src/ppTranslateVersionDefinitions.h.in
===================================================================
--- trunk/ppTranslate/src/ppTranslateVersionDefinitions.h.in	(revision 25923)
+++ trunk/ppTranslate/src/ppTranslateVersionDefinitions.h.in	(revision 25923)
@@ -0,0 +1,8 @@
+#ifndef PPTRANSLATE_VERSION_DEFINITIONS_H
+#define PPTRANSLATE_VERSION_DEFINITIONS_H
+
+#define PPTRANSLATE_VERSION @PPTRANSLATE_VERSION@ // SVN version
+#define PPTRANSLATE_BRANCH  @PPTRANSLATE_BRANCH@  // SVN branch
+#define PPTRANSLATE_SOURCE  @PPTRANSLATE_SOURCE@  // SVN source
+
+#endif
