Index: /tags/rel-0_0_2/archive/scripts/src/phase2/.cvsignore
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/.cvsignore	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/.cvsignore	(revision 7503)
@@ -0,0 +1,16 @@
+Makefile.in
+aclocal.m4
+autom4te.cache
+configure
+Makefile
+config.log
+config.status
+compile
+config.guess
+config.sub
+depcomp
+install-sh
+ltmain.sh
+missing
+stamp-h1
+.deps
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/Makefile.am
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/Makefile.am	(revision 7503)
@@ -0,0 +1,52 @@
+bin_PROGRAMS = phase2
+
+phase2_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(phase2_CFLAGS)
+phase2_LDADD = $(PSLIB_LIBS) $(PSMODULE_LIBS)
+phase2_SOURCES = \
+	papPhase2.c
+#	papStuff.c \
+#	pmChipMosaic.c \
+#	pmConfig.c \
+#	pmFPA.c \
+#	pmFPAConceptsGet.c \
+#	pmFPAConceptsSet.c \
+#	pmFPAConstruct.c \
+#	pmFPARead.c \
+#	pmFPAWrite.c \
+#	pmFlatField.c \
+#	pmMaskBadPixels.c \
+#	pmNonLinear.c \
+#	pmSubtractBias.c \
+#	psAdditionals.c
+
+noinst_HEADERS =
+#	papStuff.h \
+#	pmChipMosaic.h \
+#	pmConfig.h \
+#	pmFPA.h \
+#	pmFPAConceptsGet.h \
+#	pmFPAConceptsSet.h \
+#	pmFPAConstruct.h \
+#	pmFPARead.h \
+#	pmFPAWrite.h \
+#	pmFlatField.h \
+#	pmFlatFieldErrors.h \
+#	pmMaskBadPixels.h \
+#	pmMaskBadPixelsErrors.h \
+#	pmNonLinear.h \
+#	pmSubtractBias.h \
+#	psAdditionals.h
+
+EXTRA_DIST = \
+	autogen.sh \
+	gpc1_raw.config \
+	ipprc.config \
+	megacam_raw.config \
+	phase2.config \
+	nonlin.dat
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/autogen.sh
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/autogen.sh	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/autogen.sh	(revision 7503)
@@ -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=phase2
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+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: /tags/rel-0_0_2/archive/scripts/src/phase2/configure.ac
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/configure.ac	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/configure.ac	(revision 7503)
@@ -0,0 +1,28 @@
+AC_PREREQ(2.59)
+
+AC_INIT([phase2], [0.0.2], [price@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([phase2.config])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+dnl AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+dnl AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0]) 
+PKG_CHECK_MODULES([PSMODULE], [psmodule >= 0.0.0]) 
+
+CFLAGS="${CFLAGS} -DTESTING"
+dnl is this the best was to setup recursive CFLAGS?
+dnl phase2_CFLAGS="-Werror -std=c99 "
+phase2_CFLAGS="-std=c99 "
+AC_SUBST([phase2_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+])
+AC_OUTPUT
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/gpc1_raw.config
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/gpc1_raw.config	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/gpc1_raw.config	(revision 7503)
@@ -0,0 +1,172 @@
+# The raw GPC data comes off the telescope with each of the chips stored in separate files
+
+# How to identify this type
+RULE	METADATA
+#	TELESCOP	STR	PS1
+#	DETECTOR	STR	GPC1
+	EXTEND		BOOL	T
+	NEXTEND		S32	64
+	NAMPS		S32	64
+END
+
+# How to read this data
+PHU		STR	CHIP	# The FITS file represents a single chip
+EXTENSIONS	STR	CELL	# The extensions represent cells
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, type
+	xy00	STR	pitch10u
+	xy01	STR	pitch10u
+	xy02	STR	pitch10u
+	xy03	STR	pitch10u
+	xy04	STR	pitch10u
+	xy05	STR	pitch10u
+	xy06	STR	pitch10u
+	xy07	STR	pitch10u
+	xy10	STR	pitch10u
+	xy11	STR	pitch10u
+	xy12	STR	pitch10u
+	xy13	STR	pitch10u
+	xy14	STR	pitch10u
+	xy15	STR	pitch10u
+	xy16	STR	pitch10u
+	xy17	STR	pitch10u
+	xy20	STR	pitch10u
+	xy21	STR	pitch10u
+	xy22	STR	pitch10u
+	xy23	STR	pitch10u
+	xy24	STR	pitch10u
+	xy25	STR	pitch10u
+	xy26	STR	pitch10u
+	xy27	STR	pitch10u
+	xy30	STR	pitch10u
+	xy31	STR	pitch10u
+	xy32	STR	pitch10u
+	xy33	STR	pitch10u
+	xy34	STR	pitch10u
+	xy35	STR	pitch10u
+	xy36	STR	pitch10u
+	xy37	STR	pitch10u
+	xy40	STR	pitch10u
+	xy41	STR	pitch10u
+	xy42	STR	pitch10u
+	xy43	STR	pitch10u
+	xy44	STR	pitch10u
+	xy45	STR	pitch10u
+	xy46	STR	pitch10u
+	xy47	STR	pitch10u
+	xy50	STR	pitch10u
+	xy51	STR	pitch10u
+	xy52	STR	pitch10u
+	xy53	STR	pitch10u
+	xy54	STR	pitch10u
+	xy55	STR	pitch10u
+	xy56	STR	pitch10u
+	xy57	STR	pitch10u
+	xy60	STR	pitch10u
+	xy61	STR	pitch10u
+	xy62	STR	pitch10u
+	xy63	STR	pitch10u
+	xy64	STR	pitch10u
+	xy65	STR	pitch10u
+	xy66	STR	pitch10u
+	xy67	STR	pitch10u
+	xy70	STR	pitch10u
+	xy71	STR	pitch10u
+	xy72	STR	pitch10u
+	xy73	STR	pitch10u
+	xy74	STR	pitch10u
+	xy75	STR	pitch10u
+	xy76	STR	pitch10u
+	xy77	STR	pitch10u
+END
+
+# Specify the cell data
+CELLS	METADATA
+	pitch10u	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[575:606,1:594]
+		CELL.TRIMSEC		STR	[1:574,1:594]
+	#	CELL.BIASSEC		STR	BIASSEC
+	#	CELL.TRIMSEC		STR	DATASEC
+	END
+
+	# This is just in here for fun
+	pitch12u	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[1:10,1:512];[523:574,1:512]
+		CELL.TRIMSEC		STR	[11:522,1:512]
+	#	CELL.BIASSEC		STR	BIASSEC
+	#	CELL.TRIMSEC		STR	TRIMSEC
+	END
+END
+
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	CELL.XBIN	STR	CCDSUM
+	CELL.YBIN	STR	CCDSUM
+	CELL.X0		STR	IMNPIX1
+	CELL.Y0		STR	IMNPIX2
+	CELL.XPARITY	STR	LTM1_1
+	CELL.YPARITY	STR	LTM2_2
+	CELL.SATURATION	STR	SATURATE
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.AIRMASS	F32	0.0
+	FPA.FILTER	STR	NONE
+	FPA.POSANGLE	F32	0.0
+	FPA.RA		STR	0:0:0
+	FPA.DEC		STR	0:0:0
+	FPA.RADECSYS	STR	ICRS
+	FPA.NAME	S32	12345
+	CELL.EXPOSURE	F32	0.0
+	CELL.DARKTIME	F32	0.0
+	CELL.GAIN	F32	1.0
+	CELL.READNOISE	F32	0.0
+	CELL.READDIR	S32	2
+	CELL.BAD	S32	0
+	CELL.TIMESYS	STR	UTC
+	CELL.TIME	STR	2005-11-23T12:34:56.78
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP,CELL
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP,CELL
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	CELL.TIME	STR	ISO
+	CELL.BINNING	STR	TOGETHER
+	CELL.X0		STR	FORTRAN
+	CELl.Y0		STR	FORTRAN
+END
+ 
+# Recipe options
+RECIPES		METADATA
+	PHASE2		STR	phase2.config		# Phase 2 recipe details
+END
+ 
+# How to get the supplementary stuff: mask and weight
+SUPPLEMENTARY	METADATA
+	MASK.SOURCE	STR	FILE		# Source type for mask: EXT | FILE
+	MASK.NAME	STR	%a_mask.fits	# Name for mask extension or filename
+	WEIGHT.SOURCE	STR	FILE		# Source type for weight: EXT | FILE
+	WEIGHT.NAME	STR	%a_weight.fits	# Name for weight extension or filename
+END
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/ipprc.config
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/ipprc.config	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/ipprc.config	(revision 7503)
@@ -0,0 +1,35 @@
+### Example .ipprc file
+
+### Database configuration
+DBSERVER	STR	ippdb.ifa.hawaii.edu	# Database host name (for psDBInit)
+DBUSER		STR	ipp			# Database user name (for psDBInit)
+DBPASSWORD	STR	password		# Database password (for psDBInit)
+
+### Setups for each camera system
+CAMERAS		METADATA
+	MEGACAM_RAW	STR	megacam_raw.config
+	GPC1_RAW	STR	gpc1_raw.config
+	MEGACAM_SPLICE	STR	megacam_splice.config
+#	LRIS_BLUE	STR	lris_blue.config
+#	LRIS_RED	STR	lris_red.config
+END
+
+### psLib setup
+#TIME		STR	/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/etc/pslib/psTime.config	# Time configuration file
+LOGLEVEL	S32	3			# Logging level; 3=INFO
+LOGFORMAT	STR	THLNM			# Log format
+LOGDEST	STR	STDOUT				# Log destination
+TRACE		METADATA			# Trace levels
+	pap			S32	10
+	pmFPAPrint		S32	10
+	pmFPAWrite		S32	10
+#	pmConfigRead		S32	10
+#	pmFPAWriteMask		S32	10
+#	pmFPAWriteWeight	S32	10
+	pmChipMosaic		S32	10
+	pmFPAMorph		S32	10
+	spliceCells		S32	10
+	spliceImage		S32	10
+	setConceptItem		S32	10
+#	pap_psMetadataCopy	S32	9
+END
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/megacam_raw.config
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/megacam_raw.config	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/megacam_raw.config	(revision 7503)
@@ -0,0 +1,175 @@
+# The raw MegaCam data comes off the telescope with each of the chips stored in extensions of a MEF file.
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+	EXTEND		BOOL	T
+	NEXTEND		S32	72
+END
+
+# How to read this data
+PHU		STR	FPA	# The FITS file represents an entire FPA
+EXTENSIONS	STR	CELL	# The extensions represent cells
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+	amp00	STR	ccd00:left
+	amp01	STR	ccd00:right
+	amp02	STR	ccd01:left
+	amp03	STR	ccd01:right
+	amp04	STR	ccd02:left
+	amp05	STR	ccd02:right
+	amp06	STR	ccd03:left
+	amp07	STR	ccd03:right
+	amp08	STR	ccd04:left
+	amp09	STR	ccd04:right
+	amp10	STR	ccd05:left
+	amp11	STR	ccd05:right
+	amp12	STR	ccd06:left
+	amp13	STR	ccd06:right
+	amp14	STR	ccd07:left
+	amp15	STR	ccd07:right
+	amp16	STR	ccd08:left
+	amp17	STR	ccd08:right
+	amp18	STR	ccd09:left
+	amp19	STR	ccd09:right
+	amp20	STR	ccd10:left
+	amp21	STR	ccd10:right
+	amp22	STR	ccd11:left
+	amp23	STR	ccd11:right
+	amp24	STR	ccd12:left
+	amp25	STR	ccd12:right
+	amp26	STR	ccd13:left
+	amp27	STR	ccd13:right
+	amp28	STR	ccd14:left
+	amp29	STR	ccd14:right
+	amp30	STR	ccd15:left
+	amp31	STR	ccd15:right
+	amp32	STR	ccd16:left
+	amp33	STR	ccd16:right
+	amp34	STR	ccd17:left
+	amp35	STR	ccd17:right
+	amp36	STR	ccd18:left
+	amp37	STR	ccd18:right
+	amp38	STR	ccd19:left
+	amp39	STR	ccd19:right
+	amp40	STR	ccd20:left
+	amp41	STR	ccd20:right
+	amp42	STR	ccd21:left
+	amp43	STR	ccd21:right
+	amp44	STR	ccd22:left
+	amp45	STR	ccd22:right
+	amp46	STR	ccd23:left
+	amp47	STR	ccd23:right
+	amp48	STR	ccd24:left
+	amp49	STR	ccd24:right
+	amp50	STR	ccd25:left
+	amp51	STR	ccd25:right
+	amp52	STR	ccd26:left
+	amp53	STR	ccd26:right
+	amp54	STR	ccd27:left
+	amp55	STR	ccd27:right
+	amp56	STR	ccd28:left
+	amp57	STR	ccd28:right
+	amp58	STR	ccd29:left
+	amp59	STR	ccd29:right
+	amp60	STR	ccd30:left
+	amp61	STR	ccd30:right
+	amp62	STR	ccd31:left
+	amp63	STR	ccd31:right
+	amp64	STR	ccd32:left
+	amp65	STR	ccd32:right
+	amp66	STR	ccd33:left
+	amp67	STR	ccd33:right
+	amp68	STR	ccd34:left
+	amp69	STR	ccd34:right
+	amp70	STR	ccd35:left
+	amp71	STR	ccd35:right
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA	# Left amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	1 # We could have specified this as a DEFAULT, but this works
+		CELL.X0			S32	1
+		CELL.Y0			S32	1
+	END
+	right	METADATA	# Right amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	-1 # This cell is read out in the opposite direction
+		CELL.X0			S32	2048
+		CELL.Y0			S32	1
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.NAME		STR	EXPNUM
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTER		STR	FILTER
+	FPA.POSANGLE		STR	ROTANGLE
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	FPA.RADECSYS		STR	RADECSYS
+	CELL.EXPOSURE		STR	EXPTIME
+	CELL.DARKTIME		STR	DARKTIME
+	CELL.GAIN		STR	GAIN
+	CELL.READNOISE		STR	RDNOISE
+	CELL.SATURATION		STR	SATURATE
+	CELL.TIME		STR	MJD-OBS
+	CELL.XBIN		STR	CCDBIN1
+	CELL.YBIN		STR	CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.READDIR		S32	1		# Cell is read in x direction
+	CELL.BAD		S32	0
+	CELL.TIMESYS		STR	UTC
+	CELL.YPARITY		S32	1
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	CELL.TIME	STR	MJD
+#	CELL.BINNING	STR	SEPARATE
+	CELL.X0		STR	FORTRAN
+	CELL.Y0		STR	FORTRAN
+END
+
+# Recipe options
+RECIPES		METADATA
+	PHASE2		STR	phase2.config		# Phase 2 recipe details
+END
+
+
+# How to get the supplementary stuff: mask and weight
+SUPPLEMENTARY	METADATA
+	MASK.SOURCE	STR	FILE		# Source type for mask: EXT | FILE
+	MASK.NAME	STR	%a_mask.fits	# Name for mask extension or filename
+	WEIGHT.SOURCE	STR	FILE		# Source type for weight: EXT | FILE
+	WEIGHT.NAME	STR	%a_weight.fits	# Name for weight extension or filename
+END
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/nonlin.dat
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/nonlin.dat	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/nonlin.dat	(revision 7503)
@@ -0,0 +1,106 @@
+# Non-linearity correction lookup table
+# Col 1: Input value
+# Col 2: Corrected value
+0	0
+100	1
+200	2
+300	3
+400	4
+500	5
+600	6
+700	7
+800	8
+900	9
+1000	10
+1100	11
+1200	12
+1300	13
+1400	14
+1500	15
+1600	16
+1700	17
+1800	18
+1900	19
+2000	20
+2100	21
+2200	22
+2300	23
+2400	24
+2500	25
+2600	26
+2700	27
+2800	28
+2900	29
+3000	30
+3100	31
+3200	32
+3300	33
+3400	34
+3500	35
+3600	36
+3700	37
+3800	38
+3900	39
+4000	40
+4100	41
+4200	42
+4300	43
+4400	44
+4500	45
+4600	46
+4700	47
+4800	48
+4900	49
+5000	50
+5100	51
+5200	52
+5300	53
+5400	54
+5500	55
+5600	56
+5700	57
+5800	58
+5900	59
+6000	60
+6100	61
+6200	62
+6300	63
+6400	64
+6500	65
+6600	66
+6700	67
+6800	68
+6900	69
+7000	70
+7100	71
+7200	72
+7300	73
+7400	74
+7500	75
+7600	76
+7700	77
+7800	78
+7900	79
+8000	80
+8100	81
+8200	82
+8300	83
+8400	84
+8500	85
+8600	86
+8700	87
+8800	88
+8900	89
+9000	90
+9100	91
+9200	92
+9300	93
+9400	94
+9500	95
+9600	96
+9700	97
+9800	98
+9900	99
+10000	100
+10001	100
+1e6	100
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/papPhase2.c
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/papPhase2.c	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/papPhase2.c	(revision 7503)
@@ -0,0 +1,858 @@
+#include <stdio.h>
+#include <strings.h>
+
+#include "pslib.h"
+
+#include "psAdditionals.h"
+
+#include "pmAstrometry.h"
+#include "pmReadout.h"
+#include "pmConfig.h"
+#include "pmFPAConstruct.h"
+#include "pmFPARead.h"
+#include "pmFPAConceptsGet.h"
+#include "pmFPAWrite.h"
+
+#include "pmFlatField.h"
+#include "pmMaskBadPixels.h"
+#include "pmNonLinear.h"
+#include "pmSubtractBias.h"
+#include "pmChipMosaic.h"
+//#include "pmFPAMorph.h"
+
+// Phase 2 needs to:
+// * Read configurations
+// * Read in the images
+// * Flag bad pixels
+// * Non-linearity correction
+// * Bias/dark subtraction (MHPCC)
+// * Flat-field (MHPCC)
+// * Fringe subtraction (Not for now)
+// * Source identification and photometry (IPP prototype)
+// * Astrometry (IPP prototype soon)
+// * Write calibrated image
+// * Write source catalogue, astrometry, etc.
+
+// Currently neglecting different input types (F32, S32, U16, etc)
+
+// Would like to fold in Nebulous at some stage as well.
+
+// Currently neglect to convolve the reference frames with the orthogonal transfer kernel
+
+// phase2 INPUT.fits OUTPUT.fits -bias BIAS.fits -dark DARK.fits -flat FLAT.fits -chip CHIP
+//
+// Most are self-explanatory.  "-chip" says "only work on this particular chip".
+
+
+#define RECIPE "PHASE2"                 // Name of the recipe to use
+
+static psMemId memPrintAlloc(const psMemBlock *mb)
+{
+    printf("Allocated memory block %ld (%ld):\n"
+           "\tFile %s, line %d, size %d\n"
+           "\tPosts: %x %x %x\n",
+           mb->id, mb->refCounter, mb->file, mb->lineno, mb->userMemorySize, mb->startblock, mb->endblock,
+           *(void**)((int8_t *)(mb + 1) + mb->userMemorySize));
+    return 0;
+}
+static psMemId memPrintFree(const psMemBlock *mb)
+{
+    printf("Freed memory block %ld (%ld):\n"
+           "\tFile %s, line %d, size %d\n"
+           "\tPosts: %x %x %x\n",
+           mb->id, mb->refCounter, mb->file, mb->lineno, mb->userMemorySize, mb->startblock, mb->endblock,
+           *(void**)((int8_t *)(mb + 1) + mb->userMemorySize));
+    return 0;
+}
+static void memPrint(const psPtr ptr)
+{
+    psMemBlock *mb = ((psMemBlock*)ptr) - 1;
+    printf("Memory block %ld (%ld):\n"
+           "\tFile %s, line %d, size %d\n"
+           "\tPosts: %x %x %x\n",
+           mb->id, mb->refCounter, mb->file, mb->lineno, mb->userMemorySize, mb->startblock, mb->endblock,
+           *(void**)((int8_t *)(mb + 1) + mb->userMemorySize));
+}
+
+int main(int argc, char *argv[])
+{
+#if 0
+    // Hunting memory leaks
+    psMemAllocCallbackSetID(22367);
+    psMemFreeCallbackSetID(22367);
+    psMemAllocCallbackSet(memPrintAlloc);
+    psMemFreeCallbackSet(memPrintFree);
+#endif
+
+    psTimerStart("phase2");
+
+    // Parse the configurations
+    psMetadata *site = NULL;            // Site configuration
+    psMetadata *camera = NULL;          // Camera configuration
+    psMetadata *recipe = NULL;          // Recipe configuration
+    if (! pmConfigRead(&site, &camera, &recipe, &argc, argv, RECIPE)) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // Parse other command-line arguments
+    psMetadata *arguments = psMetadataAlloc(); // The arguments, with default values
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-bias", 0, "Name of the bias image", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-dark", 0, "Name of the dark image", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-flat", 0, "Name of the flat-field image", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-mask", 0, "Name of the mask image", "");
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-chip", 0, "Chip number to process (if positive)", -1);
+    if (! psArgumentParse(arguments, &argc, argv) || argc != 3) {
+        printf("\nPan-STARRS Phase 2 processing\n\n");
+        printf("Usage: %s INPUT.fits OUTPUT.fits\n\n", argv[0]);
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        exit(EXIT_FAILURE);
+    }
+    const char *inputName = argv[1];    // Name of input image
+    const char *outputName = argv[2];   // Name of output image
+    const char *biasName = psMetadataLookupStr(NULL, arguments, "-bias"); // Name of bias image
+    const char *darkName = psMetadataLookupStr(NULL, arguments, "-dark"); // Name of dark image
+    const char *flatName = psMetadataLookupStr(NULL, arguments, "-flat"); // Name of flat-field image
+    const char *maskName = psMetadataLookupStr(NULL, arguments, "-mask"); // Name of mask image
+    const int chipNum = psMetadataLookupS32(NULL, arguments, "-chip"); // Chip number to work on
+    printf("Input: %s\nOutput: %s\n", inputName, outputName);
+    printf("Bias: %s\n", biasName);
+    printf("Dark: %s\n", darkName);
+    printf("Flat: %s\n", flatName);
+    printf("Mask: %s\n", maskName);
+    printf("Chip: %d\n", chipNum);
+
+    // Open the input
+    psFits *inputFile = psFitsOpen(inputName, "r"); // File handle for FITS file
+    if (! inputFile) {
+        psErrorStackPrint(stderr, "Can't open input image: %s\n", inputName);
+        exit(EXIT_FAILURE);
+    }
+    psMetadata *header = psFitsReadHeader(NULL, inputFile); // FITS header
+#if PRODUCTION
+    psDB *database = pmConfigDB(site);  // Database handle
+#else
+    psDB *database = NULL;              // Database handle
+#endif
+
+    // Open the output and output mask
+    // We do it here so that we don't process the whole lot and then find out we can't open the output file
+    psFits *outputFile = psFitsOpen(outputName, "w");
+    if (! outputFile) {
+        psErrorStackPrint(stderr, "Can't open output image: %s\n", outputName);
+        exit(EXIT_FAILURE);
+    }
+
+    // Get camera configuration from header if not already defined
+    if (! camera) {
+        camera = pmConfigCameraFromHeader(site, header);
+        if (! camera) {
+            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+            exit(EXIT_FAILURE);
+        }
+   } else if (! pmConfigValidateCamera(camera, header)) {
+        psError(PS_ERR_IO, true, "%s does not seem to be from the camera.\n", inputName);
+        exit(EXIT_FAILURE);
+    }
+    if (! recipe && !(recipe = pmConfigRecipeFromCamera(camera, RECIPE))) {
+        psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // Construct camera in preparation for reading
+    pmFPA *input = pmFPAConstruct(camera);
+    pmFPA *mask = pmFPAConstruct(camera);
+    pmFPA *bias = pmFPAConstruct(camera);
+    pmFPA *dark = pmFPAConstruct(camera);
+    pmFPA *flat = pmFPAConstruct(camera);
+
+    // Set various tasks
+    bool doMask = false;                // Mask bad pixles
+    bool doNonLin = false;              // Non-linearity correction
+    bool doBias = false;                // Bias subtraction
+    bool doDark = false;                // Dark subtraction
+    bool doOverscan = false;            // Overscan subtraction
+    bool doFlat = false;                // Flat-field normalisation
+    bool doFringe = false;              // Fringe subtraction
+    bool doSource = false;              // Source identification and photometry
+    bool doAstrom = false;              // Astrometry
+    pmOverscanAxis overscanMode = PM_OVERSCAN_NONE; // Axis for overscan
+    pmFit overscanFitType = PM_FIT_NONE; // Fit type for overscan
+    int overscanBins = 1;               // Number of pixels per bin for overscan
+    psStats *overscanStats = NULL;      // Statistics for overscan
+    void *overscanFit = NULL;           // Overscan fit (polynomial or spline)
+
+    if (psMetadataLookupBool(NULL, recipe, "MASK")) {
+        if (strlen(maskName) > 0) {
+            doMask = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Masking is desired, but no mask was supplied --- no masking "
+                     "will be performed.\n");
+        }
+    }
+    if (psMetadataLookupBool(NULL, recipe, "NONLIN")) {
+        doNonLin = true;
+    }
+    if (psMetadataLookupBool(NULL, recipe, "BIAS")) {
+        if (strlen(biasName) > 0) {
+            doBias = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Bias subtraction is desired, but no bias was supplied --- "
+                     "no bias subtraction will be performed.\n");
+        }
+    }
+    if (psMetadataLookupBool(NULL, recipe, "DARK")) {
+        if (strlen(darkName) > 0) {
+            doDark = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Dark subtraction is desired, but no dark was supplied --- "
+                     "no dark subtraction will be performed.\n");
+        }
+    }
+    if (psMetadataLookupBool(NULL, recipe, "OVERSCAN")) {
+        doOverscan = true;
+        psString mode = psMetadataLookupStr(NULL, recipe, "OVERSCAN.MODE");
+        if (strcasecmp(mode, "INDIVIDUAL") == 0) {
+            overscanMode = PM_OVERSCAN_ROWS;
+            // By "ROWS", I mean either rows or columns --- will check the READDIR for each chip
+        } else if (strcasecmp(mode, "ALL") == 0) {
+            overscanMode = PM_OVERSCAN_ALL;
+        } else if (strcasecmp(mode, "NONE") != 0) {
+            psLogMsg("phase2", PS_LOG_WARN, "OVERSCAN.MODE (%s) is not one of NONE, INDIVIDUAL, or ALL:"
+                     " assuming NONE.\n", mode);
+        }
+        psString fit = psMetadataLookupStr(NULL, recipe, "OVERSCAN.FIT");
+        if (strcasecmp(fit, "POLYNOMIAL") == 0) {
+            overscanFitType = PM_FIT_POLYNOMIAL;
+            int order = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER"); // Order of polynomial fit
+            overscanFit = psPolynomial1DAlloc(order, PS_POLYNOMIAL_ORD);
+        } else if (strcasecmp(fit, "SPLINE") == 0) {
+            overscanFitType = PM_FIT_SPLINE;
+            int order = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER"); // Order of polynomial fit
+            overscanFit = NULL;
+//          overscanFit = psSpline1DAlloc();
+        } else if (strcasecmp(fit, "NONE") != 0) {
+            psLogMsg("phase2", PS_LOG_WARN, "OVERSCAN.FIT (%s) is not one of NONE, POLYNOMIAL, or SPLINE:"
+                     " assuming NONE.\n", fit);
+        }
+        overscanBins = psMetadataLookupS32(NULL, recipe, "OVERSCAN.BIN");
+        if (overscanBins <= 0) {
+            psErrorStackPrint(stderr, "OVERSCAN.BIN (%d) is non-positive --- assuming 1.\n", overscanBins);
+            overscanBins = 1;
+        }
+        psString stat = psMetadataLookupStr(NULL, recipe, "OVERSCAN.STAT");
+        if (strcasecmp(stat, "MEAN")) {
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        } else if (strcasecmp(stat, "MEDIAN")) {
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+        } else {
+            psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) is not one of MEAN, MEDIAN: assuming MEAN\n", stat);
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        }
+    }
+
+    if (psMetadataLookupBool(NULL, recipe, "FLAT")) {
+        if (strlen(flatName) > 0) {
+            doFlat = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Flat-fielding is desired, but no flat was supplied --- "
+                     "no flat-fielding will be performed.\n");
+        }
+    }
+
+    // Chip selection
+    if (chipNum >= 0) {
+        if (! pmFPASelectChip(input, chipNum) || ! pmFPASelectChip(bias, chipNum) ||
+            ! pmFPASelectChip(dark, chipNum) || ! pmFPASelectChip(flat, chipNum) ||
+            ! pmFPASelectChip(mask, chipNum)) {
+            psErrorStackPrint(stderr, "Chip number %d doesn't exist in camera.\n", chipNum);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("phase2", PS_LOG_INFO, "Operating only on chip %d\n", chipNum);
+    }
+
+    psLogMsg("phase2", PS_LOG_INFO, "Setup completed after %f sec.\n", psTimerMark("phase2"));
+
+    // Read in the input pixels
+    psLogMsg("phase2", PS_LOG_INFO, "Opening input image: %s\n", inputName);
+    if (! pmFPARead(input, inputFile, header, database)) {
+        psErrorStackPrint(stderr, "Unable to populate camera from FITS file: %s\n", inputName);
+        exit(EXIT_FAILURE);
+    }
+    psFitsClose(inputFile);
+
+#if 0
+    pmFPAReadMask(input, inputFile);
+    pmFPAReadWeight(input, inputFile);
+//#else
+    {
+        // Generate mask and weight frame
+        p_pmHDU *hdu = NULL;
+        if (input->hdu) {
+            hdu = input->hdu;
+        }
+        psArray *chips = input->chips;
+        for (int chipNum = 0; chipNum < chips->n; chipNum++) {
+            pmChip *chip = chips->data[chipNum];
+            if (chip->valid) {
+                if (chip->hdu) {
+                    hdu = chip->hdu;
+                }
+                psArray *cells = chip->cells;
+                for (int cellNum = 0; cellNum < cells->n; cellNum++) {
+                    pmCell *cell = cells->data[cellNum];
+                    if (cell->valid) {
+                        if (cell->hdu) {
+                            hdu = cell->hdu;
+                        }
+
+                        hdu->masks = psArrayAlloc(hdu->images->n);
+                        hdu->weights = psArrayAlloc(hdu->images->n);
+                        psArray *readouts = cell->readouts;
+                        for (int readNum = 0; readNum < hdu->images->n; readNum++) {
+                            psImage *image = hdu->images->data[readNum];
+                            psImage *mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+                            psImage *weight = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+                            pmReadout *readout = readouts->data[readNum];
+                            for (int j = 0; j < image->numRows; j++) {
+                                for (int i = 0; i < image->numCols; i++) {
+                                    mask->data.U8[j][i] = j + i;
+                                    weight->data.F32[j][i] = sqrtf(image->data.F32[j][i]);
+                                }
+                            }
+                            hdu->masks->data[readNum] = mask;
+                            hdu->weights->data[readNum] = weight;
+                            readout->mask = psMemIncrRefCounter(mask);
+                            readout->weight = psMemIncrRefCounter(weight);
+                        }
+                    }
+                }
+            }
+        }
+    }
+#endif
+
+#if 0
+    pmFPAPrint(input);
+#endif
+
+    // Load the calibration frames, if required
+    if (doBias) {
+        psFits *biasFile = psFitsOpen(biasName, "r"); // File handle for bias
+        psMetadata *biasHeader = psFitsReadHeader(NULL, biasFile); // FITS header for bias
+        if (! pmConfigValidateCamera(camera, biasHeader)) {
+            psError(PS_ERR_IO, true, "Bias (%s) does not seem to be from the same camera.\n", biasName);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("phase2", PS_LOG_INFO, "Opening bias image: %s\n", biasName);
+        if (! pmFPARead(bias, biasFile, biasHeader, database)) {
+            psErrorStackPrint(stderr, "Unable to populate bias camera from fits FITS: %s\n", biasName);
+            exit(EXIT_FAILURE);
+        }
+        psFree(biasHeader);
+        psFitsClose(biasFile);
+    }
+
+    if (doDark) {
+        psFits *darkFile = psFitsOpen(darkName, "r"); // File handle for dark
+        psMetadata *darkHeader = psFitsReadHeader(NULL, darkFile); // FITS header for dark
+        if (! pmConfigValidateCamera(camera, darkHeader)) {
+            psError(PS_ERR_IO, true, "Dark (%s) does not seem to be from the same camera.\n", darkName);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("phase2", PS_LOG_INFO, "Opening dark image: %s\n", darkName);
+        if (! pmFPARead(dark, darkFile, darkHeader, database)) {
+            psErrorStackPrint(stderr, "Unable to populate dark camera from fits FITS: %s\n", darkName);
+            exit(EXIT_FAILURE);
+        }
+        psFree(darkHeader);
+        psFitsClose(darkFile);
+    }
+
+    if (doMask) {
+        psFits *maskFile = psFitsOpen(maskName, "r"); // File handle for mask
+        psMetadata *maskHeader = psFitsReadHeader(NULL, maskFile); // FITS header for mask
+        if (! pmConfigValidateCamera(camera, maskHeader)) {
+            psError(PS_ERR_IO, true, "Mask (%s) does not seem to be from the same camera.\n", maskName);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("phase2", PS_LOG_INFO, "Opening mask image: %s\n", maskName);
+        if (! pmFPARead(mask, maskFile, maskHeader, database)) {
+            psErrorStackPrint(stderr, "Unable to populate mask camera from fits FITS: %s\n", maskName);
+            exit(EXIT_FAILURE);
+        }
+        psFree(maskHeader);
+        psFitsClose(maskFile);
+    }
+
+    if (doFlat) {
+        psFits *flatFile = psFitsOpen(flatName, "r"); // File handle for flat
+        if (! flatFile) {
+            psErrorStackPrint(stderr, "Can't open flat image: %s\n", flatName);
+            exit(EXIT_FAILURE);
+        }
+        psMetadata *flatHeader = psFitsReadHeader(NULL, flatFile); // FITS header for flat
+        if (! pmConfigValidateCamera(camera, flatHeader)) {
+            psError(PS_ERR_IO, true, "Flat (%s) does not seem to be from the same camera.\n", flatName);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("phase2", PS_LOG_INFO, "Opening flat image: %s\n", flatName);
+        if (! pmFPARead(flat, flatFile, flatHeader, database)) {
+            psErrorStackPrint(stderr, "Unable to populate flat camera from fits FITS: %s\n", flatName);
+            exit(EXIT_FAILURE);
+        }
+        psFree(flatHeader);
+        psFitsClose(flatFile);
+    }
+
+    psLogMsg("phase2", PS_LOG_INFO, "Input completed after %f sec.\n", psTimerMark("phase2"));
+
+    psArray *inputChips = input->chips; // Array of chips in input image
+    psArray *biasChips = bias->chips;   // Array of chips in bias image
+    psArray *darkChips = dark->chips;   // Array of chips in dark image
+    psArray *maskChips = mask->chips;   // Array of chips in mask image
+    psArray *flatChips = flat->chips;   // Array of chips in flat image
+    for (int i = 0; i < inputChips->n; i++) {
+        pmChip *inputChip = inputChips->data[i]; // Chip of interest in input image
+        pmChip *biasChip = biasChips->data[i]; // Chip of interest in bias image
+        pmChip *darkChip = darkChips->data[i]; // Chip of interest in dark image
+        pmChip *maskChip = maskChips->data[i]; // Chip of interest in mask image
+        pmChip *flatChip = flatChips->data[i]; // Chip of interest in flat image
+
+        if (! inputChip->valid) {
+            continue;
+        }
+
+        psArray *inputCells = inputChip->cells; // Array of cells in input image
+        psArray *biasCells = biasChip->cells; // Array of cells in bias image
+        psArray *darkCells = darkChip->cells; // Array of cells in dark image
+        psArray *maskCells = maskChip->cells; // Array of cells in mask image
+        psArray *flatCells = flatChip->cells; // Array of cells in flat image
+
+        for (int j = 0; j < inputCells->n; j++) {
+            pmCell *inputCell = inputCells->data[j]; // Cell of interest in input image
+            pmCell *biasCell = biasCells->data[j]; // Cell of interest in bias image
+            pmCell *darkCell = darkCells->data[j]; // Cell of interest in dark imag
+            pmCell *maskCell = maskCells->data[j]; // Cell of interest in mask image
+            pmCell *flatCell = flatCells->data[j]; // Cell of interest in flat image
+
+            if (! inputCell->valid) {
+                continue;
+            }
+
+            psArray *inputReadouts = inputCell->readouts; // Array of readouts in input image
+            if (doBias && biasCell->readouts->n > 1) {
+                psLogMsg("phase2", PS_LOG_WARN, "Bias contains multiple readouts: only the first will be"
+                         " used.");
+            }
+            if (doDark && darkCell->readouts->n > 1) {
+                psLogMsg("phase2", PS_LOG_WARN, "Dark contains multiple readouts: only the first will be"
+                         " used.");
+            }
+            if (doMask && maskCell->readouts->n > 1) {
+                psLogMsg("phase2", PS_LOG_WARN, "Mask contains multiple readouts: only the first will be"
+                         " used.");
+            }
+            if (doFlat && flatCell->readouts->n > 1) {
+                psLogMsg("phase2", PS_LOG_WARN, "Flat contains multiple readouts: only the first will be"
+                         " used.");
+            }
+
+            pmReadout *biasReadout = NULL; // Bias readout
+            pmReadout *maskReadout = NULL; // Mask readout
+            pmReadout *flatReadout = NULL; // Flat readout
+            psImage *biasImage = NULL;  // Bias pixels
+            psImage *darkImage = NULL;  // Dark pixels
+            float darkTime = 1.0;       // Dark time for dark image
+            psImage *maskImage = NULL;  // Mask pixels
+
+            if (doBias) {
+                biasReadout = biasCell->readouts->data[0]; // Readout of interest in bias image
+                biasImage = biasReadout->image;
+            }
+            if (doDark) {
+                pmReadout *darkReadout = darkCell->readouts->data[0]; // Readout of interest in dark image
+                darkImage = darkReadout->image;
+                darkTime = psMetadataLookupF32(NULL, darkCell->concepts, "CELL.DARKTIME");
+                if (darkTime <= 0.0) {
+                    psErrorStackPrint(stderr, "DARKTIME for dark image (%f) is non-positive.\n", darkTime);
+                    exit(EXIT_FAILURE);
+                }
+            }
+            if (doMask) {
+                maskReadout = maskCell->readouts->data[0]; // Readout of interest in mask image
+                maskImage = maskReadout->image;
+            }
+            if (doFlat) {
+                flatReadout = flatCell->readouts->data[0]; // Readout of interest in mask image
+            }
+
+            for (int k = 0; k < inputReadouts->n; k++) {
+                pmReadout *inputReadout = inputReadouts->data[k]; // Readout of interest in input image
+                psImage *inputImage = inputReadout->image; // The actual image
+
+                // Mask bad pixels
+                if (doMask) {
+                    float saturation = psMetadataLookupF32(NULL, inputCell->concepts, "CELL.SATURATION");
+
+                    // Need to change this later to grow the mask by the size of the convolution kernel
+                    (void)pmMaskBadPixels(inputReadout, maskImage,
+                                          PM_MASK_TRAP | PM_MASK_BADCOL | PM_MASK_SAT, saturation,
+                                          PM_MASK_TRAP, 0);
+                }
+
+                // Non-linearity correction
+                if (doNonLin) {
+                    psMetadataItem *dataItem = psMetadataLookup(recipe, "NONLIN.DATA");
+                    if (! dataItem) {
+                        psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction desired, but unable to "
+                                 "find NONLIN.DATA in recipe --- ignored.\n");
+                    } else {
+                        switch (dataItem->type) {
+                          case PS_DATA_VECTOR:
+                            {
+                                // These are the polynomial coefficients
+                                psVector *coeff = dataItem->data.V; // The coefficient vector
+                                if (coeff->type.type != PS_TYPE_F64) {
+                                    psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64); // F64 version
+                                    coeff = temp;
+                                }
+                                psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1,
+                                                                                 PS_POLYNOMIAL_ORD);
+                                psFree(correction->coeff);
+                                correction->coeff = psMemIncrRefCounter(coeff->data.F64);
+                                (void)pmNonLinearityPolynomial(inputReadout, correction);
+                                psFree(coeff);
+                                psFree(correction);
+                            }
+                            break;
+                          case PS_DATA_STRING:
+                            {
+                                // This is a filename
+                                psString name = dataItem->data.V;       // Filename
+                                psLookupTable *table = psLookupTableAlloc(name, "%f %f", 0);
+                                if (psLookupTableRead(table) <= 0) {
+                                    psErrorStackPrint(stderr, "Unable to read non-linearity correction file "
+                                                      "%s --- ignored\n", name);
+                                } else {
+#ifdef PRODUCTION
+                                    (void)pmNonLinearityLookup(inputReadout, table);
+#else
+                                    psVector *influx = table->values->data[0];
+                                    psVector *outflux = table->values->data[1];
+                                    (void)pmNonLinearityLookup(inputReadout, table->values->data[0],
+                                                               table->values->data[1]);
+#endif
+                                }
+                                psFree(table);
+                            }
+                            break;
+                          case PS_DATA_METADATA:
+                            {
+                                // This is a menu
+                                psMetadata *options = dataItem->data.V; // Options with concept values as keys
+                                bool mdok = false; // Success of MD lookup
+                                psString concept = psMetadataLookupStr(&mdok, recipe, "NONLIN.SOURCE");
+                                if (! mdok || ! concept) {
+                                    psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction desired, but "
+                                             "unable to find NONLIN.SOURCE in recipe --- ignored.\n");
+                                } else {
+                                    psMetadataItem *conceptValueItem = pmCellGetConcept(inputCell, concept);
+                                    if (! conceptValueItem) {
+                                        psLogMsg("phase2", PS_LOG_WARN, "Unable to find value of concept %s "
+                                                 "for non-linearity correction --- ignored.\n", concept);
+                                    } else if (conceptValueItem->type != PS_DATA_STRING) {
+                                        psLogMsg("phase2", PS_LOG_WARN, "Type for concept %s isn't STRING, as"
+                                                 " expected for non-linearity correction --- ignored.\n",
+                                                 concept);
+                                    } else {
+                                        psString conceptValue = conceptValueItem->data.V;
+                                        // Get the value of the concept
+                                        psMetadataItem *optionItem = psMetadataLookup(options, conceptValue);
+                                        if (!optionItem) {
+                                            psLogMsg("phase2", PS_LOG_WARN, "Unable to find %s in NONLIN.DATA"
+                                                     " --- ignored.\n", conceptValue);
+                                        } else if (optionItem->type == PS_DATA_VECTOR) {
+                                            // These are the polynomial coefficients
+                                            psVector *coeff = optionItem->data.V; // The coefficient vector
+                                            if (coeff->type.type != PS_TYPE_F64) {
+                                                psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64);
+                                                coeff = temp;
+                                            }
+                                            // Polynomial correction
+                                            psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1,
+                                                                                             PS_POLYNOMIAL_ORD);
+                                            psFree(correction->coeff);
+                                            correction->coeff = psMemIncrRefCounter(coeff->data.F64);
+                                            (void)pmNonLinearityPolynomial(inputReadout, correction);
+                                            psFree(coeff);
+                                            psFree(correction);
+                                        } else if (optionItem->type == PS_DATA_STRING) {
+                                            // This is a filename
+                                            psString tableName = optionItem->data.V; // The filename
+                                            psLookupTable *table = psLookupTableAlloc(tableName, "%f %f", 0);
+                                            int numLines = 0; // Number of lines read from table
+                                            if ((numLines = psLookupTableRead(table)) <= 0) {
+                                                psErrorStackPrint(stderr, "Unable to read non-linearity "
+                                                                  "correction file %s --- ignored\n",
+                                                                  tableName);
+                                            } else {
+#ifdef PRODUCTION
+                                                (void)pmNonLinearityLookup(inputReadout, table);
+#else
+                                                printf("XXX: Non-linearity correction from lookup table not "
+                                                       "yet implemented.\n");
+#endif
+                                            }
+                                            psFree(table);
+                                        } else {
+                                            psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction "
+                                                     "desired but unable to interpret NONLIN.DATA for %s"
+                                                     " --- ignored\n", conceptValue);
+                                        }
+                                    }
+                                }
+                            }
+                            break;
+                          default:
+                            psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction desired, but "
+                                     "NONLIN.DATA is of invalid type --- ignored.\n");
+                        }
+                    }
+                } // Non-linearity correction
+
+                // Bias, dark and overscan subtraction are all merged.
+
+                // Changed the definition of pmSubtractBias to do the dark subtraction as well, but
+                // it's not in there yet.  Once it is, we can get rid of "pedestal".
+
+#ifdef PRODUCTION
+                psImage *pedestal = NULL;       // Pedestal image (bias + scaled dark)
+                if (doDark) {
+                    // Dark time for input image
+                    float inputTime = psMetadataLookupF32(NULL, inputCell->concepts, "CELL.DARKTIME");
+                    if (inputTime <= 0) {
+                        psErrorStackPrint(stderr, "DARKTIME for input image (%f) is non-positive.\n",
+                                          inputTime);
+                        exit(EXIT_FAILURE);
+                    }
+
+                    pedestal = (psImage*)psBinaryOp(NULL, darkImage, "*",
+                                                    psScalarAlloc(inputTime * darkTime, PS_TYPE_F32));
+                }
+                if (doBias) {
+                    if (pedestal) {
+                        if (biasImage->numRows != darkImage->numRows ||
+                            biasImage->numCols != darkImage->numCols) {
+                            psError(PS_ERR_IO, true, "Bias and dark images have different dimensions: "
+                                    "%dx%d vs %dx%d\n", biasImage->numCols, biasImage->numRows,
+                                    darkImage->numCols, darkImage->numRows);
+                            exit(EXIT_FAILURE);
+                        }
+                        (void)psBinaryOp(pedestal, pedestal, "+", biasImage);
+                    } else {
+                        pedestal = psMemIncrRefCounter(biasImage);
+                    }
+                }
+#endif
+                if (overscanMode == PM_OVERSCAN_ROWS || overscanMode == PM_OVERSCAN_COLUMNS) {
+                    // Need to get the read direction
+                    int readdir = psMetadataLookupS32(NULL, inputCell->concepts, "CELL.READDIR");
+                    if (readdir == 1) {
+// psmodule-0.8.0 has confused PM_OVERSCAN_ROWS and PM_OVERSCAN_COLUMNS; bug 608.
+#ifdef PRODUCTION
+                        overscanMode = PM_OVERSCAN_ROWS;
+#else
+                        overscanMode = PM_OVERSCAN_COLUMNS;
+#endif
+                    } else if (readdir == 2) {
+#ifdef PRODUCTION
+                        overscanMode = PM_OVERSCAN_COLUMNS;
+#else
+                        overscanMode = PM_OVERSCAN_ROWS;
+#endif
+                    } else {
+                        psErrorStackPrint(stderr, "CELL.READDIR (%d) is not 1 or 2 --- assuming 1.\n",
+                                          readdir);
+                        overscanMode = PM_OVERSCAN_ROWS;
+                    }
+                }
+
+                if (doBias || doOverscan || doDark) {
+                    psList *inputOverscans = pmReadoutGetBias(inputReadout); // List of overscan bias regions
+#ifdef PRODUCTION
+                    (void)pmSubtractBias(inputReadout, overscanFit, inputOverscans, overscanMode,
+                                         overscanStats, overscanBins, overscanFitType, pedestal);
+#else
+                    (void)pmSubtractBias(inputReadout, overscanFit, inputOverscans, overscanMode,
+                                         overscanStats, overscanBins, overscanFitType, biasReadout);
+#endif
+                    psFree(inputOverscans);
+
+                    // Output overscan fit results, if required
+                    if (doOverscan) {
+                        if (! overscanFit) {
+                            psLogMsg("phase2", PS_LOG_WARN, "No fit generated!\n");
+                        } else {
+                            switch (overscanFitType) {
+                              case PM_FIT_POLYNOMIAL:
+                                {
+                                    psPolynomial1D *poly = (psPolynomial1D*)overscanFit; // The polynomial
+                                    psString coeffs = NULL;     // String containing the coefficients
+                                    for (int i = 0; i < poly->nX; i++) {
+                                        psStringAppend(&coeffs, "%e ", poly->coeff[i]);
+                                    }
+                                    psLogMsg("phase2", PS_LOG_INFO, "Overscan polynomial coefficients:\n%s\n",
+                                             coeffs);
+                                    psFree(coeffs);
+                                }
+                                break;
+                              case PM_FIT_SPLINE:
+                                {
+                                    psSpline1D *spline = (psSpline1D*)overscanFit; // The spline
+                                    psString coeffs = NULL;     // String containing the coefficients
+                                    for (int i = 0; i < spline->n; i++) {
+                                        psPolynomial1D *poly = spline->spline[i]; // i-th polynomial
+                                        psStringAppend(&coeffs, "%d: ", i);
+                                        for (int j = 0; j < poly->nX; j++) {
+                                            psStringAppend(&coeffs, "%e ", poly->coeff[i]);
+                                        }
+                                        psStringAppend(&coeffs, "\n");
+                                    }
+                                    psLogMsg("phase2", PS_LOG_INFO, "Overscan spline coefficients:\n%s\n",
+                                             coeffs);
+                                    psFree(coeffs);
+                                }
+                                break;
+                              case PM_FIT_NONE:
+                                break;
+                              default:
+                                psAbort(__func__, "Should never get here!!!\n");
+                            }
+                        }
+                    }
+                }
+
+                // XXX: Temporary: until the other functions are altered to do this themselves.
+                // Trim, so that flat, fringe etc computations are faster.
+#if 0
+#ifndef PRODUCTION
+                psRegion *trimRegion = psMetadataLookupPtr(NULL, inputCell->concepts, "CELL.TRIMSEC");
+                psImage *trimmed = psImageSubset(inputReadout->image, *trimRegion);
+                psFree(inputReadout->image);
+                inputReadout->image = trimmed;
+#endif
+#endif
+                // Flat-field correction
+                if (doFlat) {
+                    psLogMsg("phase2", PS_LOG_INFO, "Performing flat field.\n");
+#ifndef PRODUCTION
+                    psImage *dummyImage = psImageAlloc(inputReadout->image->numCols,
+                                                       inputReadout->image->numRows,
+                                                       PS_TYPE_U8);
+                    pmReadout *dummyMask = pmReadoutAlloc(NULL);
+                    dummyMask->image = dummyImage;
+                    (void)pmFlatField(inputReadout, dummyMask, flatReadout);
+                    psFree(dummyMask);
+                    psFree(dummyImage);
+#endif
+                }
+
+                // Fringe subtraction
+                if (doFringe) {
+#if 0
+                    // Placeholder
+                    pmReadout *pmSubtractSky(pmReadout *in, psPolynomial2D *poly, psImage *mask,
+                                             psU8 maskVal, int binFactor, psStats *stats, float clipSD);
+#endif
+                }
+
+                // Source identification and photometry
+                if (doSource) {
+                }
+
+                // Astrometry
+                if (doAstrom) {
+                }
+
+
+            } // Iterating over readouts
+        } // Iterating over cells
+
+#if 0
+        {
+            // Testing pmChipMosaic
+            psImage *mosaic = pmChipMosaic(inputChip, 1, 1); // Mosaic of chip
+            psFits *mosaicFile = psFitsOpen("mosaic.fits", "w"); // FITS file to which to write
+            psFitsWriteImage(mosaicFile, NULL, mosaic, 0);
+            psFitsClose(mosaicFile);
+            psFree(mosaic);
+        }
+#endif
+
+    } // Iterating over chips
+
+    psLogMsg("phase2", PS_LOG_INFO, "Processing completed after %f sec.\n", psTimerMark("phase2"));
+
+
+#if 0
+    {
+        // Testing pmFPAMorph
+        int nBad = 0;
+        psMetadata *newCamera = psMetadataConfigParse(NULL, &nBad, "megacam_splice.config", true);
+        pmFPA *newFPA = pmFPAConstruct(newCamera);
+        pmFPAMorph(newFPA, input, true, 0, 0);
+        //pmFPAPrint(newFPA);
+        psFits *newFile = psFitsOpen("morph.fits", "w");
+        pmFPAWrite(newFile, newFPA, database);
+        psFitsClose(newFile);
+        psFree(newFPA);
+    }
+#endif
+
+#if 1
+    // Write the output
+    pmFPAWrite(outputFile, input, database);
+    pmFPAWriteMask(input, outputFile);
+    pmFPAWriteWeight(input, outputFile);
+#endif
+
+    psLogMsg("phase2", PS_LOG_INFO, "Output completed after %f sec.\n", psTimerMark("phase2"));
+
+    psFitsClose(outputFile);
+
+    psFree(arguments);
+    psFree(site);
+    psFree(header);
+    psFree(camera);
+    psFree(recipe);
+    psFree(input);
+    psFree(mask);
+    psFree(bias);
+    psFree(dark);
+    psFree(flat);
+    psFree(overscanFit);
+    psFree(overscanStats);
+
+    psLogMsg("phase2", PS_LOG_INFO, "Cleanup completed after %f sec.\n", psTimerMark("phase2"));
+    psTimerStop();
+
+#if 0
+    psMemCheckCorruption(true);
+    psMemBlock **leaks = NULL;          // List of leaks
+    int nLeaks = psMemCheckLeaks(0, &leaks, NULL, false); // Number of leaks
+    printf("%d leaks found.\n", nLeaks);
+#if 1
+    for (int i = 0; i < nLeaks; i++) {
+        psMemBlock *mb = leaks[i];
+        printf("Memory leak %ld (%ld):\n"
+               "\tFile %s, line %d, size %d\n"
+               "\tPosts: %x %x %x\n",
+               mb->id, mb->refCounter, mb->file, mb->lineno, mb->userMemorySize, mb->startblock, mb->endblock,
+               *(void**)((int8_t *)(mb + 1) + mb->userMemorySize));
+    }
+#endif
+#endif
+
+}
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/papStuff.c
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/papStuff.c	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/papStuff.c	(revision 7503)
@@ -0,0 +1,56 @@
+#include <stdio.h>
+#include <strings.h>
+
+#include "pslib.h"
+#include "psAdditionals.h"
+
+#include "papStuff.h"
+
+
+static void memPrint(const psPtr ptr)
+{
+    psMemBlock *mb = ((psMemBlock*)ptr) - 1;
+    printf("Memory block %lld (%lld):\n"
+	   "\tFile %s, line %d, size %d\n"
+	   "\tPosts: %x %x %x\n",
+	   mb->id, mb->refCounter, mb->file, mb->lineno, mb->userMemorySize, mb->startblock, mb->endblock,
+	   *(void**)((int8_t *)(mb + 1) + mb->userMemorySize));
+}
+
+// Split string on given characters
+psList *papSplit(const char *string, const char *splitters)
+{
+    psList *values = psListAlloc(NULL);	// The list of values to return
+    unsigned int length = strlen(string); // The length of the string
+    unsigned int numSplitters = strlen(splitters); // Number of characters that might split
+    unsigned int start = 0;		// The position of the start of a word
+    for (int i = 1; i < length; i++) {
+	bool split = false;		// Is this character a splitter?
+	for (int j = 0; j < numSplitters && ! split; j++) {
+	    if (string[i] == splitters[j]) {
+		split = true;
+	    }
+	}
+	if (split) {
+	    if (i == start) {
+		// Some idiot put in two spaces, or two commas or something
+		start++;
+	    } else {
+		// We're at the end of the word
+		psString word = psStringNCopy(&string[start], i - start);
+		(void)psListAdd(values, PS_LIST_TAIL, word);
+		start = i + 1;
+		psFree(word);
+	    }
+	}
+    }
+    if (start < length) {
+	// Copy the last word
+	psString word = psStringNCopy(&string[start], length - start);
+	(void)psListAdd(values, PS_LIST_TAIL, word);
+	psFree(word);
+    }
+
+    return values;
+}
+
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/papStuff.h
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/papStuff.h	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/papStuff.h	(revision 7503)
@@ -0,0 +1,9 @@
+#ifndef PAP_STUFF_H
+#define PAP_STUFF_H
+
+#include "pslib.h"
+
+// Split string on given characters
+psList *papSplit(const char *string, const char *splitters);
+
+#endif
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/phase2.config
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/phase2.config	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/phase2.config	(revision 7503)
@@ -0,0 +1,65 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+NONLIN		BOOL	TRUE		# Non-linearity correction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+SOURCE		BOOL	FALSE		# Source identification and photometry
+ASTROM		BOOL	FALSE		# Astrometry
+
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.MODE		STR	INDIVIDUAL	# NONE | INDIVIDUAL | ALL
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.BIN		S32	0		# Number of pixels per bin
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/pmFPAMorph.c
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/pmFPAMorph.c	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/pmFPAMorph.c	(revision 7503)
@@ -0,0 +1,727 @@
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include "pslib.h"
+
+#include "papStuff.h"
+#include "pmFPA.h"
+#include "pmFPAMorph.h"
+
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+
+// Flip the chip in x
+static psImage *xFlipImage(psImage *image, // Image to be flipped
+                           int size     // Size of image in flip dimension
+    )
+{
+    psImage *flipped = psImageAlloc(size, image->numRows, image->type.type); // Flipped image
+    switch (image->type.type) {
+      case PS_TYPE_U16:
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                flipped->data.U16[y][size - x - 1] = image->data.U16[y][x];
+            }
+        }
+        break;
+      case PS_TYPE_F32:
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                flipped->data.F32[y][size - x - 1] = image->data.F32[y][x];
+            }
+        }
+        break;
+      default:
+        psError(PS_ERR_IO, true, "Image type %x not yet supported for flip.\n", image->type.type);
+    }
+
+    return flipped;
+}
+
+// Flip the chip in y
+static psImage *yFlipImage(psImage *image, // Image to be flipped
+                           int size     // Size of image in flip dimension
+    )
+{
+    psImage *flipped = psImageAlloc(image->numCols, size, image->type.type); // Flipped image
+    switch (image->type.type) {
+      case PS_TYPE_U16:
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                flipped->data.U16[size - y - 1][x] = image->data.U16[y][x];
+            }
+        }
+        break;
+      default:
+        psError(PS_ERR_IO, true, "Image type %x not yet supported for flip.\n", image->type.type);
+    }
+
+    return flipped;
+}
+
+
+#if 0
+// Return a list of the region strings and the method for interpreting them
+static psList *getRegions(psString *method, // Method for evaluating the regions
+                          psString methodValues // The METHOD:VALUES string
+    )
+{
+    char *values = strchr(methodValues, ':'); // The VALUES
+    *method = psStringNCopy(methodValues, strlen(methodValues) - strlen(values)); // The METHOD
+    psList *regionStrings = NULL;       // List of the region strings
+    if (strncmp(*method, "HEADER", 6) == 0 || strncmp(*method, "HD", 2) == 0) {
+        regionStrings = papSplit(values, ", ");
+    } else if (strncmp(*method, "VALUE", 5) == 0 || strncmp(*method, "VAL", 3) == 0) {
+        regionStrings = papSplit(values, "; ");
+    }
+
+    return regionStrings;
+}
+#endif
+
+
+// Splice an image into another image
+static psRegion *spliceImage(psImage *target, // Target image onto which to splice
+                             int *x0, int *y0, // Starting coordinates to splice to
+                             psImage *source, // Source image from which to splice
+                             const psRegion *from, // Region to splice from
+                             int readdir, // Read direction
+                             bool xFlip, bool yFlip // Flip x and y axes?
+                        )
+{
+    psImage *toSplice = psImageSubset(source, *from); // Subimage, to splice into target
+    if (xFlip) {
+        int size = 0;           // Size of flipped image
+        if (readdir == 1) {
+            size = toSplice->numCols;
+        } else if (readdir == 2) {
+            size = target->numCols;
+        }
+        psImage *temp = xFlipImage(toSplice, size);
+        psFree(toSplice);
+        toSplice = temp;
+    }
+    if (yFlip) {
+        int size = 0;           // Size of flipped image
+        if (readdir == 1) {
+            size = target->numRows;
+        } else if (readdir == 2) {
+            size = toSplice->numRows;
+        }
+        psImage *temp = yFlipImage(toSplice, size);
+        psFree(toSplice);
+        toSplice = temp;
+    }
+
+    psTrace(__func__, 5, "Splicing %dx%d image in at %d,%d\n", toSplice->numCols, toSplice->numRows,
+            *x0, *y0);
+    (void)psImageOverlaySection(target, toSplice, *x0, *y0, "=");
+
+    if (readdir == 1) {
+        *x0 += toSplice->numCols;
+    } else if (readdir == 2) {
+        *y0 += toSplice->numRows;
+    }
+
+    // Return the region where we pasted the image
+    psRegion *outRegion = psAlloc(sizeof(psRegion));
+    // Addding 1 to get FITS standard
+    outRegion->x0 = *x0 + 1;
+    outRegion->x1 = *x0 + toSplice->numCols;
+    outRegion->y0 = *y0 + 1;
+    outRegion->y1 = *y0 + toSplice->numRows;
+
+    psFree(toSplice);
+    return outRegion;
+}
+
+// Splice bias regions (overscans)
+static bool spliceBias(psImage *splice, // Image to which to splice
+                       int *x0, int *y0, // Starting position for splice
+                       const pmCell *in, // Input cell
+                       pmCell *out,     // Output cell
+                       int xFlip, int yFlip, // Flip the image?
+                       int readNum,     // Number of readout
+                       int readdir      // Read direction
+                       )
+{
+    psArray *readouts = in->readouts; // The readouts;
+    pmReadout *readout = readouts->data[readNum]; // The readout of interest
+    psList *inBiassecs = psMetadataLookupPtr(NULL, in->concepts, "CELL.BIASSEC"); // List of input biassecs
+    psListIterator *inBiassecsIter = psListIteratorAlloc(inBiassecs, PS_LIST_HEAD, false);
+    psRegion *inBiassec = NULL; // BIASSEC from list
+    psList *outBiassecs = psListAlloc(NULL); // List of biassecs for output
+    while (inBiassec = psListGetAndIncrement(inBiassecsIter)) {
+        psRegion *outBiassec = spliceImage(splice, x0, y0, readout->image, inBiassec, readdir, xFlip,
+                                           yFlip);
+        psListAdd(outBiassecs, PS_LIST_TAIL, outBiassec);
+    }
+    psFree(inBiassecsIter);
+    psMetadataAdd(out->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_DATA_LIST | PS_META_REPLACE,
+                  "BIASSEC from pmFPAMorph", outBiassecs);
+
+    return true;
+}
+
+psImage *p_pmImageMosaic(const psArray *source, // Images to splice in
+                         const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                         const psVector *xBinSource, const psVector *yBinSource, // Binning in x and y of
+                                                                                 // source images
+                         int xBinTarget, int yBinTarget, // Binning in x and y of target images
+                         const psVector *x0, const psVector *y0 // Offsets for source images on target
+                         );
+
+// Splice a list of cells together
+// We need to do the following:
+// 1. Copy the contents of the cells (i.e., the readouts) over from the in to the out
+// 2. Splice together everything in the bucket for the image that we will write out
+static psArray *spliceCells(psList *outCells, // List of target cells (required for parity info)
+                            psList *inCells, // List of cells to splice together
+                            bool posDep // Position dependent placement of overscans?
+    )
+{
+    assert(outCells);
+    assert(inCells);
+
+    psTrace(__func__, 5, "Splicing together all cells in the bucket.\n");
+
+    if (inCells->n != outCells->n) {
+        psError(PS_ERR_IO, true, "Sizes of input (%d) and output (%d) lists of cells don't match.\n",
+                inCells->n, outCells->n);
+        return NULL;
+    }
+
+    int numCells = inCells->n;          // Number of cells
+    int readdir = 0;                    // Read direction for CCD; currently unknown
+    int numReadouts = 0;                // Number of readouts in a cell
+    psArray *spliced = NULL;            // Array of spliced readouts
+    psElemType type = 0;                // Image type (U16, S32, F32, etc)
+
+    psVector *xFlip = psVectorAlloc(numCells, PS_TYPE_U8); // Do we need to flip in x?
+    psVector *yFlip = psVectorAlloc(numCells, PS_TYPE_U8); // Do we need to flip in y?
+    psVector *xBin = psVectorAlloc(numCells, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(numCells, PS_TYPE_S32); // Binning in y
+    psVector *x0 = psVectorAlloc(numCells, PS_TYPE_S32); // Offset in x
+    psVector *y0 = psVectorAlloc(numCells, PS_TYPE_S32); // Offset in y
+    int xBinOut = 0;                    // Binning in x for output
+    int yBinOut = 0;                    // Binning in y for output
+
+    // We go through the cells to make sure everything's kosher, and to get the sizes.
+    psListIterator *inCellsIter = psListIteratorAlloc(inCells, PS_LIST_HEAD, false); // Iterator for cells
+    psListIterator *outCellsIter = psListIteratorAlloc(outCells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *inCell = NULL;              // Cell from list of input cells
+    pmCell *outCell = NULL;             // Cell from list of output cells
+    int cellNum = -1;                   // Cell number
+    while (inCell = psListGetAndIncrement(inCellsIter) && outCell = psListGetAndIncrement(outCellsIter)) {
+        cellNum++;
+        psArray *inReadouts = inCell->readouts;   // The readouts comprising the cell
+
+        // Get and check the read direction
+        int cellReaddir = psMetadataLookupS32(NULL, inCell->concepts, "CELL.READDIR");
+        psTrace(__func__, 10, "Checking read direction for cell %d: %d\n", cellNum, cellReaddir);
+        if (cellNum == 0) {
+            // First run through; set it.
+            readdir = cellReaddir;
+        } else if (readdir != cellReaddir) {
+            psError(PS_ERR_IO, true, "Trying to splice cells with different read directions (%d vs %d). I "
+                    "don't know how to do that!\n", readdir, cellReaddir);
+            // XXX Clean up before returning
+            return NULL;
+        }
+
+        // Get and check the number of readouts
+        if (! spliced) {
+            numReadouts = inReadouts->n;
+            spliced = psArrayAlloc(numReadouts);
+            xSize = psVectorAlloc(numReadouts, PS_TYPE_S32);
+            ySize = psVectorAlloc(numReadouts, PS_TYPE_S32);
+            for (int i = 0; i < numReadouts; i++) {
+                xSize->data.S32[i] = 0;
+                ySize->data.S32[i] = 0;
+            }
+        } else if (inReadouts->n != numReadouts) {
+            psError(PS_ERR_IO, true, "Trying to splice cells with different number of reads (%d vs %d)\n",
+                    numReadouts, readouts->n);
+            // XXX Clean up before returning
+            return NULL;
+        }
+
+        // Get and check the binning --- we can't splice to cells that have different binnings, because
+        // the spliced image can't be defined.  Hence all the "to" cells must have the same binning.
+        xBinIn = psMetadataLookupS32(NULL, inCell->concepts, "CELL.XBIN");
+        yBinIn = psMetadataLookupS32(NULL, inCell->concepts, "CELL.YBIN");
+        int xBinCheck = psMetadataLookupS32(NULL, outCell->concepts, "CELL.XBIN");
+        int yBinCheck = psMetadataLookupS32(NULL, outCell->concepts, "CELL.YBIN");
+        if (xBinOut == 0) {
+            xBinOut = xBinCheck;
+        } else if (xBinCheck != xBinOut) {
+            psError(PS_ERR_IO, true, "Trying to splice to cells with different x binnings (%d vs %d)\n",
+                    xBinOut, xBinCheck);
+            // XXX Clean up before returning
+            return NULL;
+        }
+        if (yBinOut == 0) {
+            yBinOut = yBinCheck;
+        } else if (yBinCheck != yBinOut) {
+            psError(PS_ERR_IO, true, "Trying to splice to cells with different y binnings (%d vs %d)\n",
+                    yBinOut, yBinCheck);
+            // XXX Clean up before returning
+            return NULL;
+        }
+
+        // Copy the readouts over
+        psArray *outReadouts = psArrayAlloc(inReadouts->n); // Array of readouts for the output cell
+        for (int i = 0; i < inReadouts->n; i++) {
+            outReadouts->data[i] = psMemIncrRefCounter(inReadouts->data[i]);
+        }
+
+        // Get the offsets
+        x0->data.S32[i] = psMetadataLookupS32(NULL, inCell->concepts, "CELL.X0");
+        y0->data.S32[i] = psMetadataLookupS32(NULL, inCell->concepts, "CELL.Y0");
+
+        // Get and check the parity
+        int xParityIn = psMetadataLookupS32(NULL, inCell->concepts, "CELL.XPARITY");
+        int yParityIn = psMetadataLookupS32(NULL, inCell->concepts, "CELL.YPARITY");
+        int xParityOut = psMetadataLookupS32(NULL, outCell->concepts, "CELL.XPARITY");
+        int yParityOut = psMetadataLookupS32(NULL, outCell->concepts, "CELL.YPARITY");
+        if (xParityIn == 0 || xParityOut == 0 || yParityIn == 0 || yParityOut == 0) {
+            psError(PS_ERR_IO, false, "Unable to determine parity of cell!\n");
+            // XXX Clean up before returning
+            return NULL;
+        }
+        if (xParityIn != xParityOut) {
+            for (int i = 0; i < outReadouts->n; i++) {
+                psImage *image = outReadouts->data[i];
+                outReadouts->data[i] = xFlipImage(image);
+                psFree(image);
+            }
+            // Correct CELL.X0 for the parity shift
+            psMetadataItem *x0item = psMetadataLookup(inCell->concepts, "CELL.X0");
+            x0item->data.S32 -= xSize->data.S32[cellNum];
+        }
+        if (yParityIn != yParityOut) {
+            for (int i = 0; i < outReadouts->n; i++) {
+                psImage *image = outReadouts->data[i];
+                outReadouts->data[i] = xFlipImage(image);
+                psFree(image);
+            }
+            // Correct CELL.Y 0 for the parity shift
+            psMetadataItem *y0item = psMetadataLookup(inCell->concepts, "CELL.Y0");
+            y0item->data.S32 -= ySize->data.S32[cellNum];
+        }
+
+        //////////////////////////////////////////////////////////////////////////////////////////////////////
+        // DO I WANT TO DO THIS????
+        // Wouldn't it be better to splice all the images together first, and then divvy up the components?
+        //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+        // Manipulate the images, if required
+        for (int i = 0; i < outReadouts->n; i++) {
+            psImage *image = manipulate(outReadouts
+
+                         psImage *image = psMemIncrRefCounter(outReadouts->data[i]);
+                         if (xParityIn != xParityOut) {
+                             psImage *image = xFlipImage(image);
+                         }
+
+
+        // THIS ISN'T SO SIMPLE: we need to SET the output x0,y0 based on where the image actually gets pasted
+        // Get the offsets
+        x0->data.S32[i] = psMetadataLookupS32(NULL, outCell->concepts, "CELL.X0");
+        y0->data.S32[i] = psMetadataLookupS32(NULL, outCell->concepts, "CELL.Y0");
+
+
+
+
+// Splice a list of cells together
+static psArray *spliceCells(psList *outCells, // List of target cells (required for parity info)
+                            psList *inCells, // List of cells to splice together
+                            bool posDep // Position dependent placement of overscans?
+    )
+{
+    assert(outCells);
+    assert(inCells);
+
+    psTrace(__func__, 5, "Splicing together all cells in the bucket.\n");
+
+    if (inCells->n != outCells->n) {
+        psError(PS_ERR_IO, true, "Sizes of input (%d) and output (%d) lists of cells don't match.\n",
+                inCells->n, outCells->n);
+        return NULL;
+    }
+
+    int numCells = inCells->n;          // Number of cells
+    int readdir = 0;                    // Read direction for CCD; currently unknown
+    int numReadouts = 0;                // Number of readouts in a cell
+    psArray *spliced = NULL;            // Array of spliced readouts
+    psElemType type = 0;                // Image type (U16, S32, F32, etc)
+
+    psVector *xSize = NULL;             // Size of spliced image in x
+    psVector *ySize = NULL;             // Size of spliced image in y
+
+    psVector *xFlip = psVectorAlloc(numCells, PS_TYPE_U8); // Do we need to flip in x?
+    psVector *yFlip = psVectorAlloc(numCells, PS_TYPE_U8); // Do we need to flip in y?
+
+
+    // We go through the cells to make sure everything's kosher, and to get the sizes.
+    psListIterator *inCellsIter = psListIteratorAlloc(inCells, PS_LIST_HEAD, false); // Iterator for cells
+    psListIterator *outCellsIter = psListIteratorAlloc(outCells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *inCell = NULL;              // Cell from list of input cells
+    int cellNum = 0;                    // Cell number;
+    while (inCell = psListGetAndIncrement(inCellsIter)) {
+        psArray *readouts = inCell->readouts;   // The readouts comprising the cell
+
+        // Get and check the read direction
+        int cellReaddir = psMetadataLookupS32(NULL, inCell->concepts, "CELL.READDIR");
+        psTrace(__func__, 10, "Checking read direction for cell %d: %d\n", cellNum, cellReaddir);
+        if (cellNum == 0) {
+            // First run through; set it.
+            readdir = cellReaddir;
+        } else if (readdir != cellReaddir) {
+            psError(PS_ERR_IO, true, "Trying to splice cells with different read directions (%d vs %d). I "
+                    "don't know how to do that!\n", readdir, cellReaddir);
+            // XXX Clean up before returning
+            return NULL;
+        }
+
+        // Get and check the number of readouts
+        if (! spliced) {
+            numReadouts = readouts->n;
+            spliced = psArrayAlloc(numReadouts);
+            xSize = psVectorAlloc(numReadouts, PS_TYPE_S32);
+            ySize = psVectorAlloc(numReadouts, PS_TYPE_S32);
+            for (int i = 0; i < numReadouts; i++) {
+                xSize->data.S32[i] = 0;
+                ySize->data.S32[i] = 0;
+            }
+        } else if (readouts->n != numReadouts) {
+            psError(PS_ERR_IO, true, "Trying to splice cells with different number of reads (%d vs %d)\n",
+                    numReadouts, readouts->n);
+            // XXX Clean up before returning
+            return NULL;
+        }
+
+        // Get and check the cell parities
+        pmCell *outCell = psListGetAndIncrement(outCellsIter);  // Corresponding output cell
+        int xParityIn = psMetadataLookupS32(NULL, inCell->concepts, "CELL.XPARITY");
+        int yParityIn = psMetadataLookupS32(NULL, inCell->concepts, "CELL.YPARITY");
+        int xParityOut = psMetadataLookupS32(NULL, outCell->concepts, "CELL.XPARITY");
+        int yParityOut = psMetadataLookupS32(NULL, outCell->concepts, "CELL.YPARITY");
+        if (xParityIn == 0 || xParityOut == 0 || yParityIn == 0 || yParityOut == 0) {
+            psError(PS_ERR_IO, false, "Unable to determine parity of cell!\n");
+            // XXX Clean up before returning
+            return NULL;
+        }
+
+        if (yParityIn == yParityOut) {
+            yFlip->data.U8[cellNum] = 0;
+        } else {
+            psTrace(__func__, 7, "Need to flip y.\n");
+            yFlip->data.U8[cellNum] = 1;
+        }
+        if (xParityIn == xParityOut) {
+            xFlip->data.U8[cellNum] = 0;
+        } else {
+            psTrace(__func__, 7, "Need to flip x.\n");
+            xFlip->data.U8[cellNum] = 1;
+        }
+        cellNum++;
+
+        // Calculate the sizes of the spliced images
+        for (int i = 0; i < numReadouts; i++) {
+            pmReadout *readout = readouts->data[i]; // The readout of interest
+            psImage *image = readout->image; // The image pixels
+
+            // Check image type
+            if (type == 0) {
+                type = image->type.type;
+            } else if (image->type.type != type) {
+                psError(PS_ERR_IO, true, "Trying to splice readouts with different image types (%d vs %d)\n",
+                        type, image->type.type);
+                // XXX Clean up before returning
+                return NULL;
+            }
+
+            psRegion *trimsec = psMetadataLookupPtr(NULL, inCell->concepts, "CELL.TRIMSEC"); // Trim section
+            psList *biassecs = psMetadataLookupPtr(NULL, inCell->concepts, "CELL.BIASSEC"); // Bias section
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+            psRegion *biassec = NULL;   // Bias section from list iteration
+
+            // Get the size of the spliced image
+            if (readdir == 1) {
+                psTrace(__func__, 7, "TRIMSEC is %.0fx%.0f\n", trimsec->x1 - trimsec->x0,
+                        trimsec->y1 - trimsec->y0);
+                xSize->data.S32[i] += trimsec->x1 - trimsec->x0;
+                ySize->data.S32[i] = MAX(ySize->data.S32[i], trimsec->y1 - trimsec->y0);
+                while (biassec = psListGetAndIncrement(biassecsIter)) {
+                    psTrace(__func__, 9, "BIASSEC is %.0fx%.0f\n", biassec->x1 - biassec->x0,
+                        biassec->y1 - biassec->y0);
+                    xSize->data.S32[i] += biassec->x1 - biassec->x0;
+                    ySize->data.S32[i] = MAX(ySize->data.S32[i], biassec->y1 - biassec->y0);
+                }
+            } else if (readdir == 2) {
+                ySize->data.S32[i] += trimsec->y1 - trimsec->y0;
+                xSize->data.S32[i] = MAX(xSize->data.S32[i], trimsec->x1 - trimsec->x0);
+                while (biassec = psListGetAndIncrement(biassecsIter)) {
+                    ySize->data.S32[i] += biassec->y1 - biassec->y0;
+                    xSize->data.S32[i] = MAX(ySize->data.S32[i], biassec->x1 - biassec->x0);
+                }
+            } else {
+                psError(PS_ERR_IO, true, "Invalid read direction: %d\n", readdir);
+                // XXX Clean up before returning
+                return NULL;
+            }
+            psFree(biassecsIter);
+        }
+    }
+    psFree(inCellsIter);
+    psFree(outCellsIter);
+
+
+    // Make sure all the readouts have the same size
+    int numRows = ySize->data.S32[0];   // Number of rows for spliced image
+    int numCols = xSize->data.S32[0];   // Number of columns for spliced image
+    for (int i = 1; i < numReadouts; i++) {
+        if (xSize->data.S32[i] != numCols || ySize->data.S32[i] != numRows) {
+            psError(PS_ERR_IO, true, "Spliced readouts would have different sizes: %dx%d vs %dx%d\n",
+                    numCols, numRows, xSize->data.S32[i], ySize->data.S32[i]);
+        }
+    }
+
+    psTrace(__func__, 5, "Spliced image has dimensions %dx%d\n", numCols, numRows);
+
+    // Now we have done the requisite checks, and know the sizes; we just go through and splice together
+    for (int i = 0; i < numReadouts; i++) {
+        psImage *splice = psImageAlloc(numCols, numRows, type); // The spliced image
+        int x0 = 0, y0 = 0;             // Position at which the splice begins
+
+        // Position-dependent overscans first
+        if (posDep) {
+            psTrace(__func__, 8, "Doing first position-dependent biases...\n");
+            for (int cellNum = 0; cellNum < inCells->n / 2; cellNum++) {
+                psTrace(__func__, 9, "Cell %d...\n", cellNum);
+                pmCell *inCell = psListGet(inCells, cellNum); // Input cell of interest
+                pmCell *outCell = psListGet(outCells, cellNum); // Output cell of interest
+                spliceBias(splice, &x0, &y0, inCell, outCell, xFlip->data.U8[i], yFlip->data.U8[i], i,
+                           readdir);
+            }
+        }
+
+        // Then the images
+        psListIterator *inCellsIter = psListIteratorAlloc(inCells, PS_LIST_HEAD, false);// Iterator for cells
+        pmCell *inCell = NULL;          // Cell from the list of cells
+        psListIterator *outCellsIter = psListIteratorAlloc(outCells, PS_LIST_HEAD, false); // Iterator
+        int cellNum = 0;                // Cell number
+        while (inCell = psListGetAndIncrement(inCellsIter)) {
+            psArray *readouts = inCell->readouts; // The readouts comprising the cell
+            pmReadout *readout = readouts->data[i]; // The specific readout of interest
+            pmCell *outCell = psListGetAndIncrement(outCellsIter); // Output cell
+            psRegion *inTrimsec = psMetadataLookupPtr(NULL, inCell->concepts, "CELL.TRIMSEC"); // TRIMSEC
+            psRegion *outTrimsec = spliceImage(splice, &x0, &y0, readout->image, inTrimsec, readdir,
+                                               xFlip->data.U8[cellNum], yFlip->data.U8[cellNum]);
+
+            // Update the output TRIMSEC
+            psMetadataAdd(outCell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_DATA_UNKNOWN | PS_META_REPLACE,
+                             "TRIMSEC from pmFPAMorph", outTrimsec);
+        }
+        psFree(inCellsIter);
+        psFree(outCellsIter);
+
+        // Then the biases
+        if (posDep) {
+            // Need to only do half the biases
+            psTrace(__func__, 8, "Doing second position-dependent biases...\n");
+            for (int cellNum = inCells->n / 2; cellNum < inCells->n; cellNum++) {
+                psTrace(__func__, 9, "Cell %d...\n", cellNum);
+                pmCell *inCell = psListGet(inCells, cellNum); // Input cell of interest
+                pmCell *outCell = psListGet(outCells, cellNum); // Output cell of interest
+                spliceBias(splice, &x0, &y0, inCell, outCell, xFlip->data.U8[i], yFlip->data.U8[i], i,
+                           readdir);
+            }
+        } else {
+            // Need to do all the biases
+            for (int cellNum = 0; cellNum < inCells->n; cellNum++) {
+                pmCell *inCell = psListGet(inCells, cellNum); // Input cell of interest
+                pmCell *outCell = psListGet(outCells, cellNum); // Output cell of interest
+                spliceBias(splice, &x0, &y0, inCell, outCell, xFlip->data.U8[i], yFlip->data.U8[i], i,
+                           readdir);
+            }
+        }
+
+        spliced->data[i] = splice;
+
+    } // Iterating over readouts
+
+    return spliced;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+bool pmFPAMorph(pmFPA *toFPA,           // FPA structure to which to morph
+                pmFPA *fromFPA,         // FPA structure from which to morph
+                bool positionDependent, // Is the position of the overscan dependent on the position?
+                int chipNum,            // Chip number, in case the scopes are different
+                int cellNum             // Cell number, in case the scopes are different
+    )
+{
+    psList *targetCells = psListAlloc(NULL); // List of target cells
+    psList *sourceCells = psListAlloc(NULL); // List of source cells
+
+    psArray *toChips = toFPA->chips;    // Array of chips
+    psArray *fromChips = fromFPA->chips;// Array of chips
+
+    psTrace(__func__, 1, "Copying FPA concepts over...\n");
+    pap_psMetadataCopy(toFPA->concepts, fromFPA->concepts);
+    pap_psMetadataCopy(toFPA->phu, fromFPA->phu);
+
+    for (int i = 0; i < toChips->n; i++) {
+        pmChip *toChip = toChips->data[i];
+        pmChip *fromChip = NULL;
+        // Select the correct chip
+        if (toChips->n == 1 && fromChips->n > chipNum) {
+            fromChip = fromChips->data[chipNum];
+        } else if (fromChips->n == 1 && toChips->n > chipNum) {
+            fromChip = fromChips->data[0];
+        } else if (toChips->n == fromChips->n) {
+            fromChip = fromChips->data[i];
+        } else {
+            psError(PS_ERR_IO, true, "Unable to discern intended chip.\n");
+            return false;
+        }
+
+        if (! fromChip->valid) {
+            toChip->valid = false;
+            continue;
+        }
+
+        psTrace(__func__, 2, "Copying chip %d concepts over...\n", i);
+        pap_psMetadataCopy(toChip->concepts, fromChip->concepts);
+
+        psArray *toCells = toChip->cells; // Array of cells
+        psArray *fromCells = fromChip->cells; // Array of cells
+
+        for (int j = 0; j < toCells->n; j++) {
+            pmCell *toCell = toCells->data[j];
+            pmCell *fromCell = NULL;
+            // Select the correct cell
+            if (toCells->n == 1 && fromCells->n > cellNum) {
+                fromCell = fromCells->data[cellNum];
+            } else if (fromCells->n == 1 && toCells->n > cellNum) {
+                fromCell = fromCells->data[0];
+            } else if (toCells->n == fromCells->n) {
+                fromCell = fromCells->data[j];
+            } else {
+                psError(PS_ERR_IO, true, "Unable to discern intended cell.\n");
+                return false;
+            }
+
+            if (! fromCell->valid) {
+                toCell->valid = false;
+                continue;
+            }
+
+#ifdef TESTING
+            int xParityIn = psMetadataLookupS32(NULL, fromCell->concepts, "CELL.XPARITY");
+            int yParityIn = psMetadataLookupS32(NULL, fromCell->concepts, "CELL.YPARITY");
+            int xParityOut = psMetadataLookupS32(NULL, toCell->concepts, "CELL.XPARITY");
+            int yParityOut = psMetadataLookupS32(NULL, toCell->concepts, "CELL.YPARITY");
+            psTrace(__func__, 3, "Chip %d, cell %d: in (%d,%d) out (%d,%d)\n", i, j, xParityIn, yParityIn,
+                    xParityOut, yParityOut);
+#endif
+
+#ifdef TESTING
+            psMetadataPrint(toCell->concepts, 7);
+#endif
+
+            // Copy the concepts over
+
+            // Need to be a little tricky here --- some concepts are already set from the camera configuration
+            // file (e.g., CELL.NAME), and we want to preserve these, not replace it with the old value.
+            psTrace(__func__, 3, "Copying cell %d concepts over...\n", j);
+            psMetadata *newConcepts = pap_psMetadataCopy(NULL, fromCell->concepts);
+            pap_psMetadataCopy(newConcepts, toCell->concepts); // This preserves any already existing concepts
+            psFree(toCell->concepts);
+            toCell->concepts = newConcepts;
+
+            // Now, we need to check if the following concepts for the target cell are defined statically:
+            // CELL.XPARITY, CELL.YPARITY, CELL.XBIN, CELL.YBIN, CELL.X0, CELL.Y0
+            // 1. If they are specified by the header, we can do what we want (because we can set the header).
+            // 2. If they are not specified by the header, we use those values.
+            const char *checkConcepts[4] = { "CELL.XPARITY",
+                                             "CELL.YPARITY",
+                                             "CELL.XBIN",
+                                             "CELL.YBIN",
+                                             "CELL.X0",
+                                             "CELL.Y0" }; // Concepts that we need to check
+            bool mdok = false;          // Result of MD lookup
+            psMetadata *translation = psMetadataLookupMD(&mdok, toFPA->camera, "TRANSLATION"); // Header
+                                        // translation table
+            if (!mdok || ! translation) {
+                psError(PS_ERR_IO, true, "Unable to find TRANSLATION in camera configuration!\n");
+                return false;
+            }
+            for (int c = 0; c < 4; c++) {
+                psString headerName = psMetadataLookupString(&mdok, translation, checkConcepts[c]); // Name of
+                                        // header holding the concept, or NULL
+                if (!mdok || strlen(headerName) <= 0) {
+                    p_pmCellIngestConcept(toFPA, toChip, toCell, db, checkConcepts[c]);
+                }
+            }
+
+#ifdef TESTING
+            psMetadataPrint(toCell->concepts, 7);
+#endif
+
+            psTrace(__func__, 5, "Putting chip %d, cell %d in the bucket.\n", i, j);
+            psListAdd(targetCells, PS_LIST_TAIL, toCell);
+            psListAdd(sourceCells, PS_LIST_TAIL, fromCell);
+
+            // Copy the cell contents over
+            toCell->readouts = psMemIncrRefCounter(fromCell->readouts);
+
+            if (toCell->hdu && strlen(toCell->hdu->extname) > 0) {
+                // Splice the component cells
+                p_pmHDU *hdu = toCell->hdu;
+                if (! hdu->header) {
+                    hdu->header = psMetadataAlloc();
+                }
+                hdu->images = spliceCells(targetCells, sourceCells, positionDependent);
+                // Purge the lists
+                while (psListRemove(targetCells, PS_LIST_TAIL));
+                while (psListRemove(sourceCells, PS_LIST_TAIL));
+            }
+
+        }
+
+        if (toChip->hdu && strlen(toChip->hdu->extname) > 0) {
+            // Splice the component cells
+            p_pmHDU *hdu = toChip->hdu;
+            if (! hdu->header) {
+                hdu->header = psMetadataAlloc();
+            }
+            hdu->images = spliceCells(targetCells, sourceCells, positionDependent);
+            // Purge the lists
+            while (psListRemove(targetCells, PS_LIST_TAIL));
+            while (psListRemove(sourceCells, PS_LIST_TAIL));
+        }
+
+    }
+
+    if (toFPA->hdu && strlen(toFPA->hdu->extname) > 0) {
+        // Splice the component cells
+        p_pmHDU *hdu = toFPA->hdu;
+        if (! hdu->header) {
+            hdu->header = psMetadataAlloc();
+        }
+        hdu->images = spliceCells(targetCells, sourceCells, positionDependent);
+        // Purge the lists
+        while (psListRemove(targetCells, PS_LIST_TAIL));
+        while (psListRemove(sourceCells, PS_LIST_TAIL));
+    }
+
+    return true;
+}
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/pmFPAMorph.h
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/pmFPAMorph.h	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/pmFPAMorph.h	(revision 7503)
@@ -0,0 +1,13 @@
+#ifndef PM_FPA_MORPH_H
+#define PM_FPA_MORPH_H
+
+// Morph one focal plane representation into another
+bool pmFPAMorph(pmFPA *toFPA,		// FPA structure to which to morph
+		pmFPA *fromFPA,		// FPA structure from which to morph
+		bool positionDependent, // Is the position of the overscan dependent on the position?
+		int chipNum,		// Chip number, in case the scopes are different
+		int cellNum		// Cell number, in case the scopes are different
+    );
+
+
+#endif
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/psAdditionals.c
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/psAdditionals.c	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/psAdditionals.c	(revision 7503)
@@ -0,0 +1,567 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+#include "psAdditionals.h"
+
+
+psMetadata *pap_psMetadataCopy(psMetadata *out,
+			       const psMetadata *in)
+{
+    PS_ASSERT_PTR_NON_NULL(in,NULL);
+    if (out ==  NULL) {
+        out = psMetadataAlloc();
+    }
+    psMetadataItem *inItem = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(*(psMetadata**)&in, PS_LIST_HEAD, NULL);
+    unsigned long numPointers = 0;	// Number of pointers we were forced to copy
+    while (inItem = psMetadataGetAndIncrement(iter)) {
+	psMetadataItem *outItem = NULL;
+
+	// Need to look for MULTI, which won't be picked up using the iterator.
+	psMetadataItem *multiCheckItem = psMetadataLookup(in, inItem->name);
+	unsigned int flag = PS_META_REPLACE; // Flag to indicate MULTI; otherwise, replace
+	if (multiCheckItem->type == PS_DATA_METADATA_MULTI) {
+	    psTrace(__func__, 10, "MULTI: %s (%s)\n", inItem->name, inItem->comment);
+	    flag = PS_DATA_METADATA_MULTI;
+	}
+
+	psTrace(__func__, 5, "Copying %s (%s)...\n", inItem->name, inItem->comment);
+
+#define PS_METADATA_COPY_CASE(NAME,TYPE) \
+          case PS_TYPE_##NAME: \
+            if (! psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_TYPE_##NAME | flag, inItem->comment, \
+			        inItem->data.TYPE)) { \
+	        psErrorStackPrint(stderr, "Error copying %s (%s) in the metadata\n", inItem->name, \
+                                  inItem->comment); \
+            } \
+            break;
+
+	switch (inItem->type) {
+	    // Numerical types
+	    PS_METADATA_COPY_CASE(BOOL,B);
+	    PS_METADATA_COPY_CASE(S8,S8);
+	    PS_METADATA_COPY_CASE(S16,S16);
+	    PS_METADATA_COPY_CASE(S32,S32);
+	    PS_METADATA_COPY_CASE(U8,U8);
+	    PS_METADATA_COPY_CASE(U16,U16);
+	    PS_METADATA_COPY_CASE(U32,U32);
+	    PS_METADATA_COPY_CASE(F32,F32);
+	    PS_METADATA_COPY_CASE(F64,F64);
+
+	    // String: relying on the fact that this will copy the string, not point at it.
+	  case PS_DATA_STRING:
+            psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_DATA_STRING | flag, inItem->comment,
+			  inItem->data.V);
+            break;
+
+	    // Metadata: copy the next level and stuff that in too
+	  case PS_DATA_METADATA:
+	    {
+		psMetadata *metadata = pap_psMetadataCopy(NULL, inItem->data.md);
+		psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_DATA_METADATA | flag, inItem->comment,
+			      metadata);
+		break;
+	    }
+	    // Other kinds of pointers
+	  default:
+	    numPointers++;
+	    psTrace(__func__, 10, "Copying a pointer in the metadata: %x\n", inItem->type);
+	    psMetadataAdd(out, PS_LIST_TAIL, inItem->name, inItem->type | flag, inItem->comment,
+			  inItem->data.V);
+	    break;
+	}
+    }
+    psFree(iter);
+
+    if (numPointers > 0) {
+	psLogMsg(__func__, PS_LOG_WARN, "Forced to copy %d pointers when copying metadata.  Updating the "
+		 "copied psMetadata will affect the original!\n", numPointers);
+    }
+
+    return out;
+}
+
+
+psMetadata *psMetadataLookupMD(bool *status, const psMetadata *md, const char *key)
+{
+    psMetadataItem *item = psMetadataLookup((psMetadata*)md, key); // The metadata with instruments
+    psMetadata *value = NULL;		// The value to return
+    if (!item) {
+	// The given key isn't in the metadata
+	if (status) {
+	    *status = false;
+	} else {
+	    psError(PS_ERR_IO, true, "Couldn't find %s in the metadata.\n");
+	}
+    } else if (item->type != PS_DATA_METADATA) {
+	// The value at the key isn't metadata
+	if (status) {
+	    *status = false;
+	} else {
+	    psLogMsg(__func__, PS_LOG_WARN, "%s isn't of type PS_DATA_METADATA, as expected.\n");
+	}
+	value = NULL;
+    } else {
+	// We have the requested metadata
+	if (status) {
+	    *status = true;
+	}
+	value = item->data.md; // The requested metadata
+    }
+    return value;
+}
+
+
+char *psMetadataLookupString(bool *status, const psMetadata *md, const char *key)
+{
+    psMetadataItem *item = psMetadataLookup((psMetadata*)md, key); // The metadata with instruments
+    char *value = NULL;			// The value to return
+    if (!item) {
+	// The given key isn't in the metadata
+	if (status) {
+	    *status = false;
+	} else {
+	    psError(PS_ERR_IO, true, "Couldn't find %s in the metadata.\n");
+	}
+    } else if (item->type != PS_DATA_STRING) {
+	// The value at the key isn't of the desired type
+	if (status) {
+	    *status = false;
+	} else {
+	    psLogMsg(__func__, PS_LOG_WARN, "%s isn't of type PS_DATA_STRING, as expected.\n");
+	}
+	value = NULL;
+    } else {
+	// We have the requested metadata
+	if (status) {
+	    *status = true;
+	}
+	value = item->data.V; // The requested metadata
+    }
+    return value;
+}
+
+
+void psMetadataPrint(psMetadata *md, int level)
+{
+    psMetadataIterator *iter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL);	// Iterator
+    psMetadataItem *item = NULL;	// Item from metadata
+    while (item = psMetadataGetAndIncrement(iter)) {
+	// Indent...
+	for (int i = 0; i < level; i++) {
+	    printf(" ");
+	}
+	printf("%s", item->name);
+	if (item->comment && strlen(item->comment) > 0) {
+	    printf(" (%s)", item->comment);
+	}
+	printf(": ");
+	switch (item->type) {
+	  case PS_DATA_STRING:
+	    printf("%s", item->data.V);
+	    break;
+	  case PS_DATA_BOOL:
+	    if (item->data.B) {
+		printf("True");
+	    } else {
+		printf("False");
+	    }
+	    break;
+	  case PS_DATA_S32:
+	    printf("%d", item->data.S32);
+	    break;
+	  case PS_DATA_F32:
+	    printf("%f", item->data.F32);
+	    break;
+	  case PS_DATA_F64:
+	    printf("%f", item->data.F64);
+	    break;
+	  case PS_DATA_METADATA:
+	    printf("\n");
+	    psMetadataPrint(item->data.V, level + 1);
+	    break;
+	  default:
+	    printf("\n");
+	    psError(PS_ERR_IO, false, "Non-printable metadata type: %x\n", item->type);
+	}
+	printf("\n");
+    }
+    psFree(iter);
+
+    return;
+}
+
+
+// Set verbosity level
+int psArgumentVerbosity(int *argc, char **argv)
+{
+    int logLevel = 2;			// Default log level
+    int argnum = 0;			// Argument number
+
+    // set in order, so that -vvv overrides -vv overrides -v
+    if (argnum = psArgumentGet(*argc, argv, "-v")) {
+        psArgumentRemove(argnum, argc, argv);
+        logLevel = 3;
+    }
+    if (argnum = psArgumentGet(*argc, argv, "-vv")) {
+        psArgumentRemove(argnum, argc, argv);
+        logLevel = 4;
+    }
+    if (argnum = psArgumentGet(*argc, argv, "-vvv")) {
+        psArgumentRemove(argnum, argc, argv);
+        logLevel = 5;
+    }
+    psLogSetLevel (logLevel);		// XXX: This function should return an error if the log level is invalid
+
+    if (argnum = psArgumentGet(*argc, argv, "-logfmt")) {
+        if (*argc < argnum + 2) {
+            psError(PS_ERR_IO, true, "-logfmt switch specified without a format.");
+        } else {
+	    psArgumentRemove(argnum, argc, argv);
+	    psLogSetFormat(argv[argnum]); // XXX EAM : this function should return an error if the log format is invalid
+	    psArgumentRemove(argnum, argc, argv);
+	}
+    }
+
+    // Now the trace stuff
+    // argument format is: -trace (facil) (level)
+    while (argnum = psArgumentGet(*argc, argv, "-trace")) {
+        if (*argc < argnum + 3) {
+            psError(PS_ERR_IO, true, "-trace switch specified without facility and level.");
+        }
+        psArgumentRemove(argnum, argc, argv);
+        psTraceSetLevel(argv[argnum], atoi(argv[argnum+1])); // XXX: This function should return an error if the trace level is invalid
+        psArgumentRemove(argnum, argc, argv);
+        psArgumentRemove(argnum, argc, argv);
+    }
+    if ((argnum = psArgumentGet(*argc, argv, "-trace-levels"))) {
+        psTracePrintLevels();
+        exit(2);
+    }
+
+    return logLevel;
+}
+ 
+// Find the location of the specified argument
+int psArgumentGet(int argc, char **argv, const char *arg)
+{
+    for (int i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], arg))
+            return i;
+    }
+   
+    return 0;
+}
+
+// Remove the specified argument (by location)
+bool psArgumentRemove(int argnum, int *argc, char **argv)
+{
+    if (argnum > 0) {
+        (*argc)--;
+        for (int i = argnum; i < *argc; i++) {
+            argv[i] = argv[i+1];
+        }
+    } else {
+	return false;
+    }
+    
+    return true;
+}
+
+
+static psMetadataItem *argumentRead(psMetadataItem *item, // Item to read into
+				    int argnum,	// Argument number
+				    int *argc,	// Number of arguments in total
+				    char **argv	// The arguments
+				    )
+{
+    psMetadataItem *newItem = NULL;
+    switch(item->type) {
+	// Only doing a representative set of types
+      case PS_DATA_S32:
+	newItem = psMetadataItemAlloc(item->name, item->type, item->comment, atoi(argv[argnum]));
+	psArgumentRemove(argnum, argc, argv);
+	break;
+      case PS_DATA_F32:
+	newItem = psMetadataItemAlloc(item->name, item->type, item->comment, atof(argv[argnum]));
+	psArgumentRemove(argnum, argc, argv);
+	break;
+      case PS_DATA_BOOL:
+	// Turn option on; no optional argument to remove
+	newItem = psMetadataItemAlloc(item->name, item->type, item->comment, true);
+	break;
+	// XXX: Include the other numerical types
+      case PS_DATA_STRING:
+	{
+	    //psString string = psStringCopy(argv[argnum]);	// Get the argument into PS memory management
+	    //psFree(string);
+	    newItem = psMetadataItemAlloc(item->name, item->type, item->comment, argv[argnum]);
+	    psArgumentRemove(argnum, argc, argv);
+	}
+	break;
+      default:
+	psError(PS_ERR_IO, true, "Argument type (%x) is not supported --- argument %s ignored\n",
+		item->type, item->name);
+	psFree(newItem);
+	return NULL;
+    }
+
+    return newItem;
+}
+
+
+// XXX: There is a memory leak in the MULTI section.  I think it might have something to do with reference
+// counting between lists and MD, in the second section of the code (copy newArgs into arguments), but I'm not
+// entirely sure.
+bool psArgumentParse(psMetadata *arguments, int *argc, char **argv)
+{
+    // We need to do a bit of mucking around in order to preserve the arguments metadata until the last
+    // minute --- if there is a bad argument, we need to return the old "arguments", since they contain
+    // the default values, which we probably want to output in a "help" message (we don't want to print
+    // the changed values and have the user think that they are default values).
+
+    psMetadata *newArgs = psMetadataAlloc(); // Place to read arguments into
+    psList *changed = psListAlloc(NULL);// List of keys that have changed
+
+    for (int i = 1; i < *argc; i++) {
+	psTrace(__func__, 7, "Looking at %s\n", argv[i]);
+	psMetadataItem *argItem = psMetadataLookup(arguments, argv[i]);
+	if (argItem) {
+	    psArgumentRemove(i, argc, argv); // Remove the switch
+	    if (argItem->type != PS_DATA_METADATA_MULTI) {
+		if (argItem->type != PS_DATA_BOOL && *argc < i + 1) {
+		    psError(PS_ERR_IO, true, "Required argument for %s is missing.\n", argItem->name);
+		    // XXX: Cleanup before returning
+		    psFree(newArgs);
+		    return false;
+		}
+		psMetadataItem *newItem = argumentRead(argItem, i, argc, argv);
+		psMetadataAddItem(newArgs, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		psFree(newItem);
+	    } else {
+		// Go through the MULTI
+		psList *multi = argItem->data.V; // The list of MULTI psMetadataItems
+		if (*argc < i + multi->n) {
+		    psError(PS_ERR_IO, true, "Not enough arguments for %s.\n", argItem->name);
+		    // Remove the arguments --- they will be ignored
+		    for (int j = i; i < *argc; i++) {
+			psArgumentRemove(i, argc, argv);
+		    }
+		    // XXX: Cleanup before returning
+		    psFree(newArgs);
+		    return false;
+		}
+
+		// Remove any prior existence in the newArgs --- this is important because we specify
+		// adding the new items as DUPLICATE_OK, so if some idiot specifies it twice, we'd end
+		// up with two copies of everything.
+		psMetadataItem *checkItem = psMetadataLookup(newArgs, argItem->name);
+		if (checkItem) {
+		    (void)psMetadataRemove(newArgs, 0, argItem->name);
+		    (void)psListRemoveData(changed, argItem->name);
+		}
+
+		psListIterator *multiIter = psListIteratorAlloc(multi, PS_LIST_HEAD, true);
+		psMetadataItem *nextItem = NULL; // Item from list
+		while (nextItem = psListGetAndIncrement(multiIter)) {
+		    psMetadataItem *newItem = argumentRead(nextItem, i, argc, argv);
+		    psMetadataAddItem(newArgs, newItem, PS_LIST_TAIL, PS_META_DUPLICATE_OK);
+		    //psFree(newItem);
+		}
+		psFree(multiIter);
+	    }
+
+	    // Some book-keeping
+	    //	    psString name = psStringCopy(argItem->name);
+	    psListAdd(changed, PS_LIST_TAIL, argItem->name);
+	    i--;
+
+	} else if (strncmp(argv[i], "-", 1) == 0 || strncmp(argv[i], "+", 1) == 0) {
+	    // Someone's specified a bad option
+	    psError(PS_ERR_IO, true, "Unknown option: %s\n", argv[i]);
+	    psFree(newArgs);
+	    return false;
+	}
+    }
+
+    // All the arguments are good, so now we can copy the newArgs over
+    psListIterator *changedIter = psListIteratorAlloc(changed, PS_LIST_HEAD, false); // Iterator
+    psString name = NULL;		// Item from iteration
+    while (name = psListGetAndIncrement(changedIter)) {
+	printf("Updating %s\n", name);
+	psMetadataItem *oldItem = psMetadataLookup(arguments, name);
+	psMetadataItem *newItem = psMetadataLookup(newArgs, name);
+	if (oldItem->type != newItem->type) {
+	    psAbort(__func__, "Shouldn't reach here!\n");
+	}
+	switch (oldItem->type) {
+	    // Only doing a representative set of types
+	  case PS_DATA_S32:
+	    oldItem->data.S32 = newItem->data.S32;
+	    break;
+	  case PS_DATA_F32:
+	    oldItem->data.F32 = newItem->data.F32;
+	    break;
+	  case PS_DATA_BOOL:
+	    oldItem->data.B = newItem->data.B;
+	    break;
+	    // XXX: Include the other numerical types
+	  case PS_DATA_STRING:
+	    psFree(oldItem->data.V);
+	    oldItem->data.V = psMemIncrRefCounter(newItem->data.V);
+	    break;
+	  case PS_DATA_METADATA_MULTI:
+	    {
+		psList *newMulti = psMemIncrRefCounter(newItem->data.V);	// The new list of MULTI
+		psList *oldMulti = oldItem->data.V;	// The old list of MULTI
+		psListIterator *newMultiIter = psListIteratorAlloc(newMulti, PS_LIST_HEAD, false);
+		psListIterator *oldMultiIter = psListIteratorAlloc(oldMulti, PS_LIST_HEAD, true);
+		psMetadataItem *newMultiItem = NULL; // Item from iterator
+		while (newMultiItem = psListGetAndIncrement(newMultiIter)) {
+		    psMetadataItem *oldMultiItem = psListGetAndIncrement(oldMultiIter);
+		    if (! oldMultiItem) {
+			psAbort(__func__, 
+				"Something went very wrong here!  The lists SHOULD be of the same length!\n");
+		    }
+		    switch (oldMultiItem->type) {
+			// Only doing a representative set of types
+		      case PS_DATA_S32:
+			oldItem->data.S32 = newItem->data.S32;
+			break;
+		      case PS_DATA_F32:
+			oldItem->data.F32 = newItem->data.F32;
+			break;
+			// XXX: Include the other numerical types
+		      case PS_DATA_STRING:
+			psFree(oldItem->data.V);
+			oldItem->data.V = psMemIncrRefCounter(newItem->data.V);
+			break;
+		      default:
+			psAbort(__func__, "Should never ever get here, ever.\n");
+		    }
+		    psFree(oldMultiItem);
+		    psFree(newMultiItem);
+		}
+		psFree(newMultiIter);
+		psFree(oldMultiIter);
+	    }
+	    break;
+	  default:
+	    psAbort(__func__, "Should never ever ever get here.\n");
+	}
+    }
+    psFree(changedIter);
+    psFree(changed);
+
+    // Now, blow away the newArgs and we're done.
+    psFree(newArgs);
+    return true;
+}
+
+
+static int argLength(psMetadataItem *arg)
+{
+    switch (arg->type) {
+	// Only doing a representative set of types
+      case PS_DATA_S32:
+	return arg->data.S32 >= 0 ? (int)log10f((float)arg->data.S32) + 1 :
+	    (int)log10f(-(float)arg->data.S32) + 2;
+	// XXX: Other numerical types
+      case PS_DATA_F32:
+	return arg->data.F32 >= 0 ? 12 : 13; // -d.dddddde?dd
+      case PS_DATA_F64:
+	return arg->data.F64 >= 0 ? 12 : 13; // -d.dddddde?dd
+      case PS_DATA_BOOL:
+	return arg->data.B ? 4 : 5;
+      case PS_DATA_STRING:
+	return strlen(arg->data.V);
+      default:
+	psAbort(__func__, "Argument type (%x) is not supported.\n", arg->type);
+    }
+ 
+    return 0;
+}
+
+#define NUM_SPACES 4			// Number of spaces between
+
+void psArgumentHelp(psMetadata *arguments)
+{
+    printf("Optional arguments, with default values:\n");
+    psMetadataIterator *argIter = psMetadataIteratorAlloc(arguments, PS_LIST_HEAD, NULL);
+    psMetadataItem *argItem = NULL;	// Item from iterator
+    int maxName = 4;			// Maximum length of a name
+    int maxValue = 4;			// Maximum length of a value
+
+    // First pass to get the sizes
+    while (argItem = psMetadataGetAndIncrement(argIter)) {
+	if (strlen(argItem->name) > maxName) {
+	    maxName = strlen(argItem->name);
+	}
+	int valLength = argLength(argItem);
+	if (valLength > maxValue) {
+	    maxValue = valLength;
+	}
+    }
+
+    // Second pass to print
+    psMetadataIteratorSet(argIter, PS_LIST_HEAD);
+    psString lastName = NULL;		// Last name we printed
+    while (argItem = psMetadataGetAndIncrement(argIter)) {
+	// Initial indent
+	for (int i = 0; i < NUM_SPACES; i++) {
+	    printf(" ");
+	}
+
+	// Print the name if required
+	int position = 0;	// Number of spaces in
+	if (! lastName || strcmp(lastName, argItem->name) != 0) {
+	    // A new name
+	    printf("%s", argItem->name);
+	    position += strlen(argItem->name);
+	    lastName = argItem->name;
+	}
+	for (int i = position; i < maxName + NUM_SPACES; i++) {
+	    printf(" ");
+	}
+
+	// Print the value
+	printf("(");
+	switch (argItem->type) {
+	    // Only doing a representative set of types
+	  case PS_DATA_S32:
+	    printf("%d", argItem->data.S32);
+	    break;
+	    // XXX: Other numerical types
+	  case PS_DATA_F32:
+	    printf("%.6e", argItem->data.F32);
+	    break;
+	  case PS_DATA_F64:
+	    printf("%.6e", argItem->data.F64);
+	    break;
+	  case PS_DATA_BOOL:
+	    if (argItem->data.B) {
+		printf("TRUE");
+	    } else {
+		printf("FALSE");
+	    }
+	    break;
+	  case PS_DATA_STRING:
+	    printf("%s", argItem->data.V);
+	    break;
+	  default:
+	    psAbort(__func__, "Argument type (%x) is not supported.\n", argItem->type);
+	}
+	printf(")");
+	for (int i = argLength(argItem); i < maxValue + NUM_SPACES; i++) {
+	    printf(" ");
+	}
+
+	// Print the comment
+	if (argItem->comment) {
+	    printf("%s", argItem->comment);
+	}
+	printf("\n");
+    }
+
+    psFree(argIter);
+}
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/psAdditionals.h
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/psAdditionals.h	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/psAdditionals.h	(revision 7503)
@@ -0,0 +1,30 @@
+#ifndef PS_ADDITIONALS_H
+#define PS_ADDITIONALS_H
+
+#include "pslib.h"
+
+// Deep copy of metadata
+psMetadata *pap_psMetadataCopy(psMetadata *out, const psMetadata *in);
+
+// Get a value from the metadata that we believe should be metadata.
+psMetadata *psMetadataLookupMD(bool *status, const psMetadata *md, const char *key);
+
+// Get a value from the metadata that we believe should be a string
+char *psMetadataLookupString(bool *status, const psMetadata *md, const char *key);
+
+#if 0
+pmChip *psMetadataLookupChip(bool *status, const psMetadata *md, const char *key);
+pmCell *psMetadataLookupCell(bool *status, const psMetadata *md, const char *key);
+#endif
+
+// Print out the metadata
+void psMetadataPrint(psMetadata *md, int level);
+
+// Argument handling
+int psArgumentVerbosity(int *argc, char **argv);
+int psArgumentGet(int argc, char **argv, const char *arg);
+bool psArgumentRemove(int argnum, int *argc, char **argv);
+bool psArgumentParse(psMetadata *arguments, int *argc, char **argv);
+void psArgumentHelp(psMetadata *arguments);
+
+#endif
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/readouts.txt
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/readouts.txt	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/readouts.txt	(revision 7503)
@@ -0,0 +1,41 @@
+Behaviour of readouts:
+
+
+typedef struct {
+	const char *extname;
+	psMetadata *header;
+	psArray *images;
+	psArray *masks;
+	psArray *weights;
+} pmPixelData;
+
+typedef struct {
+	...
+	psMetadata *concepts;
+	pmCell *parent;
+	psImage *image;
+	psImage *mask;
+	psImage *weight;
+} pmReadout;
+
+
+Procedures:
+
+a. Read: set pmReadout->image = pmPixelData->images->data[i],
+   pmReadout->mask = pmPixelData->masks->data[i], pmReadout->weight =
+   pmPixelData->weights->data[i]
+
+b. Mask bad pixels: Copy the provided mask into the input image mask.
+
+c. Bias/overscan subtraction: Read the CELL.BIASSEC, and apply to the
+   CELL.TRIMSEC.
+
+d. Trim: Cut the CELL.TRIMSEC, and paste into pmReadout->image,
+   pmReadout->mask, pmReadout->weight.
+
+e. Flat-fielding: Pretty straight-forward.  The FF just needs to be of
+   the same format as the input image.
+
+f. Splice: If images are untrimmed, then have to pluck out the
+   CELL.TRIMSEC and CELL.BIASSEC, and need to worry about the overscan
+   regions.
Index: /tags/rel-0_0_2/archive/scripts/src/phase2/testing.txt
===================================================================
--- /tags/rel-0_0_2/archive/scripts/src/phase2/testing.txt	(revision 7503)
+++ /tags/rel-0_0_2/archive/scripts/src/phase2/testing.txt	(revision 7503)
@@ -0,0 +1,65 @@
+Overscans:
+
+Modes: NONE | INDIVIDUAL | ALL
+
+* NONE produces warnings that there is no overscan subtraction:
+  "Proceeding to full fram subtraction."  This is probably
+  unnecessary.  The second warning is "bias frame is NULL.  Returning
+  original image."  This is in the spec --- good.
+
+* INDIVIDUAL works well.  The exponential ramp at the bottom of the
+  megacam image is removed. It leaves horizontal streaks, but I've
+  seen these before, and I figure they're part of the instrument, not
+  our software.
+
+* ALL also works.  The bias is removed, but the ramp remains, which is
+  exactly what I expected.
+
+Bin: An integer.  Using INDIVIDUAL mode.
+
+* 1 removes the ramp
+* 0 removes the ramp
+* Large values (50,100) have the effect of smoothing out the
+  horizontal streaks.
+
+Stat: MEAN | MEDIAN
+
+* Do a similar job (not identical).  Slightly less horizontal
+  structure with median.
+
+Fit: NONE | POLYNOMIAL | SPLINE
+
+* NONE is fine.
+
+* POLYNOMIAL produces a SEGV when I try to print out the resultant
+  polynomial.  I realise I haven't told it the polynomial order
+  (should create a psPolynomial1D to feed in to specify the order),
+  but I would have expected an error message.  Fixing this up, the
+  polynomial fitting works fairly well.
+
+* SPLINE doesn't seem to work --- it won't give back a spline for me
+  to print out the results of the fit.  Filed a bug.
+
+
+Flat: it works, but I'm concerned it's doing the entire image area,
+instead of just the appropriate area (CELL.TRIMSEC).  I added a trim
+to the phase 2, and it runs faster now.  The program still outputs the
+overscan, beacause it writes the pixels it read it.  The trim only
+affects where the computations are done.  Flat-fielding the image by
+itself (w/o overscan) results in ones everywhere, except the overscan.
+ 
+Full-frame bias subtraction: Works, but it acts on the entire image.
+Fixed the definition so that this is only done on the region specified
+by CELL.TRIMSEC.  That way, the overscan of the input image isn't
+affected by the full-frame subtraction.
+
+Non-linearity: This is really slow, or there's something really weird
+going on.  It used a heap of memory.  OK, found the problem: I had an
+extraneous free that caused corruption of the number of elements in
+the vector of coefficients.  Again, it wants to act on the entire
+image, so adding to the spec that it should only act on the
+CELL.TRIMSEC (assuming the non-linearity is from the CCD itself, not
+the amplifier).  It's fairly slow, but that might be because of the
+multiple trace messages in the polynomial evaluation.  Got the
+individual correction parameters working.  Now need to do the lookup
+table.
Index: /tags/rel-0_0_2/ippdb/Doxyfile.in
===================================================================
--- /tags/rel-0_0_2/ippdb/Doxyfile.in	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/Doxyfile.in	(revision 7503)
@@ -0,0 +1,1219 @@
+# Doxyfile 1.4.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = @PACKAGE_NAME@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+# @top_builddir@ doesn't work for some reason
+OUTPUT_DIRECTORY       = @builddir@/docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the progam writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @top_srcdir@/src
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = @PERL@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /tags/rel-0_0_2/ippdb/Makefile.am
===================================================================
--- /tags/rel-0_0_2/ippdb/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/Makefile.am	(revision 7503)
@@ -0,0 +1,51 @@
+SUBDIRS = src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= ippdb.pc
+
+EXTRA_DIST = ippdb.pc.in Doxyfile.in
+
+if DOXYGEN
+
+man_MANS = \
+    $(top_builddir)/docs/man/man3/ippdb.3 \
+    $(top_builddir)/docs/man/man3/weatherRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_transparencyRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_absorptionRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_emissionRow.3 \
+    $(top_builddir)/docs/man/man3/dimmRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_irRow.3 \
+    $(top_builddir)/docs/man/man3/domeRow.3 \
+    $(top_builddir)/docs/man/man3/telescopeRow.3 \
+    $(top_builddir)/docs/man/man3/summitExpRow.3 \
+    $(top_builddir)/docs/man/man3/pzPendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/pzPendingImfileRow.3 \
+    $(top_builddir)/docs/man/man3/newExpRow.3 \
+    $(top_builddir)/docs/man/man3/newImfileRow.3 \
+    $(top_builddir)/docs/man/man3/rawDetrendExpRow.3 \
+    $(top_builddir)/docs/man/man3/rawScienceExpRow.3 \
+    $(top_builddir)/docs/man/man3/rawImfileRow.3 \
+    $(top_builddir)/docs/man/man3/p1PendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/p2PendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/p2PendingImfileRow.3 \
+    $(top_builddir)/docs/man/man3/p2DoneExpRow.3 \
+    $(top_builddir)/docs/man/man3/p2DoneImfileRow.3 \
+    $(top_builddir)/docs/man/man3/p3PendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/detRunRow.3 \
+    $(top_builddir)/docs/man/man3/detInputExpRow.3 \
+    $(top_builddir)/docs/man/man3/detProcessedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detStackedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detNormalizedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detMasterFrameRow.3 \
+    $(top_builddir)/docs/man/man3/detMasterImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detResidImfileAnalysisRow.3 \
+    $(top_builddir)/docs/man/man3/detResidExpAnalysisRow.3 
+
+
+docs/man/man3/ippdb.3 docs/man/man3/weatherRow.3 docs/man/man3/skyp_transparencyRow.3 docs/man/man3/skyp_absorptionRow.3 docs/man/man3/skyp_emissionRow.3 docs/man/man3/dimmRow.3 docs/man/man3/skyp_irRow.3 docs/man/man3/domeRow.3 docs/man/man3/telescopeRow.3 docs/man/man3/summitExpRow.3 docs/man/man3/pzPendingExpRow.3 docs/man/man3/pzPendingImfileRow.3 docs/man/man3/newExpRow.3 docs/man/man3/newImfileRow.3 docs/man/man3/rawDetrendExpRow.3 docs/man/man3/rawScienceExpRow.3 docs/man/man3/rawImfileRow.3 docs/man/man3/p1PendingExpRow.3 docs/man/man3/p2PendingExpRow.3 docs/man/man3/p2PendingImfileRow.3 docs/man/man3/p2DoneExpRow.3 docs/man/man3/p2DoneImfileRow.3 docs/man/man3/p3PendingExpRow.3 docs/man/man3/detRunRow.3 docs/man/man3/detInputExpRow.3 docs/man/man3/detProcessedImfileRow.3 docs/man/man3/detStackedImfileRow.3 docs/man/man3/detNormalizedImfileRow.3 docs/man/man3/detMasterFrameRow.3 docs/man/man3/detMasterImfileRow.3 docs/man/man3/detResidImfileAnalysisRow.3 docs/man/man3/detResidExpAnalysisRow.3:
+	$(DOXYGEN)
+
+endif
+
+clean-local:
+	-rm -rf docs
Index: /tags/rel-0_0_2/ippdb/autogen.sh
===================================================================
--- /tags/rel-0_0_2/ippdb/autogen.sh	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/autogen.sh	(revision 7503)
@@ -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=ippdb
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+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: /tags/rel-0_0_2/ippdb/configure.ac
===================================================================
--- /tags/rel-0_0_2/ippdb/configure.ac	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/configure.ac	(revision 7503)
@@ -0,0 +1,39 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ippdb], [0.0.2], [pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([ippdb.pc.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_CONFIG_TESTDIR([tests])
+AC_CONFIG_FILES([tests/Makefile])
+AM_MISSING_PROG([AUTOM4TE], [autom4te])
+
+AC_LANG(C)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0]) 
+
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+AC_PATH_PROG([doxygen], [doxygen], [missing])
+AM_CONDITIONAL([DOXYGEN], test "x$doxygen" = "xmissing")
+
+dnl is this the best was to setup recursive CFLAGS?
+ippdb_CFLAGS="-Wall -pedantic -std=c99 -fno-strict-aliasing"
+AC_SUBST([ippdb_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  ippdb.pc
+  Doxyfile
+])
+AC_OUTPUT
Index: /tags/rel-0_0_2/ippdb/ippdb.pc.in
===================================================================
--- /tags/rel-0_0_2/ippdb/ippdb.pc.in	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/ippdb.pc.in	(revision 7503)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE@
+Description: some library
+Version: @VERSION@
+Requires: pslib
+Libs: -L${libdir} -l@PACKAGE@
+Cflags: -I${includedir}
Index: /tags/rel-0_0_2/ippdb/src/Makefile.am
===================================================================
--- /tags/rel-0_0_2/ippdb/src/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/src/Makefile.am	(revision 7503)
@@ -0,0 +1,10 @@
+AM_CFLAGS = @ippdb_CFLAGS@
+
+include_HEADERS = ippdb.h
+lib_LTLIBRARIES = libippdb.la
+libippdb_la_CFLAGS  = $(PSLIB_CFLAGS) $(AM_CFLAGS)
+libippdb_la_LIBS    = $(PSLIB_LIBS) $(AM_LIBS)
+libippdb_la_LDFLAGS = -release $(PACKAGE_VERSION)
+libippdb_la_SOURCES = \
+    ippdb.h \
+    ippdb.c
Index: /tags/rel-0_0_2/ippdb/src/ippdb.c
===================================================================
--- /tags/rel-0_0_2/ippdb/src/ippdb.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/src/ippdb.c	(revision 7503)
@@ -0,0 +1,14492 @@
+#include "ippdb.h"
+
+#define WEATHER_TABLE_NAME "weather"
+#define WEATHER_INDEX_NAME "position"
+#define SKYP_TRANSPARENCY_TABLE_NAME "skyp_transparency"
+#define SKYP_TRANSPARENCY_INDEX_NAME "position"
+#define SKYP_ABSORPTION_TABLE_NAME "skyp_absorption"
+#define SKYP_ABSORPTION_INDEX_NAME "position"
+#define SKYP_EMISSION_TABLE_NAME "skyp_emission"
+#define SKYP_EMISSION_INDEX_NAME "position"
+#define DIMM_TABLE_NAME "dimm"
+#define DIMM_INDEX_NAME "position"
+#define SKYP_IR_TABLE_NAME "skyp_ir"
+#define SKYP_IR_INDEX_NAME "position"
+#define DOME_TABLE_NAME "dome"
+#define DOME_INDEX_NAME "position"
+#define TELESCOPE_TABLE_NAME "telescope"
+#define TELESCOPE_INDEX_NAME "position"
+#define SUMMITEXP_TABLE_NAME "summitExp"
+#define SUMMITEXP_INDEX_NAME "position"
+#define PZPENDINGEXP_TABLE_NAME "pzPendingExp"
+#define PZPENDINGEXP_INDEX_NAME "position"
+#define PZPENDINGIMFILE_TABLE_NAME "pzPendingImfile"
+#define PZPENDINGIMFILE_INDEX_NAME "position"
+#define NEWEXP_TABLE_NAME "newExp"
+#define NEWEXP_INDEX_NAME "position"
+#define NEWIMFILE_TABLE_NAME "newImfile"
+#define NEWIMFILE_INDEX_NAME "position"
+#define RAWDETRENDEXP_TABLE_NAME "rawDetrendExp"
+#define RAWDETRENDEXP_INDEX_NAME "position"
+#define RAWSCIENCEEXP_TABLE_NAME "rawScienceExp"
+#define RAWSCIENCEEXP_INDEX_NAME "position"
+#define RAWIMFILE_TABLE_NAME "rawImfile"
+#define RAWIMFILE_INDEX_NAME "position"
+#define P1PENDINGEXP_TABLE_NAME "p1PendingExp"
+#define P1PENDINGEXP_INDEX_NAME "position"
+#define P2PENDINGEXP_TABLE_NAME "p2PendingExp"
+#define P2PENDINGEXP_INDEX_NAME "position"
+#define P2PENDINGIMFILE_TABLE_NAME "p2PendingImfile"
+#define P2PENDINGIMFILE_INDEX_NAME "position"
+#define P2DONEEXP_TABLE_NAME "p2DoneExp"
+#define P2DONEEXP_INDEX_NAME "position"
+#define P2DONEIMFILE_TABLE_NAME "p2DoneImfile"
+#define P2DONEIMFILE_INDEX_NAME "position"
+#define P3PENDINGEXP_TABLE_NAME "p3PendingExp"
+#define P3PENDINGEXP_INDEX_NAME "position"
+#define DETRUN_TABLE_NAME "detRun"
+#define DETRUN_INDEX_NAME "position"
+#define DETINPUTEXP_TABLE_NAME "detInputExp"
+#define DETINPUTEXP_INDEX_NAME "position"
+#define DETPROCESSEDIMFILE_TABLE_NAME "detProcessedImfile"
+#define DETPROCESSEDIMFILE_INDEX_NAME "position"
+#define DETSTACKEDIMFILE_TABLE_NAME "detStackedImfile"
+#define DETSTACKEDIMFILE_INDEX_NAME "position"
+#define DETNORMALIZEDIMFILE_TABLE_NAME "detNormalizedImfile"
+#define DETNORMALIZEDIMFILE_INDEX_NAME "position"
+#define DETMASTERFRAME_TABLE_NAME "detMasterFrame"
+#define DETMASTERFRAME_INDEX_NAME "position"
+#define DETMASTERIMFILE_TABLE_NAME "detMasterImfile"
+#define DETMASTERIMFILE_INDEX_NAME "position"
+#define DETRESIDIMFILEANALYSIS_TABLE_NAME "detResidImfileAnalysis"
+#define DETRESIDIMFILEANALYSIS_INDEX_NAME "position"
+#define DETRESIDEXPANALYSIS_TABLE_NAME "detResidExpAnalysis"
+#define DETRESIDEXPANALYSIS_INDEX_NAME "position"
+#define MAX_STRING_LENGTH 1024
+
+psDB *ippdbInit(const char *host, const char *user, const char *passwd, const char *dbname)
+{
+    return psDBInit(host, user, passwd, dbname);
+}
+
+void ippdbCleanup(psDB *dbh)
+{
+    psDBCleanup(dbh);
+}
+
+static void weatherRowFree(weatherRow *object);
+
+weatherRow *weatherRowAlloc(psF32 temp01, psF32 humi01, psF32 temp02, psF32 humi02, psF32 temp03, psF32 humi03, psF32 pressure)
+{
+    weatherRow      *object;
+
+    object = psAlloc(sizeof(weatherRow));
+    psMemSetDeallocator(object, (psFreeFunc)weatherRowFree);
+
+    object->temp01 = temp01;
+    object->humi01 = humi01;
+    object->temp02 = temp02;
+    object->humi02 = humi02;
+    object->temp03 = temp03;
+    object->humi03 = humi03;
+    object->pressure = pressure;
+
+    return object;
+}
+
+static void weatherRowFree(weatherRow *object)
+{
+}
+
+bool weatherCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, WEATHER_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", WEATHER_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item pressure");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, WEATHER_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool weatherDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, WEATHER_TABLE_NAME);
+}
+
+bool weatherInsert(psDB * dbh, psF32 temp01, psF32 humi01, psF32 temp02, psF32 humi02, psF32 temp03, psF32 humi03, psF32 pressure)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, temp01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, humi01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, temp02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, humi02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, temp03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, humi03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, pressure)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item pressure");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, WEATHER_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool weatherPop(psDB *dbh, psF32 *temp01, psF32 *humi01, psF32 *temp02, psF32 *humi02, psF32 *temp03, psF32 *humi03, psF32 *pressure)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, WEATHER_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", WEATHER_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, WEATHER_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", WEATHER_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, WEATHER_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, WEATHER_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *temp01 = psMetadataLookupF32(&status, row, "temp01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp01");
+        psFree(row);
+        return false;
+    }
+    *humi01 = psMetadataLookupF32(&status, row, "humi01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi01");
+        psFree(row);
+        return false;
+    }
+    *temp02 = psMetadataLookupF32(&status, row, "temp02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp02");
+        psFree(row);
+        return false;
+    }
+    *humi02 = psMetadataLookupF32(&status, row, "humi02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi02");
+        psFree(row);
+        return false;
+    }
+    *temp03 = psMetadataLookupF32(&status, row, "temp03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp03");
+        psFree(row);
+        return false;
+    }
+    *humi03 = psMetadataLookupF32(&status, row, "humi03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi03");
+        psFree(row);
+        return false;
+    }
+    *pressure = psMetadataLookupF32(&status, row, "pressure");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item pressure");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool weatherInsertObject(psDB *dbh, weatherRow *object)
+{
+    return weatherInsert(dbh, object->temp01, object->humi01, object->temp02, object->humi02, object->temp03, object->humi03, object->pressure);
+}
+
+weatherRow *weatherPopObject(psDB *dbh)
+{
+    psF32           temp01;
+    psF32           humi01;
+    psF32           temp02;
+    psF32           humi02;
+    psF32           temp03;
+    psF32           humi03;
+    psF32           pressure;
+
+    if (!weatherPop(dbh, &temp01, &humi01, &temp02, &humi02, &temp03, &humi03, &pressure)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return weatherRowAlloc(temp01, humi01, temp02, humi02, temp03, humi03, pressure);
+}
+
+bool weatherInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  WEATHER_TABLE_NAME
+    if (!psFitsMoveExtName(fits, WEATHER_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WEATHER_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, WEATHER_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool weatherPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!weatherSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                WEATHER_TABLE_NAME, WEATHER_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool weatherSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WEATHER_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, WEATHER_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", WEATHER_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, WEATHER_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *weatherMetadataFromObject(const weatherRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, object->temp01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp01");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, object->humi01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi01");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, object->temp02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp02");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, object->humi02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi02");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, object->temp03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp03");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, object->humi03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi03");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, object->pressure)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item pressure");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+weatherRow *weatherObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF32           temp01;
+    psF32           humi01;
+    psF32           temp02;
+    psF32           humi02;
+    psF32           temp03;
+    psF32           humi03;
+    psF32           pressure;
+
+    temp01 = psMetadataLookupF32(&status, md, "temp01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp01");
+        return false;
+    }
+    humi01 = psMetadataLookupF32(&status, md, "humi01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi01");
+        return false;
+    }
+    temp02 = psMetadataLookupF32(&status, md, "temp02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp02");
+        return false;
+    }
+    humi02 = psMetadataLookupF32(&status, md, "humi02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi02");
+        return false;
+    }
+    temp03 = psMetadataLookupF32(&status, md, "temp03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp03");
+        return false;
+    }
+    humi03 = psMetadataLookupF32(&status, md, "humi03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi03");
+        return false;
+    }
+    pressure = psMetadataLookupF32(&status, md, "pressure");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item pressure");
+        return false;
+    }
+
+    return weatherRowAlloc(temp01, humi01, temp02, humi02, temp03, humi03, pressure);
+}
+psArray *weatherSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WEATHER_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, WEATHER_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", WEATHER_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, weatherObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long weatherDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        weatherRow *object = objects->data[i];
+        psMetadata *where = weatherMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, WEATHER_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from weather");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_transparencyRowFree(skyp_transparencyRow *object);
+
+skyp_transparencyRow *skyp_transparencyRowAlloc(const char *filter, psF64 trans, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    skyp_transparencyRow *object;
+
+    object = psAlloc(sizeof(skyp_transparencyRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_transparencyRowFree);
+
+    object->filter = psStringCopy(filter);
+    object->trans = trans;
+    object->nstars = nstars;
+    object->ra = ra;
+    object->decl = decl;
+    object->exptime = exptime;
+    object->sky_bright = sky_bright;
+
+    return object;
+}
+
+static void skyp_transparencyRowFree(skyp_transparencyRow *object)
+{
+    psFree(object->filter);
+}
+
+bool skyp_transparencyCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_TRANSPARENCY_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item trans");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_TRANSPARENCY_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_transparencyDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_TRANSPARENCY_TABLE_NAME);
+}
+
+bool skyp_transparencyInsert(psDB * dbh, const char *filter, psF64 trans, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, trans)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item trans");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_TRANSPARENCY_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_transparencyPop(psDB *dbh, char **filter, psF64 *trans, psS32 *nstars, psF64 *ra, psF64 *decl, psF32 *exptime, psF64 *sky_bright)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, SKYP_TRANSPARENCY_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_TRANSPARENCY_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *trans = psMetadataLookupF64(&status, row, "trans");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item trans");
+        psFree(row);
+        return false;
+    }
+    *nstars = psMetadataLookupS32(&status, row, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exptime = psMetadataLookupF32(&status, row, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        psFree(row);
+        return false;
+    }
+    *sky_bright = psMetadataLookupF64(&status, row, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_transparencyInsertObject(psDB *dbh, skyp_transparencyRow *object)
+{
+    return skyp_transparencyInsert(dbh, object->filter, object->trans, object->nstars, object->ra, object->decl, object->exptime, object->sky_bright);
+}
+
+skyp_transparencyRow *skyp_transparencyPopObject(psDB *dbh)
+{
+    char            filter[256];
+    psF64           trans;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    if (!skyp_transparencyPop(dbh, (char **)&filter, &trans, &nstars, &ra, &decl, &exptime, &sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_transparencyRowAlloc(filter, trans, nstars, ra, decl, exptime, sky_bright);
+}
+
+bool skyp_transparencyInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_TRANSPARENCY_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_TRANSPARENCY_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_TRANSPARENCY_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_transparencyPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_transparencySelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                SKYP_TRANSPARENCY_TABLE_NAME, SKYP_TRANSPARENCY_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool skyp_transparencySelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, SKYP_TRANSPARENCY_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_TRANSPARENCY_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_transparencyMetadataFromObject(const skyp_transparencyRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, object->trans)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item trans");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, object->nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, object->exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, object->sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_transparencyRow *skyp_transparencyObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *filter;
+    psF64           trans;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    trans = psMetadataLookupF64(&status, md, "trans");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item trans");
+        return false;
+    }
+    nstars = psMetadataLookupS32(&status, md, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exptime = psMetadataLookupF32(&status, md, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        return false;
+    }
+    sky_bright = psMetadataLookupF64(&status, md, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        return false;
+    }
+
+    return skyp_transparencyRowAlloc(filter, trans, nstars, ra, decl, exptime, sky_bright);
+}
+psArray *skyp_transparencySelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, SKYP_TRANSPARENCY_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, skyp_transparencyObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_transparencyDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_transparencyRow *object = objects->data[i];
+        psMetadata *where = skyp_transparencyMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_transparency");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_absorptionRowFree(skyp_absorptionRow *object);
+
+skyp_absorptionRow *skyp_absorptionRowAlloc(const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    skyp_absorptionRow *object;
+
+    object = psAlloc(sizeof(skyp_absorptionRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_absorptionRowFree);
+
+    object->disperser_id = psStringCopy(disperser_id);
+    object->atmcomp1 = atmcomp1;
+    object->atmcomp2 = atmcomp2;
+    object->atmcomp3 = atmcomp3;
+    object->nstars = nstars;
+    object->ra = ra;
+    object->decl = decl;
+    object->exptime = exptime;
+    object->sky_bright = sky_bright;
+
+    return object;
+}
+
+static void skyp_absorptionRowFree(skyp_absorptionRow *object)
+{
+    psFree(object->disperser_id);
+}
+
+bool skyp_absorptionCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_ABSORPTION_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_ABSORPTION_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_ABSORPTION_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_absorptionDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_ABSORPTION_TABLE_NAME);
+}
+
+bool skyp_absorptionInsert(psDB * dbh, const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, psStringCopy(disperser_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_ABSORPTION_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_absorptionPop(psDB *dbh, char **disperser_id, psF32 *atmcomp1, psF32 *atmcomp2, psF32 *atmcomp3, psS32 *nstars, psF64 *ra, psF64 *decl, psF32 *exptime, psF64 *sky_bright)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_ABSORPTION_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_ABSORPTION_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, SKYP_ABSORPTION_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_ABSORPTION_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_ABSORPTION_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_ABSORPTION_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *disperser_id = psMetadataLookupPtr(&status, row, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        psFree(row);
+        return false;
+    }
+    *atmcomp1 = psMetadataLookupF32(&status, row, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        psFree(row);
+        return false;
+    }
+    *atmcomp2 = psMetadataLookupF32(&status, row, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        psFree(row);
+        return false;
+    }
+    *atmcomp3 = psMetadataLookupF32(&status, row, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        psFree(row);
+        return false;
+    }
+    *nstars = psMetadataLookupS32(&status, row, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exptime = psMetadataLookupF32(&status, row, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        psFree(row);
+        return false;
+    }
+    *sky_bright = psMetadataLookupF64(&status, row, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_absorptionInsertObject(psDB *dbh, skyp_absorptionRow *object)
+{
+    return skyp_absorptionInsert(dbh, object->disperser_id, object->atmcomp1, object->atmcomp2, object->atmcomp3, object->nstars, object->ra, object->decl, object->exptime, object->sky_bright);
+}
+
+skyp_absorptionRow *skyp_absorptionPopObject(psDB *dbh)
+{
+    char            disperser_id[256];
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    if (!skyp_absorptionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &nstars, &ra, &decl, &exptime, &sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_absorptionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, nstars, ra, decl, exptime, sky_bright);
+}
+
+bool skyp_absorptionInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_ABSORPTION_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_ABSORPTION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_ABSORPTION_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SKYP_ABSORPTION_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_absorptionPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_absorptionSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                SKYP_ABSORPTION_TABLE_NAME, SKYP_ABSORPTION_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool skyp_absorptionSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_ABSORPTION_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, SKYP_ABSORPTION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_ABSORPTION_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_ABSORPTION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_absorptionMetadataFromObject(const skyp_absorptionRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, psStringCopy(object->disperser_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, object->atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, object->atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, object->atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, object->nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, object->exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, object->sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_absorptionRow *skyp_absorptionObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    disperser_id = psMetadataLookupPtr(&status, md, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        return false;
+    }
+    atmcomp1 = psMetadataLookupF32(&status, md, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        return false;
+    }
+    atmcomp2 = psMetadataLookupF32(&status, md, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        return false;
+    }
+    atmcomp3 = psMetadataLookupF32(&status, md, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        return false;
+    }
+    nstars = psMetadataLookupS32(&status, md, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exptime = psMetadataLookupF32(&status, md, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        return false;
+    }
+    sky_bright = psMetadataLookupF64(&status, md, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        return false;
+    }
+
+    return skyp_absorptionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, nstars, ra, decl, exptime, sky_bright);
+}
+psArray *skyp_absorptionSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_ABSORPTION_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, SKYP_ABSORPTION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_ABSORPTION_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, skyp_absorptionObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_absorptionDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_absorptionRow *object = objects->data[i];
+        psMetadata *where = skyp_absorptionMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_ABSORPTION_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_absorption");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_emissionRowFree(skyp_emissionRow *object);
+
+skyp_emissionRow *skyp_emissionRowAlloc(const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psF32 continuum, psF32 exptime)
+{
+    skyp_emissionRow *object;
+
+    object = psAlloc(sizeof(skyp_emissionRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_emissionRowFree);
+
+    object->disperser_id = psStringCopy(disperser_id);
+    object->atmcomp1 = atmcomp1;
+    object->atmcomp2 = atmcomp2;
+    object->atmcomp3 = atmcomp3;
+    object->continuum = continuum;
+    object->exptime = exptime;
+
+    return object;
+}
+
+static void skyp_emissionRowFree(skyp_emissionRow *object)
+{
+    psFree(object->disperser_id);
+}
+
+bool skyp_emissionCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_EMISSION_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_EMISSION_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item continuum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_EMISSION_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_emissionDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_EMISSION_TABLE_NAME);
+}
+
+bool skyp_emissionInsert(psDB * dbh, const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psF32 continuum, psF32 exptime)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, psStringCopy(disperser_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, continuum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item continuum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_EMISSION_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_emissionPop(psDB *dbh, char **disperser_id, psF32 *atmcomp1, psF32 *atmcomp2, psF32 *atmcomp3, psF32 *continuum, psF32 *exptime)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_EMISSION_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_EMISSION_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, SKYP_EMISSION_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_EMISSION_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_EMISSION_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_EMISSION_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *disperser_id = psMetadataLookupPtr(&status, row, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        psFree(row);
+        return false;
+    }
+    *atmcomp1 = psMetadataLookupF32(&status, row, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        psFree(row);
+        return false;
+    }
+    *atmcomp2 = psMetadataLookupF32(&status, row, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        psFree(row);
+        return false;
+    }
+    *atmcomp3 = psMetadataLookupF32(&status, row, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        psFree(row);
+        return false;
+    }
+    *continuum = psMetadataLookupF32(&status, row, "continuum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item continuum");
+        psFree(row);
+        return false;
+    }
+    *exptime = psMetadataLookupF32(&status, row, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_emissionInsertObject(psDB *dbh, skyp_emissionRow *object)
+{
+    return skyp_emissionInsert(dbh, object->disperser_id, object->atmcomp1, object->atmcomp2, object->atmcomp3, object->continuum, object->exptime);
+}
+
+skyp_emissionRow *skyp_emissionPopObject(psDB *dbh)
+{
+    char            disperser_id[256];
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psF32           continuum;
+    psF32           exptime;
+
+    if (!skyp_emissionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &continuum, &exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_emissionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, continuum, exptime);
+}
+
+bool skyp_emissionInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_EMISSION_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_EMISSION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_EMISSION_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SKYP_EMISSION_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_emissionPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_emissionSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                SKYP_EMISSION_TABLE_NAME, SKYP_EMISSION_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool skyp_emissionSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_EMISSION_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, SKYP_EMISSION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_EMISSION_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_EMISSION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_emissionMetadataFromObject(const skyp_emissionRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, psStringCopy(object->disperser_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, object->atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, object->atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, object->atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, object->continuum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item continuum");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, object->exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_emissionRow *skyp_emissionObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psF32           continuum;
+    psF32           exptime;
+
+    disperser_id = psMetadataLookupPtr(&status, md, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        return false;
+    }
+    atmcomp1 = psMetadataLookupF32(&status, md, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        return false;
+    }
+    atmcomp2 = psMetadataLookupF32(&status, md, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        return false;
+    }
+    atmcomp3 = psMetadataLookupF32(&status, md, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        return false;
+    }
+    continuum = psMetadataLookupF32(&status, md, "continuum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item continuum");
+        return false;
+    }
+    exptime = psMetadataLookupF32(&status, md, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        return false;
+    }
+
+    return skyp_emissionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, continuum, exptime);
+}
+psArray *skyp_emissionSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_EMISSION_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, SKYP_EMISSION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_EMISSION_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, skyp_emissionObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_emissionDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_emissionRow *object = objects->data[i];
+        psMetadata *where = skyp_emissionMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_EMISSION_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_emission");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void dimmRowFree(dimmRow *object);
+
+dimmRow *dimmRowAlloc(psF32 sigmax, psF32 sigmay, psF32 fwhm, psF64 ra, psF64 decl, psF32 expttime, const char *telescope_id)
+{
+    dimmRow         *object;
+
+    object = psAlloc(sizeof(dimmRow));
+    psMemSetDeallocator(object, (psFreeFunc)dimmRowFree);
+
+    object->sigmax = sigmax;
+    object->sigmay = sigmay;
+    object->fwhm = fwhm;
+    object->ra = ra;
+    object->decl = decl;
+    object->expttime = expttime;
+    object->telescope_id = psStringCopy(telescope_id);
+
+    return object;
+}
+
+static void dimmRowFree(dimmRow *object)
+{
+    psFree(object->telescope_id);
+}
+
+bool dimmCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DIMM_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DIMM_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmax");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmay");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expttime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope_id");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DIMM_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool dimmDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DIMM_TABLE_NAME);
+}
+
+bool dimmInsert(psDB * dbh, psF32 sigmax, psF32 sigmay, psF32 fwhm, psF64 ra, psF64 decl, psF32 expttime, const char *telescope_id)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, sigmax)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmax");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, sigmay)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmay");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, fwhm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, expttime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expttime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, psStringCopy(telescope_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope_id");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DIMM_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool dimmPop(psDB *dbh, psF32 *sigmax, psF32 *sigmay, psF32 *fwhm, psF64 *ra, psF64 *decl, psF32 *expttime, char **telescope_id)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DIMM_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DIMM_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DIMM_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DIMM_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DIMM_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DIMM_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *sigmax = psMetadataLookupF32(&status, row, "sigmax");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmax");
+        psFree(row);
+        return false;
+    }
+    *sigmay = psMetadataLookupF32(&status, row, "sigmay");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmay");
+        psFree(row);
+        return false;
+    }
+    *fwhm = psMetadataLookupF32(&status, row, "fwhm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *expttime = psMetadataLookupF32(&status, row, "expttime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expttime");
+        psFree(row);
+        return false;
+    }
+    *telescope_id = psMetadataLookupPtr(&status, row, "telescope_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope_id");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool dimmInsertObject(psDB *dbh, dimmRow *object)
+{
+    return dimmInsert(dbh, object->sigmax, object->sigmay, object->fwhm, object->ra, object->decl, object->expttime, object->telescope_id);
+}
+
+dimmRow *dimmPopObject(psDB *dbh)
+{
+    psF32           sigmax;
+    psF32           sigmay;
+    psF32           fwhm;
+    psF64           ra;
+    psF64           decl;
+    psF32           expttime;
+    char            telescope_id[256];
+
+    if (!dimmPop(dbh, &sigmax, &sigmay, &fwhm, &ra, &decl, &expttime, (char **)&telescope_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return dimmRowAlloc(sigmax, sigmay, fwhm, ra, decl, expttime, telescope_id);
+}
+
+bool dimmInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DIMM_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DIMM_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIMM_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DIMM_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool dimmPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!dimmSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DIMM_TABLE_NAME, DIMM_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool dimmSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIMM_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DIMM_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DIMM_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DIMM_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *dimmMetadataFromObject(const dimmRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, object->sigmax)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmax");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, object->sigmay)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmay");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, object->fwhm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, object->expttime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expttime");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, psStringCopy(object->telescope_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope_id");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+dimmRow *dimmObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF32           sigmax;
+    psF32           sigmay;
+    psF32           fwhm;
+    psF64           ra;
+    psF64           decl;
+    psF32           expttime;
+    char            *telescope_id;
+
+    sigmax = psMetadataLookupF32(&status, md, "sigmax");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmax");
+        return false;
+    }
+    sigmay = psMetadataLookupF32(&status, md, "sigmay");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmay");
+        return false;
+    }
+    fwhm = psMetadataLookupF32(&status, md, "fwhm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    expttime = psMetadataLookupF32(&status, md, "expttime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expttime");
+        return false;
+    }
+    telescope_id = psMetadataLookupPtr(&status, md, "telescope_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope_id");
+        return false;
+    }
+
+    return dimmRowAlloc(sigmax, sigmay, fwhm, ra, decl, expttime, telescope_id);
+}
+psArray *dimmSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIMM_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DIMM_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DIMM_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, dimmObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long dimmDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        dimmRow *object = objects->data[i];
+        psMetadata *where = dimmMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DIMM_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from dimm");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_irRowFree(skyp_irRow *object);
+
+skyp_irRow *skyp_irRowAlloc(psF64 sky_bright, psF64 sky_var, psF64 ra, psF64 decl, psF32 fov_x, psF32 fov_y)
+{
+    skyp_irRow      *object;
+
+    object = psAlloc(sizeof(skyp_irRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_irRowFree);
+
+    object->sky_bright = sky_bright;
+    object->sky_var = sky_var;
+    object->ra = ra;
+    object->decl = decl;
+    object->fov_x = fov_x;
+    object->fov_y = fov_y;
+
+    return object;
+}
+
+static void skyp_irRowFree(skyp_irRow *object)
+{
+}
+
+bool skyp_irCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_IR_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_IR_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_var");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_x");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_y");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_IR_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_irDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_IR_TABLE_NAME);
+}
+
+bool skyp_irInsert(psDB * dbh, psF64 sky_bright, psF64 sky_var, psF64 ra, psF64 decl, psF32 fov_x, psF32 fov_y)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, sky_var)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_var");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, fov_x)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_x");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, fov_y)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_y");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_IR_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_irPop(psDB *dbh, psF64 *sky_bright, psF64 *sky_var, psF64 *ra, psF64 *decl, psF32 *fov_x, psF32 *fov_y)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_IR_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_IR_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, SKYP_IR_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_IR_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_IR_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_IR_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *sky_bright = psMetadataLookupF64(&status, row, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        psFree(row);
+        return false;
+    }
+    *sky_var = psMetadataLookupF64(&status, row, "sky_var");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_var");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *fov_x = psMetadataLookupF32(&status, row, "fov_x");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_x");
+        psFree(row);
+        return false;
+    }
+    *fov_y = psMetadataLookupF32(&status, row, "fov_y");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_y");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_irInsertObject(psDB *dbh, skyp_irRow *object)
+{
+    return skyp_irInsert(dbh, object->sky_bright, object->sky_var, object->ra, object->decl, object->fov_x, object->fov_y);
+}
+
+skyp_irRow *skyp_irPopObject(psDB *dbh)
+{
+    psF64           sky_bright;
+    psF64           sky_var;
+    psF64           ra;
+    psF64           decl;
+    psF32           fov_x;
+    psF32           fov_y;
+
+    if (!skyp_irPop(dbh, &sky_bright, &sky_var, &ra, &decl, &fov_x, &fov_y)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_irRowAlloc(sky_bright, sky_var, ra, decl, fov_x, fov_y);
+}
+
+bool skyp_irInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_IR_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_IR_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_IR_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SKYP_IR_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_irPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_irSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                SKYP_IR_TABLE_NAME, SKYP_IR_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool skyp_irSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_IR_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, SKYP_IR_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_IR_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_IR_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_irMetadataFromObject(const skyp_irRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, object->sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, object->sky_var)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_var");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, object->fov_x)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_x");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, object->fov_y)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_y");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_irRow *skyp_irObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF64           sky_bright;
+    psF64           sky_var;
+    psF64           ra;
+    psF64           decl;
+    psF32           fov_x;
+    psF32           fov_y;
+
+    sky_bright = psMetadataLookupF64(&status, md, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        return false;
+    }
+    sky_var = psMetadataLookupF64(&status, md, "sky_var");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_var");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    fov_x = psMetadataLookupF32(&status, md, "fov_x");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_x");
+        return false;
+    }
+    fov_y = psMetadataLookupF32(&status, md, "fov_y");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_y");
+        return false;
+    }
+
+    return skyp_irRowAlloc(sky_bright, sky_var, ra, decl, fov_x, fov_y);
+}
+psArray *skyp_irSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_IR_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, SKYP_IR_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_IR_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, skyp_irObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_irDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_irRow *object = objects->data[i];
+        psMetadata *where = skyp_irMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_IR_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_ir");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void domeRowFree(domeRow *object);
+
+domeRow *domeRowAlloc(psF32 az, bool open, bool light, bool track)
+{
+    domeRow         *object;
+
+    object = psAlloc(sizeof(domeRow));
+    psMemSetDeallocator(object, (psFreeFunc)domeRowFree);
+
+    object->az = az;
+    object->open = open;
+    object->light = light;
+    object->track = track;
+
+    return object;
+}
+
+static void domeRowFree(domeRow *object)
+{
+}
+
+bool domeCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DOME_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DOME_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item open");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item light");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item track");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DOME_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool domeDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DOME_TABLE_NAME);
+}
+
+bool domeInsert(psDB * dbh, psF32 az, bool open, bool light, bool track)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, open)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item open");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, light)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item light");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, track)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item track");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DOME_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool domePop(psDB *dbh, psF32 *az, bool *open, bool *light, bool *track)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DOME_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DOME_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DOME_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DOME_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DOME_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DOME_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *az = psMetadataLookupF32(&status, row, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        psFree(row);
+        return false;
+    }
+    *open = psMetadataLookupBool(&status, row, "open");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item open");
+        psFree(row);
+        return false;
+    }
+    *light = psMetadataLookupBool(&status, row, "light");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item light");
+        psFree(row);
+        return false;
+    }
+    *track = psMetadataLookupBool(&status, row, "track");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item track");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool domeInsertObject(psDB *dbh, domeRow *object)
+{
+    return domeInsert(dbh, object->az, object->open, object->light, object->track);
+}
+
+domeRow *domePopObject(psDB *dbh)
+{
+    psF32           az;
+    bool            open;
+    bool            light;
+    bool            track;
+
+    if (!domePop(dbh, &az, &open, &light, &track)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return domeRowAlloc(az, open, light, track);
+}
+
+bool domeInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DOME_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DOME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DOME_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DOME_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool domePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!domeSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DOME_TABLE_NAME, DOME_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool domeSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DOME_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DOME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DOME_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DOME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *domeMetadataFromObject(const domeRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, object->az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, object->open)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item open");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, object->light)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item light");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, object->track)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item track");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+domeRow *domeObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF32           az;
+    bool            open;
+    bool            light;
+    bool            track;
+
+    az = psMetadataLookupF32(&status, md, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        return false;
+    }
+    open = psMetadataLookupBool(&status, md, "open");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item open");
+        return false;
+    }
+    light = psMetadataLookupBool(&status, md, "light");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item light");
+        return false;
+    }
+    track = psMetadataLookupBool(&status, md, "track");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item track");
+        return false;
+    }
+
+    return domeRowAlloc(az, open, light, track);
+}
+psArray *domeSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DOME_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DOME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DOME_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, domeObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long domeDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        domeRow *object = objects->data[i];
+        psMetadata *where = domeMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DOME_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from dome");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void telescopeRowFree(telescopeRow *object);
+
+telescopeRow *telescopeRowAlloc(const char *guide, psF32 alt, psF32 az, psF64 ra, psF64 decl)
+{
+    telescopeRow    *object;
+
+    object = psAlloc(sizeof(telescopeRow));
+    psMemSetDeallocator(object, (psFreeFunc)telescopeRowFree);
+
+    object->guide = psStringCopy(guide);
+    object->alt = alt;
+    object->az = az;
+    object->ra = ra;
+    object->decl = decl;
+
+    return object;
+}
+
+static void telescopeRowFree(telescopeRow *object)
+{
+    psFree(object->guide);
+}
+
+bool telescopeCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, TELESCOPE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", TELESCOPE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, TELESCOPE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool telescopeDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, TELESCOPE_TABLE_NAME);
+}
+
+bool telescopeInsert(psDB * dbh, const char *guide, psF32 alt, psF32 az, psF64 ra, psF64 decl)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, psStringCopy(guide))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, TELESCOPE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool telescopePop(psDB *dbh, char **guide, psF32 *alt, psF32 *az, psF64 *ra, psF64 *decl)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, TELESCOPE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", TELESCOPE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, TELESCOPE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", TELESCOPE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, TELESCOPE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, TELESCOPE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *guide = psMetadataLookupPtr(&status, row, "guide");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide");
+        psFree(row);
+        return false;
+    }
+    *alt = psMetadataLookupF32(&status, row, "alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
+        psFree(row);
+        return false;
+    }
+    *az = psMetadataLookupF32(&status, row, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool telescopeInsertObject(psDB *dbh, telescopeRow *object)
+{
+    return telescopeInsert(dbh, object->guide, object->alt, object->az, object->ra, object->decl);
+}
+
+telescopeRow *telescopePopObject(psDB *dbh)
+{
+    char            guide[256];
+    psF32           alt;
+    psF32           az;
+    psF64           ra;
+    psF64           decl;
+
+    if (!telescopePop(dbh, (char **)&guide, &alt, &az, &ra, &decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return telescopeRowAlloc(guide, alt, az, ra, decl);
+}
+
+bool telescopeInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  TELESCOPE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, TELESCOPE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", TELESCOPE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, TELESCOPE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool telescopePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!telescopeSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                TELESCOPE_TABLE_NAME, TELESCOPE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool telescopeSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, TELESCOPE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, TELESCOPE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", TELESCOPE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, TELESCOPE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *telescopeMetadataFromObject(const telescopeRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, psStringCopy(object->guide))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, object->alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, object->az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+telescopeRow *telescopeObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *guide;
+    psF32           alt;
+    psF32           az;
+    psF64           ra;
+    psF64           decl;
+
+    guide = psMetadataLookupPtr(&status, md, "guide");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide");
+        return false;
+    }
+    alt = psMetadataLookupF32(&status, md, "alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
+        return false;
+    }
+    az = psMetadataLookupF32(&status, md, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+
+    return telescopeRowAlloc(guide, alt, az, ra, decl);
+}
+psArray *telescopeSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, TELESCOPE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, TELESCOPE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", TELESCOPE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, telescopeObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long telescopeDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        telescopeRow *object = objects->data[i];
+        psMetadata *where = telescopeMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, TELESCOPE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from telescope");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void summitExpRowFree(summitExpRow *object);
+
+summitExpRow *summitExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, const char *uri)
+{
+    summitExpRow    *object;
+
+    object = psAlloc(sizeof(summitExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)summitExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->uri = psStringCopy(uri);
+
+    return object;
+}
+
+static void summitExpRowFree(summitExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->uri);
+}
+
+bool summitExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SUMMITEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SUMMITEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SUMMITEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool summitExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SUMMITEXP_TABLE_NAME);
+}
+
+bool summitExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SUMMITEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool summitExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SUMMITEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, SUMMITEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SUMMITEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SUMMITEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool summitExpInsertObject(psDB *dbh, summitExpRow *object)
+{
+    return summitExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->uri);
+}
+
+summitExpRow *summitExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    char            uri[256];
+
+    if (!summitExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return summitExpRowAlloc(exp_id, camera, telescope, exp_type, uri);
+}
+
+bool summitExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SUMMITEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SUMMITEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SUMMITEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SUMMITEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool summitExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!summitExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                SUMMITEXP_TABLE_NAME, SUMMITEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool summitExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, SUMMITEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SUMMITEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SUMMITEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *summitExpMetadataFromObject(const summitExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+summitExpRow *summitExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return summitExpRowAlloc(exp_id, camera, telescope, exp_type, uri);
+}
+psArray *summitExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, SUMMITEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SUMMITEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, summitExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long summitExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        summitExpRow *object = objects->data[i];
+        psMetadata *where = summitExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void pzPendingExpRowFree(pzPendingExpRow *object);
+
+pzPendingExpRow *pzPendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    pzPendingExpRow *object;
+
+    object = psAlloc(sizeof(pzPendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)pzPendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+
+    return object;
+}
+
+static void pzPendingExpRowFree(pzPendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+}
+
+bool pzPendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, PZPENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", PZPENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, PZPENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool pzPendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, PZPENDINGEXP_TABLE_NAME);
+}
+
+bool pzPendingExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, PZPENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool pzPendingExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, PZPENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, PZPENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, PZPENDINGEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool pzPendingExpInsertObject(psDB *dbh, pzPendingExpRow *object)
+{
+    return pzPendingExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles);
+}
+
+pzPendingExpRow *pzPendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+
+    if (!pzPendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return pzPendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+
+bool pzPendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  PZPENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, PZPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZPENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, PZPENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool pzPendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!pzPendingExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                PZPENDINGEXP_TABLE_NAME, PZPENDINGEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool pzPendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, PZPENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, PZPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *pzPendingExpMetadataFromObject(const pzPendingExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+pzPendingExpRow *pzPendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+
+    return pzPendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+psArray *pzPendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, PZPENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, pzPendingExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long pzPendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        pzPendingExpRow *object = objects->data[i];
+        psMetadata *where = pzPendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void pzPendingImfileRowFree(pzPendingImfileRow *object);
+
+pzPendingImfileRow *pzPendingImfileRowAlloc(const char *exp_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri)
+{
+    pzPendingImfileRow *object;
+
+    object = psAlloc(sizeof(pzPendingImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)pzPendingImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->bytes = bytes;
+    object->md5sum = psStringCopy(md5sum);
+    object->class = psStringCopy(class);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+
+    return object;
+}
+
+static void pzPendingImfileRowFree(pzPendingImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->md5sum);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool pzPendingImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, PZPENDINGIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", PZPENDINGIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, "32")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, PZPENDINGIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool pzPendingImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, PZPENDINGIMFILE_TABLE_NAME);
+}
+
+bool pzPendingImfileInsert(psDB * dbh, const char *exp_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, bytes)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, psStringCopy(md5sum))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, psStringCopy(class))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, PZPENDINGIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool pzPendingImfilePop(psDB *dbh, char **exp_id, psS32 *bytes, char **md5sum, char **class, char **class_id, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, PZPENDINGIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, PZPENDINGIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, PZPENDINGIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *bytes = psMetadataLookupS32(&status, row, "bytes");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bytes");
+        psFree(row);
+        return false;
+    }
+    *md5sum = psMetadataLookupPtr(&status, row, "md5sum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item md5sum");
+        psFree(row);
+        return false;
+    }
+    *class = psMetadataLookupPtr(&status, row, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool pzPendingImfileInsertObject(psDB *dbh, pzPendingImfileRow *object)
+{
+    return pzPendingImfileInsert(dbh, object->exp_id, object->bytes, object->md5sum, object->class, object->class_id, object->uri);
+}
+
+pzPendingImfileRow *pzPendingImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    psS32           bytes;
+    char            md5sum[256];
+    char            class[256];
+    char            class_id[256];
+    char            uri[256];
+
+    if (!pzPendingImfilePop(dbh, (char **)&exp_id, &bytes, (char **)&md5sum, (char **)&class, (char **)&class_id, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return pzPendingImfileRowAlloc(exp_id, bytes, md5sum, class, class_id, uri);
+}
+
+bool pzPendingImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  PZPENDINGIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, PZPENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZPENDINGIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, PZPENDINGIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool pzPendingImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!pzPendingImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                PZPENDINGIMFILE_TABLE_NAME, PZPENDINGIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool pzPendingImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, PZPENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, PZPENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *pzPendingImfileMetadataFromObject(const pzPendingImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, object->bytes)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, psStringCopy(object->md5sum))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, psStringCopy(object->class))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+pzPendingImfileRow *pzPendingImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    psS32           bytes;
+    char            *md5sum;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    bytes = psMetadataLookupS32(&status, md, "bytes");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bytes");
+        return false;
+    }
+    md5sum = psMetadataLookupPtr(&status, md, "md5sum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item md5sum");
+        return false;
+    }
+    class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return pzPendingImfileRowAlloc(exp_id, bytes, md5sum, class, class_id, uri);
+}
+psArray *pzPendingImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, PZPENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, pzPendingImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long pzPendingImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        pzPendingImfileRow *object = objects->data[i];
+        psMetadata *where = pzPendingImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void newExpRowFree(newExpRow *object);
+
+newExpRow *newExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    newExpRow       *object;
+
+    object = psAlloc(sizeof(newExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)newExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+
+    return object;
+}
+
+static void newExpRowFree(newExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+}
+
+bool newExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, NEWEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", NEWEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, NEWEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool newExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, NEWEXP_TABLE_NAME);
+}
+
+bool newExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, NEWEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool newExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, NEWEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, NEWEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool newExpInsertObject(psDB *dbh, newExpRow *object)
+{
+    return newExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles);
+}
+
+newExpRow *newExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+
+    if (!newExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return newExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+
+bool newExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  NEWEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, NEWEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", NEWEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, NEWEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool newExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!newExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                NEWEXP_TABLE_NAME, NEWEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool newExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, NEWEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, NEWEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *newExpMetadataFromObject(const newExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+newExpRow *newExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+
+    return newExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+psArray *newExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, NEWEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, newExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long newExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        newExpRow *object = objects->data[i];
+        psMetadata *where = newExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void newImfileRowFree(newImfileRow *object);
+
+newImfileRow *newImfileRowAlloc(const char *exp_id, const char *class, const char *class_id, const char *uri)
+{
+    newImfileRow    *object;
+
+    object = psAlloc(sizeof(newImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)newImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->class = psStringCopy(class);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+
+    return object;
+}
+
+static void newImfileRowFree(newImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool newImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, NEWIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", NEWIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, NEWIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool newImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, NEWIMFILE_TABLE_NAME);
+}
+
+bool newImfileInsert(psDB * dbh, const char *exp_id, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, psStringCopy(class))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, NEWIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool newImfilePop(psDB *dbh, char **exp_id, char **class, char **class_id, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, NEWIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, NEWIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class = psMetadataLookupPtr(&status, row, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool newImfileInsertObject(psDB *dbh, newImfileRow *object)
+{
+    return newImfileInsert(dbh, object->exp_id, object->class, object->class_id, object->uri);
+}
+
+newImfileRow *newImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class[256];
+    char            class_id[256];
+    char            uri[256];
+
+    if (!newImfilePop(dbh, (char **)&exp_id, (char **)&class, (char **)&class_id, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return newImfileRowAlloc(exp_id, class, class_id, uri);
+}
+
+bool newImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  NEWIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, NEWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", NEWIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, NEWIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool newImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!newImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                NEWIMFILE_TABLE_NAME, NEWIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool newImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, NEWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, NEWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *newImfileMetadataFromObject(const newImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, psStringCopy(object->class))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+newImfileRow *newImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return newImfileRowAlloc(exp_id, class, class_id, uri);
+}
+psArray *newImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, NEWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, newImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long newImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        newImfileRow *object = objects->data[i];
+        psMetadata *where = newImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void rawDetrendExpRowFree(rawDetrendExpRow *object);
+
+rawDetrendExpRow *rawDetrendExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats)
+{
+    rawDetrendExpRow *object;
+
+    object = psAlloc(sizeof(rawDetrendExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)rawDetrendExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+
+    return object;
+}
+
+static void rawDetrendExpRowFree(rawDetrendExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+}
+
+bool rawDetrendExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, RAWDETRENDEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", RAWDETRENDEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, RAWDETRENDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawDetrendExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWDETRENDEXP_TABLE_NAME);
+}
+
+bool rawDetrendExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, RAWDETRENDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool rawDetrendExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, char **stats)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, RAWDETRENDEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWDETRENDEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, RAWDETRENDEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWDETRENDEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, RAWDETRENDEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, RAWDETRENDEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool rawDetrendExpInsertObject(psDB *dbh, rawDetrendExpRow *object)
+{
+    return rawDetrendExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->stats);
+}
+
+rawDetrendExpRow *rawDetrendExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+
+    if (!rawDetrendExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return rawDetrendExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats);
+}
+
+bool rawDetrendExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWDETRENDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWDETRENDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWDETRENDEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, RAWDETRENDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawDetrendExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!rawDetrendExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                RAWDETRENDEXP_TABLE_NAME, RAWDETRENDEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool rawDetrendExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWDETRENDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, RAWDETRENDEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWDETRENDEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWDETRENDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawDetrendExpMetadataFromObject(const rawDetrendExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+rawDetrendExpRow *rawDetrendExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+
+    return rawDetrendExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats);
+}
+psArray *rawDetrendExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWDETRENDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, RAWDETRENDEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWDETRENDEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, rawDetrendExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long rawDetrendExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawDetrendExpRow *object = objects->data[i];
+        psMetadata *where = rawDetrendExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWDETRENDEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawDetrendExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void rawScienceExpRowFree(rawScienceExpRow *object);
+
+rawScienceExpRow *rawScienceExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats)
+{
+    rawScienceExpRow *object;
+
+    object = psAlloc(sizeof(rawScienceExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)rawScienceExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+
+    return object;
+}
+
+static void rawScienceExpRowFree(rawScienceExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+}
+
+bool rawScienceExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, RAWSCIENCEEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", RAWSCIENCEEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, RAWSCIENCEEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawScienceExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWSCIENCEEXP_TABLE_NAME);
+}
+
+bool rawScienceExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, RAWSCIENCEEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool rawScienceExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, char **stats)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, RAWSCIENCEEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWSCIENCEEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, RAWSCIENCEEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWSCIENCEEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, RAWSCIENCEEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, RAWSCIENCEEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool rawScienceExpInsertObject(psDB *dbh, rawScienceExpRow *object)
+{
+    return rawScienceExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->stats);
+}
+
+rawScienceExpRow *rawScienceExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+
+    if (!rawScienceExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return rawScienceExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats);
+}
+
+bool rawScienceExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWSCIENCEEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWSCIENCEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWSCIENCEEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, RAWSCIENCEEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawScienceExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!rawScienceExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                RAWSCIENCEEXP_TABLE_NAME, RAWSCIENCEEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool rawScienceExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWSCIENCEEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, RAWSCIENCEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWSCIENCEEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWSCIENCEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawScienceExpMetadataFromObject(const rawScienceExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+rawScienceExpRow *rawScienceExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+
+    return rawScienceExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats);
+}
+psArray *rawScienceExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWSCIENCEEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, RAWSCIENCEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWSCIENCEEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, rawScienceExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long rawScienceExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawScienceExpRow *object = objects->data[i];
+        psMetadata *where = rawScienceExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWSCIENCEEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawScienceExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void rawImfileRowFree(rawImfileRow *object);
+
+rawImfileRow *rawImfileRowAlloc(const char *exp_id, const char *class_id, const char *uri, const char *stats)
+{
+    rawImfileRow    *object;
+
+    object = psAlloc(sizeof(rawImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)rawImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+
+    return object;
+}
+
+static void rawImfileRowFree(rawImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+}
+
+bool rawImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, RAWIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", RAWIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, RAWIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWIMFILE_TABLE_NAME);
+}
+
+bool rawImfileInsert(psDB * dbh, const char *exp_id, const char *class_id, const char *uri, const char *stats)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, RAWIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool rawImfilePop(psDB *dbh, char **exp_id, char **class_id, char **uri, char **stats)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, RAWIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, RAWIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool rawImfileInsertObject(psDB *dbh, rawImfileRow *object)
+{
+    return rawImfileInsert(dbh, object->exp_id, object->class_id, object->uri, object->stats);
+}
+
+rawImfileRow *rawImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+
+    if (!rawImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return rawImfileRowAlloc(exp_id, class_id, uri, stats);
+}
+
+bool rawImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, RAWIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!rawImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                RAWIMFILE_TABLE_NAME, RAWIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool rawImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, RAWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawImfileMetadataFromObject(const rawImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+rawImfileRow *rawImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+
+    return rawImfileRowAlloc(exp_id, class_id, uri, stats);
+}
+psArray *rawImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, RAWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, rawImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long rawImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawImfileRow *object = objects->data[i];
+        psMetadata *where = rawImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p1PendingExpRowFree(p1PendingExpRow *object);
+
+p1PendingExpRow *p1PendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p1_version)
+{
+    p1PendingExpRow *object;
+
+    object = psAlloc(sizeof(p1PendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p1PendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+
+    return object;
+}
+
+static void p1PendingExpRowFree(p1PendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool p1PendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P1PENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P1PENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P1PENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p1PendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P1PENDINGEXP_TABLE_NAME);
+}
+
+bool p1PendingExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p1_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P1PENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool p1PendingExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, char **stats, char **recipe, psS32 *p1_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P1PENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P1PENDINGEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, P1PENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P1PENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P1PENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P1PENDINGEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p1PendingExpInsertObject(psDB *dbh, p1PendingExpRow *object)
+{
+    return p1PendingExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->stats, object->recipe, object->p1_version);
+}
+
+p1PendingExpRow *p1PendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+    char            recipe[256];
+    psS32           p1_version;
+
+    if (!p1PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p1PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats, recipe, p1_version);
+}
+
+bool p1PendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P1PENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P1PENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P1PENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, P1PENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p1PendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p1PendingExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                P1PENDINGEXP_TABLE_NAME, P1PENDINGEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool p1PendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P1PENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, P1PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P1PENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P1PENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p1PendingExpMetadataFromObject(const p1PendingExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p1PendingExpRow *p1PendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+
+    return p1PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats, recipe, p1_version);
+}
+psArray *p1PendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P1PENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P1PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P1PENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, p1PendingExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p1PendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p1PendingExpRow *object = objects->data[i];
+        psMetadata *where = p1PendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P1PENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p1PendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p2PendingExpRowFree(p2PendingExpRow *object);
+
+p2PendingExpRow *p2PendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2PendingExpRow *object;
+
+    object = psAlloc(sizeof(p2PendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2PendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+    object->p2_version = p2_version;
+
+    return object;
+}
+
+static void p2PendingExpRowFree(p2PendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool p2PendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2PENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2PENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P2PENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p2PendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P2PENDINGEXP_TABLE_NAME);
+}
+
+bool p2PendingExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P2PENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool p2PendingExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, char **stats, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, P2PENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2PENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2PENDINGEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2PendingExpInsertObject(psDB *dbh, p2PendingExpRow *object)
+{
+    return p2PendingExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->stats, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2PendingExpRow *p2PendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats, recipe, p1_version, p2_version);
+}
+
+bool p2PendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P2PENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P2PENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2PENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, P2PENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p2PendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2PendingExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                P2PENDINGEXP_TABLE_NAME, P2PENDINGEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool p2PendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, P2PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P2PENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p2PendingExpMetadataFromObject(const p2PendingExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2PendingExpRow *p2PendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    p2_version = psMetadataLookupS32(&status, md, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        return false;
+    }
+
+    return p2PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats, recipe, p1_version, p2_version);
+}
+psArray *p2PendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P2PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, p2PendingExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p2PendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p2PendingExpRow *object = objects->data[i];
+        psMetadata *where = p2PendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P2PENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2PendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p2PendingImfileRowFree(p2PendingImfileRow *object);
+
+p2PendingImfileRow *p2PendingImfileRowAlloc(const char *exp_id, const char *class_id, const char *uri, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2PendingImfileRow *object;
+
+    object = psAlloc(sizeof(p2PendingImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2PendingImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+    object->p2_version = p2_version;
+
+    return object;
+}
+
+static void p2PendingImfileRowFree(p2PendingImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool p2PendingImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2PENDINGIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2PENDINGIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P2PENDINGIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p2PendingImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P2PENDINGIMFILE_TABLE_NAME);
+}
+
+bool p2PendingImfileInsert(psDB * dbh, const char *exp_id, const char *class_id, const char *uri, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P2PENDINGIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool p2PendingImfilePop(psDB *dbh, char **exp_id, char **class_id, char **uri, char **stats, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, P2PENDINGIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2PENDINGIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2PENDINGIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2PendingImfileInsertObject(psDB *dbh, p2PendingImfileRow *object)
+{
+    return p2PendingImfileInsert(dbh, object->exp_id, object->class_id, object->uri, object->stats, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2PendingImfileRow *p2PendingImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2PendingImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2PendingImfileRowAlloc(exp_id, class_id, uri, stats, recipe, p1_version, p2_version);
+}
+
+bool p2PendingImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P2PENDINGIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P2PENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2PENDINGIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, P2PENDINGIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p2PendingImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2PendingImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                P2PENDINGIMFILE_TABLE_NAME, P2PENDINGIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool p2PendingImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, P2PENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P2PENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p2PendingImfileMetadataFromObject(const p2PendingImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2PendingImfileRow *p2PendingImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    p2_version = psMetadataLookupS32(&status, md, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        return false;
+    }
+
+    return p2PendingImfileRowAlloc(exp_id, class_id, uri, stats, recipe, p1_version, p2_version);
+}
+psArray *p2PendingImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P2PENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, p2PendingImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p2PendingImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p2PendingImfileRow *object = objects->data[i];
+        psMetadata *where = p2PendingImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P2PENDINGIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2PendingImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p2DoneExpRowFree(p2DoneExpRow *object);
+
+p2DoneExpRow *p2DoneExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2DoneExpRow    *object;
+
+    object = psAlloc(sizeof(p2DoneExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2DoneExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+    object->p2_version = p2_version;
+
+    return object;
+}
+
+static void p2DoneExpRowFree(p2DoneExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool p2DoneExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2DONEEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2DONEEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P2DONEEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p2DoneExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P2DONEEXP_TABLE_NAME);
+}
+
+bool p2DoneExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P2DONEEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool p2DoneExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, char **stats, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2DONEEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, P2DONEEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2DONEEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2DONEEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2DoneExpInsertObject(psDB *dbh, p2DoneExpRow *object)
+{
+    return p2DoneExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->stats, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2DoneExpRow *p2DoneExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2DoneExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2DoneExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats, recipe, p1_version, p2_version);
+}
+
+bool p2DoneExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P2DONEEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P2DONEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2DONEEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, P2DONEEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p2DoneExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2DoneExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                P2DONEEXP_TABLE_NAME, P2DONEEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool p2DoneExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, P2DONEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P2DONEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p2DoneExpMetadataFromObject(const p2DoneExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2DoneExpRow *p2DoneExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    p2_version = psMetadataLookupS32(&status, md, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        return false;
+    }
+
+    return p2DoneExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, stats, recipe, p1_version, p2_version);
+}
+psArray *p2DoneExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P2DONEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, p2DoneExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p2DoneExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p2DoneExpRow *object = objects->data[i];
+        psMetadata *where = p2DoneExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P2DONEEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2DoneExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p2DoneImfileRowFree(p2DoneImfileRow *object);
+
+p2DoneImfileRow *p2DoneImfileRowAlloc(const char *exp_id, const char *class_id, const char *uri, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2DoneImfileRow *object;
+
+    object = psAlloc(sizeof(p2DoneImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2DoneImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+    object->p2_version = p2_version;
+
+    return object;
+}
+
+static void p2DoneImfileRowFree(p2DoneImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool p2DoneImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2DONEIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2DONEIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P2DONEIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p2DoneImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P2DONEIMFILE_TABLE_NAME);
+}
+
+bool p2DoneImfileInsert(psDB * dbh, const char *exp_id, const char *class_id, const char *uri, const char *stats, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P2DONEIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool p2DoneImfilePop(psDB *dbh, char **exp_id, char **class_id, char **uri, char **stats, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2DONEIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, P2DONEIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2DONEIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2DONEIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2DoneImfileInsertObject(psDB *dbh, p2DoneImfileRow *object)
+{
+    return p2DoneImfileInsert(dbh, object->exp_id, object->class_id, object->uri, object->stats, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2DoneImfileRow *p2DoneImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2DoneImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2DoneImfileRowAlloc(exp_id, class_id, uri, stats, recipe, p1_version, p2_version);
+}
+
+bool p2DoneImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P2DONEIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P2DONEIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2DONEIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, P2DONEIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p2DoneImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2DoneImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                P2DONEIMFILE_TABLE_NAME, P2DONEIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool p2DoneImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, P2DONEIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P2DONEIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p2DoneImfileMetadataFromObject(const p2DoneImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2DoneImfileRow *p2DoneImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    p2_version = psMetadataLookupS32(&status, md, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        return false;
+    }
+
+    return p2DoneImfileRowAlloc(exp_id, class_id, uri, stats, recipe, p1_version, p2_version);
+}
+psArray *p2DoneImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P2DONEIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, p2DoneImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p2DoneImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p2DoneImfileRow *object = objects->data[i];
+        psMetadata *where = p2DoneImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P2DONEIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2DoneImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p3PendingExpRowFree(p3PendingExpRow *object);
+
+p3PendingExpRow *p3PendingExpRowAlloc(const char *exp_id, const char *camera, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p2_version, psS32 p3_version)
+{
+    p3PendingExpRow *object;
+
+    object = psAlloc(sizeof(p3PendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p3PendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->p2_version = p2_version;
+    object->p3_version = p3_version;
+
+    return object;
+}
+
+static void p3PendingExpRowFree(p3PendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool p3PendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P3PENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P3PENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P3PENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p3PendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P3PENDINGEXP_TABLE_NAME);
+}
+
+bool p3PendingExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *exp_type, psS32 imfiles, const char *filter, const char *stats, const char *recipe, psS32 p2_version, psS32 p3_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, p3_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P3PENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool p3PendingExpPop(psDB *dbh, char **exp_id, char **camera, char **exp_type, psS32 *imfiles, char **filter, char **stats, char **recipe, psS32 *p2_version, psS32 *p3_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P3PENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P3PENDINGEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, P3PENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P3PENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P3PENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P3PENDINGEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+    *p3_version = psMetadataLookupS32(&status, row, "p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p3PendingExpInsertObject(psDB *dbh, p3PendingExpRow *object)
+{
+    return p3PendingExpInsert(dbh, object->exp_id, object->camera, object->exp_type, object->imfiles, object->filter, object->stats, object->recipe, object->p2_version, object->p3_version);
+}
+
+p3PendingExpRow *p3PendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+    char            recipe[256];
+    psS32           p2_version;
+    psS32           p3_version;
+
+    if (!p3PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p2_version, &p3_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p3PendingExpRowAlloc(exp_id, camera, exp_type, imfiles, filter, stats, recipe, p2_version, p3_version);
+}
+
+bool p3PendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P3PENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P3PENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P3PENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, P3PENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p3PendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p3PendingExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                P3PENDINGEXP_TABLE_NAME, P3PENDINGEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool p3PendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P3PENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, P3PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P3PENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P3PENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p3PendingExpMetadataFromObject(const p3PendingExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, object->p3_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p3PendingExpRow *p3PendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p2_version;
+    psS32           p3_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p2_version = psMetadataLookupS32(&status, md, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        return false;
+    }
+    p3_version = psMetadataLookupS32(&status, md, "p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
+        return false;
+    }
+
+    return p3PendingExpRowAlloc(exp_id, camera, exp_type, imfiles, filter, stats, recipe, p2_version, p3_version);
+}
+psArray *p3PendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P3PENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P3PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P3PENDINGEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, p3PendingExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p3PendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p3PendingExpRow *object = objects->data[i];
+        psMetadata *where = p3PendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P3PENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p3PendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detRunRowFree(detRunRow *object);
+
+detRunRow *detRunRowAlloc(const char *det_type, psS32 iteration)
+{
+    detRunRow       *object;
+
+    object = psAlloc(sizeof(detRunRow));
+    psMemSetDeallocator(object, (psFreeFunc)detRunRowFree);
+
+    object->det_type = psStringCopy(det_type);
+    object->iteration = iteration;
+
+    return object;
+}
+
+static void detRunRowFree(detRunRow *object)
+{
+    psFree(object->det_type);
+}
+
+bool detRunCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETRUN_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETRUN_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, "Key", "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETRUN_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detRunDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRUN_TABLE_NAME);
+}
+
+bool detRunInsert(psDB * dbh, const char *det_type, psS32 iteration)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, NULL, psStringCopy(det_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETRUN_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detRunPop(psDB *dbh, char **det_type, psS32 *iteration)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRUN_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETRUN_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRUN_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETRUN_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_type = psMetadataLookupPtr(&status, row, "det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_type");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detRunInsertObject(psDB *dbh, detRunRow *object)
+{
+    return detRunInsert(dbh, object->det_type, object->iteration);
+}
+
+detRunRow *detRunPopObject(psDB *dbh)
+{
+    char            det_type[256];
+    psS32           iteration;
+
+    if (!detRunPop(dbh, (char **)&det_type, &iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detRunRowAlloc(det_type, iteration);
+}
+
+bool detRunInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRUN_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRUN_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRUN_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detRunPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detRunSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETRUN_TABLE_NAME, DETRUN_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETRUN_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRUN_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detRunMetadataFromObject(const detRunRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, NULL, psStringCopy(object->det_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detRunRow *detRunObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *det_type;
+    psS32           iteration;
+
+    det_type = psMetadataLookupPtr(&status, md, "det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_type");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+
+    return detRunRowAlloc(det_type, iteration);
+}
+psArray *detRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETRUN_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRUN_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detRunObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detRunRow *object = objects->data[i];
+        psMetadata *where = detRunMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detInputExpRowFree(detInputExpRow *object);
+
+detInputExpRow *detInputExpRowAlloc(psS32 det_id, psS32 iteration, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats)
+{
+    detInputExpRow  *object;
+
+    object = psAlloc(sizeof(detInputExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)detInputExpRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->stats = psStringCopy(stats);
+
+    return object;
+}
+
+static void detInputExpRowFree(detInputExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->stats);
+}
+
+bool detInputExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETINPUTEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETINPUTEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETINPUTEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detInputExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETINPUTEXP_TABLE_NAME);
+}
+
+bool detInputExpInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, const char *stats)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETINPUTEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detInputExpPop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, char **stats)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETINPUTEXP_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETINPUTEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETINPUTEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETINPUTEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detInputExpInsertObject(psDB *dbh, detInputExpRow *object)
+{
+    return detInputExpInsert(dbh, object->det_id, object->iteration, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->stats);
+}
+
+detInputExpRow *detInputExpPopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    char            stats[256];
+
+    if (!detInputExpPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detInputExpRowAlloc(det_id, iteration, exp_id, camera, telescope, exp_type, imfiles, filter, stats);
+}
+
+bool detInputExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETINPUTEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETINPUTEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETINPUTEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETINPUTEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detInputExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detInputExpSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETINPUTEXP_TABLE_NAME, DETINPUTEXP_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detInputExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETINPUTEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETINPUTEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETINPUTEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detInputExpMetadataFromObject(const detInputExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, psStringCopy(object->camera))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, psStringCopy(object->telescope))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, psStringCopy(object->exp_type))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, psStringCopy(object->filter))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detInputExpRow *detInputExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+
+    return detInputExpRowAlloc(det_id, iteration, exp_id, camera, telescope, exp_type, imfiles, filter, stats);
+}
+psArray *detInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETINPUTEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETINPUTEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detInputExpObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detInputExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detInputExpRow *object = objects->data[i];
+        psMetadata *where = detInputExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detProcessedImfileRowFree(detProcessedImfileRow *object);
+
+detProcessedImfileRow *detProcessedImfileRowAlloc(psS32 det_id, const char *exp_id, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    detProcessedImfileRow *object;
+
+    object = psAlloc(sizeof(detProcessedImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)detProcessedImfileRowFree);
+
+    object->det_id = det_id;
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detProcessedImfileRowFree(detProcessedImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool detProcessedImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETPROCESSEDIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETPROCESSEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detProcessedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETPROCESSEDIMFILE_TABLE_NAME);
+}
+
+bool detProcessedImfileInsert(psDB * dbh, psS32 det_id, const char *exp_id, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETPROCESSEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detProcessedImfilePop(psDB *dbh, psS32 *det_id, char **exp_id, char **class_id, char **uri, char **stats, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETPROCESSEDIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETPROCESSEDIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detProcessedImfileInsertObject(psDB *dbh, detProcessedImfileRow *object)
+{
+    return detProcessedImfileInsert(dbh, object->det_id, object->exp_id, object->class_id, object->uri, object->stats, object->recipe);
+}
+
+detProcessedImfileRow *detProcessedImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+    char            recipe[256];
+
+    if (!detProcessedImfilePop(dbh, &det_id, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detProcessedImfileRowAlloc(det_id, exp_id, class_id, uri, stats, recipe);
+}
+
+bool detProcessedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETPROCESSEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETPROCESSEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETPROCESSEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detProcessedImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detProcessedImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETPROCESSEDIMFILE_TABLE_NAME, DETPROCESSEDIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETPROCESSEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETPROCESSEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detProcessedImfileMetadataFromObject(const detProcessedImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detProcessedImfileRow *detProcessedImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detProcessedImfileRowAlloc(det_id, exp_id, class_id, uri, stats, recipe);
+}
+psArray *detProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETPROCESSEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detProcessedImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detProcessedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detProcessedImfileRow *object = objects->data[i];
+        psMetadata *where = detProcessedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detStackedImfileRowFree(detStackedImfileRow *object);
+
+detStackedImfileRow *detStackedImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    detStackedImfileRow *object;
+
+    object = psAlloc(sizeof(detStackedImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)detStackedImfileRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detStackedImfileRowFree(detStackedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool detStackedImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETSTACKEDIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETSTACKEDIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETSTACKEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detStackedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETSTACKEDIMFILE_TABLE_NAME);
+}
+
+bool detStackedImfileInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETSTACKEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detStackedImfilePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **class_id, char **uri, char **stats, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETSTACKEDIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETSTACKEDIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETSTACKEDIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETSTACKEDIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detStackedImfileInsertObject(psDB *dbh, detStackedImfileRow *object)
+{
+    return detStackedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->stats, object->recipe);
+}
+
+detStackedImfileRow *detStackedImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+    char            recipe[256];
+
+    if (!detStackedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, stats, recipe);
+}
+
+bool detStackedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETSTACKEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETSTACKEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETSTACKEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detStackedImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detStackedImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETSTACKEDIMFILE_TABLE_NAME, DETSTACKEDIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detStackedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETSTACKEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETSTACKEDIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETSTACKEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detStackedImfileMetadataFromObject(const detStackedImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detStackedImfileRow *detStackedImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, stats, recipe);
+}
+psArray *detStackedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETSTACKEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETSTACKEDIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detStackedImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detStackedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detStackedImfileRow *object = objects->data[i];
+        psMetadata *where = detStackedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detNormalizedImfileRowFree(detNormalizedImfileRow *object);
+
+detNormalizedImfileRow *detNormalizedImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    detNormalizedImfileRow *object;
+
+    object = psAlloc(sizeof(detNormalizedImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)detNormalizedImfileRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detNormalizedImfileRowFree(detNormalizedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool detNormalizedImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETNORMALIZEDIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detNormalizedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETNORMALIZEDIMFILE_TABLE_NAME);
+}
+
+bool detNormalizedImfileInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detNormalizedImfilePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **class_id, char **uri, char **stats, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETNORMALIZEDIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETNORMALIZEDIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detNormalizedImfileInsertObject(psDB *dbh, detNormalizedImfileRow *object)
+{
+    return detNormalizedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->stats, object->recipe);
+}
+
+detNormalizedImfileRow *detNormalizedImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+    char            recipe[256];
+
+    if (!detNormalizedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, stats, recipe);
+}
+
+bool detNormalizedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETNORMALIZEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETNORMALIZEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detNormalizedImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detNormalizedImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETNORMALIZEDIMFILE_TABLE_NAME, DETNORMALIZEDIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detNormalizedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETNORMALIZEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detNormalizedImfileMetadataFromObject(const detNormalizedImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, stats, recipe);
+}
+psArray *detNormalizedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETNORMALIZEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detNormalizedImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detNormalizedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detNormalizedImfileRow *object = objects->data[i];
+        psMetadata *where = detNormalizedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detMasterFrameRowFree(detMasterFrameRow *object);
+
+detMasterFrameRow *detMasterFrameRowAlloc(psS32 det_id, psS32 iteration, const char *comment)
+{
+    detMasterFrameRow *object;
+
+    object = psAlloc(sizeof(detMasterFrameRow));
+    psMemSetDeallocator(object, (psFreeFunc)detMasterFrameRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->comment = psStringCopy(comment);
+
+    return object;
+}
+
+static void detMasterFrameRowFree(detMasterFrameRow *object)
+{
+    psFree(object->comment);
+}
+
+bool detMasterFrameCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETMASTERFRAME_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETMASTERFRAME_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "comment", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETMASTERFRAME_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detMasterFrameDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETMASTERFRAME_TABLE_NAME);
+}
+
+bool detMasterFrameInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *comment)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "comment", 0, NULL, psStringCopy(comment))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETMASTERFRAME_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detMasterFramePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **comment)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERFRAME_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERFRAME_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETMASTERFRAME_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERFRAME_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETMASTERFRAME_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETMASTERFRAME_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *comment = psMetadataLookupPtr(&status, row, "comment");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item comment");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detMasterFrameInsertObject(psDB *dbh, detMasterFrameRow *object)
+{
+    return detMasterFrameInsert(dbh, object->det_id, object->iteration, object->comment);
+}
+
+detMasterFrameRow *detMasterFramePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            comment[256];
+
+    if (!detMasterFramePop(dbh, &det_id, &iteration, (char **)&comment)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detMasterFrameRowAlloc(det_id, iteration, comment);
+}
+
+bool detMasterFrameInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETMASTERFRAME_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETMASTERFRAME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETMASTERFRAME_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETMASTERFRAME_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detMasterFramePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detMasterFrameSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETMASTERFRAME_TABLE_NAME, DETMASTERFRAME_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detMasterFrameSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERFRAME_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETMASTERFRAME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERFRAME_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETMASTERFRAME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detMasterFrameMetadataFromObject(const detMasterFrameRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "comment", 0, NULL, psStringCopy(object->comment))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detMasterFrameRow *detMasterFrameObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *comment;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    comment = psMetadataLookupPtr(&status, md, "comment");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item comment");
+        return false;
+    }
+
+    return detMasterFrameRowAlloc(det_id, iteration, comment);
+}
+psArray *detMasterFrameSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERFRAME_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETMASTERFRAME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERFRAME_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detMasterFrameObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detMasterFrameDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detMasterFrameRow *object = objects->data[i];
+        psMetadata *where = detMasterFrameMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETMASTERFRAME_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detMasterFrame");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detMasterImfileRowFree(detMasterImfileRow *object);
+
+detMasterImfileRow *detMasterImfileRowAlloc(psS32 det_id, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    detMasterImfileRow *object;
+
+    object = psAlloc(sizeof(detMasterImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)detMasterImfileRowFree);
+
+    object->det_id = det_id;
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detMasterImfileRowFree(detMasterImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool detMasterImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETMASTERIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETMASTERIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETMASTERIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detMasterImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETMASTERIMFILE_TABLE_NAME);
+}
+
+bool detMasterImfileInsert(psDB * dbh, psS32 det_id, const char *class_id, const char *uri, const char *stats, const char *recipe)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETMASTERIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detMasterImfilePop(psDB *dbh, psS32 *det_id, char **class_id, char **uri, char **stats, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETMASTERIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETMASTERIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETMASTERIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detMasterImfileInsertObject(psDB *dbh, detMasterImfileRow *object)
+{
+    return detMasterImfileInsert(dbh, object->det_id, object->class_id, object->uri, object->stats, object->recipe);
+}
+
+detMasterImfileRow *detMasterImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    char            class_id[256];
+    char            uri[256];
+    char            stats[256];
+    char            recipe[256];
+
+    if (!detMasterImfilePop(dbh, &det_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detMasterImfileRowAlloc(det_id, class_id, uri, stats, recipe);
+}
+
+bool detMasterImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETMASTERIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETMASTERIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETMASTERIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETMASTERIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detMasterImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detMasterImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETMASTERIMFILE_TABLE_NAME, DETMASTERIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detMasterImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETMASTERIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETMASTERIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detMasterImfileMetadataFromObject(const detMasterImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, psStringCopy(object->uri))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detMasterImfileRow *detMasterImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detMasterImfileRowAlloc(det_id, class_id, uri, stats, recipe);
+}
+psArray *detMasterImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETMASTERIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detMasterImfileObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detMasterImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detMasterImfileRow *object = objects->data[i];
+        psMetadata *where = detMasterImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETMASTERIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detMasterImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detResidImfileAnalysisRowFree(detResidImfileAnalysisRow *object);
+
+detResidImfileAnalysisRow *detResidImfileAnalysisRowAlloc(psS32 det_id, psS32 iteration, const char *exp_id, const char *class_id, const char *stats, const char *recipe)
+{
+    detResidImfileAnalysisRow *object;
+
+    object = psAlloc(sizeof(detResidImfileAnalysisRow));
+    psMemSetDeallocator(object, (psFreeFunc)detResidImfileAnalysisRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detResidImfileAnalysisRowFree(detResidImfileAnalysisRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool detResidImfileAnalysisCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETRESIDIMFILEANALYSIS_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detResidImfileAnalysisDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME);
+}
+
+bool detResidImfileAnalysisInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *exp_id, const char *class_id, const char *stats, const char *recipe)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detResidImfileAnalysisPop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **exp_id, char **class_id, char **stats, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETRESIDIMFILEANALYSIS_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETRESIDIMFILEANALYSIS_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detResidImfileAnalysisInsertObject(psDB *dbh, detResidImfileAnalysisRow *object)
+{
+    return detResidImfileAnalysisInsert(dbh, object->det_id, object->iteration, object->exp_id, object->class_id, object->stats, object->recipe);
+}
+
+detResidImfileAnalysisRow *detResidImfileAnalysisPopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            exp_id[256];
+    char            class_id[256];
+    char            stats[256];
+    char            recipe[256];
+
+    if (!detResidImfileAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&class_id, (char **)&stats, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detResidImfileAnalysisRowAlloc(det_id, iteration, exp_id, class_id, stats, recipe);
+}
+
+bool detResidImfileAnalysisInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRESIDIMFILEANALYSIS_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRESIDIMFILEANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDIMFILEANALYSIS_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detResidImfileAnalysisPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detResidImfileAnalysisSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETRESIDIMFILEANALYSIS_TABLE_NAME, DETRESIDIMFILEANALYSIS_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detResidImfileAnalysisSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETRESIDIMFILEANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDIMFILEANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detResidImfileAnalysisMetadataFromObject(const detResidImfileAnalysisRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, psStringCopy(object->class_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detResidImfileAnalysisRow *detResidImfileAnalysisObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *class_id;
+    char            *stats;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detResidImfileAnalysisRowAlloc(det_id, iteration, exp_id, class_id, stats, recipe);
+}
+psArray *detResidImfileAnalysisSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETRESIDIMFILEANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detResidImfileAnalysisObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detResidImfileAnalysisDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detResidImfileAnalysisRow *object = objects->data[i];
+        psMetadata *where = detResidImfileAnalysisMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfileAnalysis");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detResidExpAnalysisRowFree(detResidExpAnalysisRow *object);
+
+detResidExpAnalysisRow *detResidExpAnalysisRowAlloc(psS32 det_id, psS32 iteration, const char *exp_id, const char *stats, const char *recipe, bool accept)
+{
+    detResidExpAnalysisRow *object;
+
+    object = psAlloc(sizeof(detResidExpAnalysisRow));
+    psMemSetDeallocator(object, (psFreeFunc)detResidExpAnalysisRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->exp_id = psStringCopy(exp_id);
+    object->stats = psStringCopy(stats);
+    object->recipe = psStringCopy(recipe);
+    object->accept = accept;
+
+    return object;
+}
+
+static void detResidExpAnalysisRowFree(detResidExpAnalysisRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->stats);
+    psFree(object->recipe);
+}
+
+bool detResidExpAnalysisCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETRESIDEXPANALYSIS_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, false)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detResidExpAnalysisDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRESIDEXPANALYSIS_TABLE_NAME);
+}
+
+bool detResidExpAnalysisInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *exp_id, const char *stats, const char *recipe, bool accept)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+bool detResidExpAnalysisPop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **exp_id, char **stats, char **recipe, bool *accept)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETRESIDEXPANALYSIS_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETRESIDEXPANALYSIS_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *stats = psMetadataLookupPtr(&status, row, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *accept = psMetadataLookupBool(&status, row, "accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detResidExpAnalysisInsertObject(psDB *dbh, detResidExpAnalysisRow *object)
+{
+    return detResidExpAnalysisInsert(dbh, object->det_id, object->iteration, object->exp_id, object->stats, object->recipe, object->accept);
+}
+
+detResidExpAnalysisRow *detResidExpAnalysisPopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            exp_id[256];
+    char            stats[256];
+    char            recipe[256];
+    bool            accept;
+
+    if (!detResidExpAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&stats, (char **)&recipe, &accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detResidExpAnalysisRowAlloc(det_id, iteration, exp_id, stats, recipe, accept);
+}
+
+bool detResidExpAnalysisInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRESIDEXPANALYSIS_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRESIDEXPANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDEXPANALYSIS_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detResidExpAnalysisPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detResidExpAnalysisSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETRESIDEXPANALYSIS_TABLE_NAME, DETRESIDEXPANALYSIS_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detResidExpAnalysisSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETRESIDEXPANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDEXPANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detResidExpAnalysisMetadataFromObject(const detResidExpAnalysisRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, psStringCopy(object->exp_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, psStringCopy(object->stats))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, psStringCopy(object->recipe))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, object->accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detResidExpAnalysisRow *detResidExpAnalysisObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *stats;
+    char            *recipe;
+    bool            accept;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    stats = psMetadataLookupPtr(&status, md, "stats");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    accept = psMetadataLookupBool(&status, md, "accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
+        return false;
+    }
+
+    return detResidExpAnalysisRowAlloc(det_id, iteration, exp_id, stats, recipe, accept);
+}
+psArray *detResidExpAnalysisSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETRESIDEXPANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, detResidExpAnalysisObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detResidExpAnalysisDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detResidExpAnalysisRow *object = objects->data[i];
+        psMetadata *where = detResidExpAnalysisMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExpAnalysis");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
Index: /tags/rel-0_0_2/ippdb/src/ippdb.h
===================================================================
--- /tags/rel-0_0_2/ippdb/src/ippdb.h	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/src/ippdb.h	(revision 7503)
@@ -0,0 +1,5873 @@
+#ifndef IPPDB_H
+#define IPPDB_H 1
+
+#include <pslib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @addtogroup ippdb
+/// @{
+
+/** Opens a new database connection
+ *
+ *  @return A new psDB object if the database connection is successful or NULL
+ *  on failure.
+ */
+
+psDB *ippdbInit(
+    const char      *host,              ///< Database server hostname
+    const char      *user,              ///< Database username
+    const char      *passwd,            ///< Database password
+    const char      *dbname             ///< Database table.namespace
+);
+
+/** Closes a database connection
+ */
+
+void ippdbCleanup(
+    psDB            *dbh                ///< Database handle
+);
+
+/** weatherRow data structure
+ *
+ * Structure for representing a single row of weather table data.
+ */
+
+typedef struct {
+    psF32           temp01;
+    psF32           humi01;
+    psF32           temp02;
+    psF32           humi02;
+    psF32           temp03;
+    psF32           humi03;
+    psF32           pressure;
+} weatherRow;
+
+/** Creates a new weatherRow object
+ *
+ *  @return A new weatherRow object or NULL on failure.
+ */
+
+weatherRow *weatherRowAlloc(
+    psF32           temp01,
+    psF32           humi01,
+    psF32           temp02,
+    psF32           humi02,
+    psF32           temp03,
+    psF32           humi03,
+    psF32           pressure
+);
+
+/** Creates a new weather table
+ *
+ * @return true on success
+ */
+
+bool weatherCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a weather table
+ *
+ * @return true on success
+ */
+
+bool weatherDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool weatherInsert(
+    psDB            *dbh,               ///< Database handle
+    psF32           temp01,
+    psF32           humi01,
+    psF32           temp02,
+    psF32           humi02,
+    psF32           temp03,
+    psF32           humi03,
+    psF32           pressure
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool weatherPop(
+    psDB            *dbh,               ///< Database handle
+    psF32           *temp01,
+    psF32           *humi01,
+    psF32           *temp02,
+    psF32           *humi02,
+    psF32           *temp03,
+    psF32           *humi03,
+    psF32           *pressure
+);
+
+/** Insert a single weatherRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool weatherInsertObject(
+    psDB            *dbh,               ///< Database handle
+    weatherRow      *object             ///< weatherRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new weatherRow on success or NULL on failure.
+ */
+
+weatherRow *weatherPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table weatherRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool weatherInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool weatherPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool weatherSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a weatherRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *weatherMetadataFromObject(
+    const weatherRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A weatherRow pointer or NULL on error
+ */
+
+weatherRow *weatherObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as weatherRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *weatherSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long weatherDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_transparencyRow data structure
+ *
+ * Structure for representing a single row of skyp_transparency table data.
+ */
+
+typedef struct {
+    char            *filter;
+    psF64           trans;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+} skyp_transparencyRow;
+
+/** Creates a new skyp_transparencyRow object
+ *
+ *  @return A new skyp_transparencyRow object or NULL on failure.
+ */
+
+skyp_transparencyRow *skyp_transparencyRowAlloc(
+    const char      *filter,
+    psF64           trans,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Creates a new skyp_transparency table
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_transparency table
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *filter,
+    psF64           trans,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyPop(
+    psDB            *dbh,               ///< Database handle
+    char            **filter,
+    psF64           *trans,
+    psS32           *nstars,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exptime,
+    psF64           *sky_bright
+);
+
+/** Insert a single skyp_transparencyRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_transparencyRow *object             ///< skyp_transparencyRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_transparencyRow on success or NULL on failure.
+ */
+
+skyp_transparencyRow *skyp_transparencyPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_transparencyRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencySelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_transparencyRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_transparencyMetadataFromObject(
+    const skyp_transparencyRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_transparencyRow pointer or NULL on error
+ */
+
+skyp_transparencyRow *skyp_transparencyObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_transparencyRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_transparencySelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_transparencyDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_absorptionRow data structure
+ *
+ * Structure for representing a single row of skyp_absorption table data.
+ */
+
+typedef struct {
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+} skyp_absorptionRow;
+
+/** Creates a new skyp_absorptionRow object
+ *
+ *  @return A new skyp_absorptionRow object or NULL on failure.
+ */
+
+skyp_absorptionRow *skyp_absorptionRowAlloc(
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Creates a new skyp_absorption table
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_absorption table
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionPop(
+    psDB            *dbh,               ///< Database handle
+    char            **disperser_id,
+    psF32           *atmcomp1,
+    psF32           *atmcomp2,
+    psF32           *atmcomp3,
+    psS32           *nstars,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exptime,
+    psF64           *sky_bright
+);
+
+/** Insert a single skyp_absorptionRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_absorptionRow *object             ///< skyp_absorptionRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_absorptionRow on success or NULL on failure.
+ */
+
+skyp_absorptionRow *skyp_absorptionPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_absorptionRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_absorptionRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_absorptionMetadataFromObject(
+    const skyp_absorptionRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_absorptionRow pointer or NULL on error
+ */
+
+skyp_absorptionRow *skyp_absorptionObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_absorptionRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_absorptionSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_absorptionDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_emissionRow data structure
+ *
+ * Structure for representing a single row of skyp_emission table data.
+ */
+
+typedef struct {
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psF32           continuum;
+    psF32           exptime;
+} skyp_emissionRow;
+
+/** Creates a new skyp_emissionRow object
+ *
+ *  @return A new skyp_emissionRow object or NULL on failure.
+ */
+
+skyp_emissionRow *skyp_emissionRowAlloc(
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psF32           continuum,
+    psF32           exptime
+);
+
+/** Creates a new skyp_emission table
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_emission table
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psF32           continuum,
+    psF32           exptime
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionPop(
+    psDB            *dbh,               ///< Database handle
+    char            **disperser_id,
+    psF32           *atmcomp1,
+    psF32           *atmcomp2,
+    psF32           *atmcomp3,
+    psF32           *continuum,
+    psF32           *exptime
+);
+
+/** Insert a single skyp_emissionRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_emissionRow *object             ///< skyp_emissionRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_emissionRow on success or NULL on failure.
+ */
+
+skyp_emissionRow *skyp_emissionPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_emissionRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_emissionRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_emissionMetadataFromObject(
+    const skyp_emissionRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_emissionRow pointer or NULL on error
+ */
+
+skyp_emissionRow *skyp_emissionObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_emissionRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_emissionSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_emissionDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** dimmRow data structure
+ *
+ * Structure for representing a single row of dimm table data.
+ */
+
+typedef struct {
+    psF32           sigmax;
+    psF32           sigmay;
+    psF32           fwhm;
+    psF64           ra;
+    psF64           decl;
+    psF32           expttime;
+    char            *telescope_id;
+} dimmRow;
+
+/** Creates a new dimmRow object
+ *
+ *  @return A new dimmRow object or NULL on failure.
+ */
+
+dimmRow *dimmRowAlloc(
+    psF32           sigmax,
+    psF32           sigmay,
+    psF32           fwhm,
+    psF64           ra,
+    psF64           decl,
+    psF32           expttime,
+    const char      *telescope_id
+);
+
+/** Creates a new dimm table
+ *
+ * @return true on success
+ */
+
+bool dimmCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a dimm table
+ *
+ * @return true on success
+ */
+
+bool dimmDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool dimmInsert(
+    psDB            *dbh,               ///< Database handle
+    psF32           sigmax,
+    psF32           sigmay,
+    psF32           fwhm,
+    psF64           ra,
+    psF64           decl,
+    psF32           expttime,
+    const char      *telescope_id
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool dimmPop(
+    psDB            *dbh,               ///< Database handle
+    psF32           *sigmax,
+    psF32           *sigmay,
+    psF32           *fwhm,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *expttime,
+    char            **telescope_id
+);
+
+/** Insert a single dimmRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool dimmInsertObject(
+    psDB            *dbh,               ///< Database handle
+    dimmRow         *object             ///< dimmRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new dimmRow on success or NULL on failure.
+ */
+
+dimmRow *dimmPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table dimmRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool dimmInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool dimmPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool dimmSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a dimmRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *dimmMetadataFromObject(
+    const dimmRow   *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A dimmRow pointer or NULL on error
+ */
+
+dimmRow *dimmObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as dimmRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *dimmSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long dimmDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_irRow data structure
+ *
+ * Structure for representing a single row of skyp_ir table data.
+ */
+
+typedef struct {
+    psF64           sky_bright;
+    psF64           sky_var;
+    psF64           ra;
+    psF64           decl;
+    psF32           fov_x;
+    psF32           fov_y;
+} skyp_irRow;
+
+/** Creates a new skyp_irRow object
+ *
+ *  @return A new skyp_irRow object or NULL on failure.
+ */
+
+skyp_irRow *skyp_irRowAlloc(
+    psF64           sky_bright,
+    psF64           sky_var,
+    psF64           ra,
+    psF64           decl,
+    psF32           fov_x,
+    psF32           fov_y
+);
+
+/** Creates a new skyp_ir table
+ *
+ * @return true on success
+ */
+
+bool skyp_irCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_ir table
+ *
+ * @return true on success
+ */
+
+bool skyp_irDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_irInsert(
+    psDB            *dbh,               ///< Database handle
+    psF64           sky_bright,
+    psF64           sky_var,
+    psF64           ra,
+    psF64           decl,
+    psF32           fov_x,
+    psF32           fov_y
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_irPop(
+    psDB            *dbh,               ///< Database handle
+    psF64           *sky_bright,
+    psF64           *sky_var,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *fov_x,
+    psF32           *fov_y
+);
+
+/** Insert a single skyp_irRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_irInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_irRow      *object             ///< skyp_irRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_irRow on success or NULL on failure.
+ */
+
+skyp_irRow *skyp_irPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_irRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_irInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_irPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_irSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_irRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_irMetadataFromObject(
+    const skyp_irRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_irRow pointer or NULL on error
+ */
+
+skyp_irRow *skyp_irObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_irRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_irSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_irDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** domeRow data structure
+ *
+ * Structure for representing a single row of dome table data.
+ */
+
+typedef struct {
+    psF32           az;
+    bool            open;
+    bool            light;
+    bool            track;
+} domeRow;
+
+/** Creates a new domeRow object
+ *
+ *  @return A new domeRow object or NULL on failure.
+ */
+
+domeRow *domeRowAlloc(
+    psF32           az,
+    bool            open,
+    bool            light,
+    bool            track
+);
+
+/** Creates a new dome table
+ *
+ * @return true on success
+ */
+
+bool domeCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a dome table
+ *
+ * @return true on success
+ */
+
+bool domeDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool domeInsert(
+    psDB            *dbh,               ///< Database handle
+    psF32           az,
+    bool            open,
+    bool            light,
+    bool            track
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool domePop(
+    psDB            *dbh,               ///< Database handle
+    psF32           *az,
+    bool            *open,
+    bool            *light,
+    bool            *track
+);
+
+/** Insert a single domeRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool domeInsertObject(
+    psDB            *dbh,               ///< Database handle
+    domeRow         *object             ///< domeRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new domeRow on success or NULL on failure.
+ */
+
+domeRow *domePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table domeRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool domeInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool domePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool domeSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a domeRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *domeMetadataFromObject(
+    const domeRow   *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A domeRow pointer or NULL on error
+ */
+
+domeRow *domeObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as domeRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *domeSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long domeDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** telescopeRow data structure
+ *
+ * Structure for representing a single row of telescope table data.
+ */
+
+typedef struct {
+    char            *guide;
+    psF32           alt;
+    psF32           az;
+    psF64           ra;
+    psF64           decl;
+} telescopeRow;
+
+/** Creates a new telescopeRow object
+ *
+ *  @return A new telescopeRow object or NULL on failure.
+ */
+
+telescopeRow *telescopeRowAlloc(
+    const char      *guide,
+    psF32           alt,
+    psF32           az,
+    psF64           ra,
+    psF64           decl
+);
+
+/** Creates a new telescope table
+ *
+ * @return true on success
+ */
+
+bool telescopeCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a telescope table
+ *
+ * @return true on success
+ */
+
+bool telescopeDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool telescopeInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *guide,
+    psF32           alt,
+    psF32           az,
+    psF64           ra,
+    psF64           decl
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool telescopePop(
+    psDB            *dbh,               ///< Database handle
+    char            **guide,
+    psF32           *alt,
+    psF32           *az,
+    psF64           *ra,
+    psF64           *decl
+);
+
+/** Insert a single telescopeRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool telescopeInsertObject(
+    psDB            *dbh,               ///< Database handle
+    telescopeRow    *object             ///< telescopeRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new telescopeRow on success or NULL on failure.
+ */
+
+telescopeRow *telescopePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table telescopeRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool telescopeInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool telescopePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool telescopeSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a telescopeRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *telescopeMetadataFromObject(
+    const telescopeRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A telescopeRow pointer or NULL on error
+ */
+
+telescopeRow *telescopeObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as telescopeRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *telescopeSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long telescopeDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** summitExpRow data structure
+ *
+ * Structure for representing a single row of summitExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    char            *uri;
+} summitExpRow;
+
+/** Creates a new summitExpRow object
+ *
+ *  @return A new summitExpRow object or NULL on failure.
+ */
+
+summitExpRow *summitExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    const char      *uri
+);
+
+/** Creates a new summitExp table
+ *
+ * @return true on success
+ */
+
+bool summitExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a summitExp table
+ *
+ * @return true on success
+ */
+
+bool summitExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    const char      *uri
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool summitExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    char            **uri
+);
+
+/** Insert a single summitExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    summitExpRow    *object             ///< summitExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new summitExpRow on success or NULL on failure.
+ */
+
+summitExpRow *summitExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table summitExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool summitExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool summitExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a summitExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *summitExpMetadataFromObject(
+    const summitExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A summitExpRow pointer or NULL on error
+ */
+
+summitExpRow *summitExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as summitExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *summitExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** pzPendingExpRow data structure
+ *
+ * Structure for representing a single row of pzPendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+} pzPendingExpRow;
+
+/** Creates a new pzPendingExpRow object
+ *
+ *  @return A new pzPendingExpRow object or NULL on failure.
+ */
+
+pzPendingExpRow *pzPendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Creates a new pzPendingExp table
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzPendingExp table
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles
+);
+
+/** Insert a single pzPendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzPendingExpRow *object             ///< pzPendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new pzPendingExpRow on success or NULL on failure.
+ */
+
+pzPendingExpRow *pzPendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table pzPendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzPendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzPendingExpMetadataFromObject(
+    const pzPendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzPendingExpRow pointer or NULL on error
+ */
+
+pzPendingExpRow *pzPendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzPendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzPendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** pzPendingImfileRow data structure
+ *
+ * Structure for representing a single row of pzPendingImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    psS32           bytes;
+    char            *md5sum;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} pzPendingImfileRow;
+
+/** Creates a new pzPendingImfileRow object
+ *
+ *  @return A new pzPendingImfileRow object or NULL on failure.
+ */
+
+pzPendingImfileRow *pzPendingImfileRowAlloc(
+    const char      *exp_id,
+    psS32           bytes,
+    const char      *md5sum,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new pzPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    psS32           bytes,
+    const char      *md5sum,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    psS32           *bytes,
+    char            **md5sum,
+    char            **class,
+    char            **class_id,
+    char            **uri
+);
+
+/** Insert a single pzPendingImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzPendingImfileRow *object             ///< pzPendingImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new pzPendingImfileRow on success or NULL on failure.
+ */
+
+pzPendingImfileRow *pzPendingImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table pzPendingImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzPendingImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzPendingImfileMetadataFromObject(
+    const pzPendingImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzPendingImfileRow pointer or NULL on error
+ */
+
+pzPendingImfileRow *pzPendingImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzPendingImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzPendingImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** newExpRow data structure
+ *
+ * Structure for representing a single row of newExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+} newExpRow;
+
+/** Creates a new newExpRow object
+ *
+ *  @return A new newExpRow object or NULL on failure.
+ */
+
+newExpRow *newExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Creates a new newExp table
+ *
+ * @return true on success
+ */
+
+bool newExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a newExp table
+ *
+ * @return true on success
+ */
+
+bool newExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool newExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles
+);
+
+/** Insert a single newExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    newExpRow       *object             ///< newExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new newExpRow on success or NULL on failure.
+ */
+
+newExpRow *newExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table newExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool newExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool newExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a newExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *newExpMetadataFromObject(
+    const newExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A newExpRow pointer or NULL on error
+ */
+
+newExpRow *newExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as newExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *newExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** newImfileRow data structure
+ *
+ * Structure for representing a single row of newImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} newImfileRow;
+
+/** Creates a new newImfileRow object
+ *
+ *  @return A new newImfileRow object or NULL on failure.
+ */
+
+newImfileRow *newImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new newImfile table
+ *
+ * @return true on success
+ */
+
+bool newImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a newImfile table
+ *
+ * @return true on success
+ */
+
+bool newImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool newImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class,
+    char            **class_id,
+    char            **uri
+);
+
+/** Insert a single newImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    newImfileRow    *object             ///< newImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new newImfileRow on success or NULL on failure.
+ */
+
+newImfileRow *newImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table newImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool newImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool newImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a newImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *newImfileMetadataFromObject(
+    const newImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A newImfileRow pointer or NULL on error
+ */
+
+newImfileRow *newImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as newImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *newImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** rawDetrendExpRow data structure
+ *
+ * Structure for representing a single row of rawDetrendExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+} rawDetrendExpRow;
+
+/** Creates a new rawDetrendExpRow object
+ *
+ *  @return A new rawDetrendExpRow object or NULL on failure.
+ */
+
+rawDetrendExpRow *rawDetrendExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats
+);
+
+/** Creates a new rawDetrendExp table
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawDetrendExp table
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats
+);
+
+/** Insert a single rawDetrendExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawDetrendExpRow *object             ///< rawDetrendExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new rawDetrendExpRow on success or NULL on failure.
+ */
+
+rawDetrendExpRow *rawDetrendExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table rawDetrendExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawDetrendExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawDetrendExpMetadataFromObject(
+    const rawDetrendExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawDetrendExpRow pointer or NULL on error
+ */
+
+rawDetrendExpRow *rawDetrendExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawDetrendExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawDetrendExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawDetrendExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** rawScienceExpRow data structure
+ *
+ * Structure for representing a single row of rawScienceExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+} rawScienceExpRow;
+
+/** Creates a new rawScienceExpRow object
+ *
+ *  @return A new rawScienceExpRow object or NULL on failure.
+ */
+
+rawScienceExpRow *rawScienceExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats
+);
+
+/** Creates a new rawScienceExp table
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawScienceExp table
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats
+);
+
+/** Insert a single rawScienceExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawScienceExpRow *object             ///< rawScienceExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new rawScienceExpRow on success or NULL on failure.
+ */
+
+rawScienceExpRow *rawScienceExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table rawScienceExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawScienceExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawScienceExpMetadataFromObject(
+    const rawScienceExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawScienceExpRow pointer or NULL on error
+ */
+
+rawScienceExpRow *rawScienceExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawScienceExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawScienceExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawScienceExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** rawImfileRow data structure
+ *
+ * Structure for representing a single row of rawImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+} rawImfileRow;
+
+/** Creates a new rawImfileRow object
+ *
+ *  @return A new rawImfileRow object or NULL on failure.
+ */
+
+rawImfileRow *rawImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats
+);
+
+/** Creates a new rawImfile table
+ *
+ * @return true on success
+ */
+
+bool rawImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawImfile table
+ *
+ * @return true on success
+ */
+
+bool rawImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool rawImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **stats
+);
+
+/** Insert a single rawImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawImfileRow    *object             ///< rawImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new rawImfileRow on success or NULL on failure.
+ */
+
+rawImfileRow *rawImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table rawImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool rawImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawImfileMetadataFromObject(
+    const rawImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawImfileRow pointer or NULL on error
+ */
+
+rawImfileRow *rawImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p1PendingExpRow data structure
+ *
+ * Structure for representing a single row of p1PendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+} p1PendingExpRow;
+
+/** Creates a new p1PendingExpRow object
+ *
+ *  @return A new p1PendingExpRow object or NULL on failure.
+ */
+
+p1PendingExpRow *p1PendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version
+);
+
+/** Creates a new p1PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p1PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats,
+    char            **recipe,
+    psS32           *p1_version
+);
+
+/** Insert a single p1PendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p1PendingExpRow *object             ///< p1PendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p1PendingExpRow on success or NULL on failure.
+ */
+
+p1PendingExpRow *p1PendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p1PendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p1PendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p1PendingExpMetadataFromObject(
+    const p1PendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p1PendingExpRow pointer or NULL on error
+ */
+
+p1PendingExpRow *p1PendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p1PendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p1PendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p1PendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2PendingExpRow data structure
+ *
+ * Structure for representing a single row of p2PendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2PendingExpRow;
+
+/** Creates a new p2PendingExpRow object
+ *
+ *  @return A new p2PendingExpRow object or NULL on failure.
+ */
+
+p2PendingExpRow *p2PendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2PendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2PendingExpRow *object             ///< p2PendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2PendingExpRow on success or NULL on failure.
+ */
+
+p2PendingExpRow *p2PendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2PendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2PendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2PendingExpMetadataFromObject(
+    const p2PendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2PendingExpRow pointer or NULL on error
+ */
+
+p2PendingExpRow *p2PendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2PendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2PendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2PendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2PendingImfileRow data structure
+ *
+ * Structure for representing a single row of p2PendingImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2PendingImfileRow;
+
+/** Creates a new p2PendingImfileRow object
+ *
+ *  @return A new p2PendingImfileRow object or NULL on failure.
+ */
+
+p2PendingImfileRow *p2PendingImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2PendingImfile table
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2PendingImfile table
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **stats,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2PendingImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2PendingImfileRow *object             ///< p2PendingImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2PendingImfileRow on success or NULL on failure.
+ */
+
+p2PendingImfileRow *p2PendingImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2PendingImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2PendingImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2PendingImfileMetadataFromObject(
+    const p2PendingImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2PendingImfileRow pointer or NULL on error
+ */
+
+p2PendingImfileRow *p2PendingImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2PendingImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2PendingImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2PendingImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2DoneExpRow data structure
+ *
+ * Structure for representing a single row of p2DoneExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2DoneExpRow;
+
+/** Creates a new p2DoneExpRow object
+ *
+ *  @return A new p2DoneExpRow object or NULL on failure.
+ */
+
+p2DoneExpRow *p2DoneExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2DoneExp table
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2DoneExp table
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2DoneExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2DoneExpRow    *object             ///< p2DoneExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2DoneExpRow on success or NULL on failure.
+ */
+
+p2DoneExpRow *p2DoneExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2DoneExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2DoneExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2DoneExpMetadataFromObject(
+    const p2DoneExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2DoneExpRow pointer or NULL on error
+ */
+
+p2DoneExpRow *p2DoneExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2DoneExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2DoneExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2DoneExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2DoneImfileRow data structure
+ *
+ * Structure for representing a single row of p2DoneImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2DoneImfileRow;
+
+/** Creates a new p2DoneImfileRow object
+ *
+ *  @return A new p2DoneImfileRow object or NULL on failure.
+ */
+
+p2DoneImfileRow *p2DoneImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2DoneImfile table
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2DoneImfile table
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **stats,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2DoneImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2DoneImfileRow *object             ///< p2DoneImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2DoneImfileRow on success or NULL on failure.
+ */
+
+p2DoneImfileRow *p2DoneImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2DoneImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2DoneImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2DoneImfileMetadataFromObject(
+    const p2DoneImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2DoneImfileRow pointer or NULL on error
+ */
+
+p2DoneImfileRow *p2DoneImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2DoneImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2DoneImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2DoneImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p3PendingExpRow data structure
+ *
+ * Structure for representing a single row of p3PendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+    char            *recipe;
+    psS32           p2_version;
+    psS32           p3_version;
+} p3PendingExpRow;
+
+/** Creates a new p3PendingExpRow object
+ *
+ *  @return A new p3PendingExpRow object or NULL on failure.
+ */
+
+p3PendingExpRow *p3PendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p2_version,
+    psS32           p3_version
+);
+
+/** Creates a new p3PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p3PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats,
+    const char      *recipe,
+    psS32           p2_version,
+    psS32           p3_version
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats,
+    char            **recipe,
+    psS32           *p2_version,
+    psS32           *p3_version
+);
+
+/** Insert a single p3PendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p3PendingExpRow *object             ///< p3PendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p3PendingExpRow on success or NULL on failure.
+ */
+
+p3PendingExpRow *p3PendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p3PendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p3PendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p3PendingExpMetadataFromObject(
+    const p3PendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p3PendingExpRow pointer or NULL on error
+ */
+
+p3PendingExpRow *p3PendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p3PendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p3PendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p3PendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detRunRow data structure
+ *
+ * Structure for representing a single row of detRun table data.
+ */
+
+typedef struct {
+    char            *det_type;
+    psS32           iteration;
+} detRunRow;
+
+/** Creates a new detRunRow object
+ *
+ *  @return A new detRunRow object or NULL on failure.
+ */
+
+detRunRow *detRunRowAlloc(
+    const char      *det_type,
+    psS32           iteration
+);
+
+/** Creates a new detRun table
+ *
+ * @return true on success
+ */
+
+bool detRunCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detRun table
+ *
+ * @return true on success
+ */
+
+bool detRunDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *det_type,
+    psS32           iteration
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detRunPop(
+    psDB            *dbh,               ///< Database handle
+    char            **det_type,
+    psS32           *iteration
+);
+
+/** Insert a single detRunRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detRunRow       *object             ///< detRunRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detRunRow on success or NULL on failure.
+ */
+
+detRunRow *detRunPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detRunRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detRunPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detRunSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detRunRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detRunMetadataFromObject(
+    const detRunRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detRunRow pointer or NULL on error
+ */
+
+detRunRow *detRunObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detRunRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detRunSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detInputExpRow data structure
+ *
+ * Structure for representing a single row of detInputExp table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    char            *stats;
+} detInputExpRow;
+
+/** Creates a new detInputExpRow object
+ *
+ *  @return A new detInputExpRow object or NULL on failure.
+ */
+
+detInputExpRow *detInputExpRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats
+);
+
+/** Creates a new detInputExp table
+ *
+ * @return true on success
+ */
+
+bool detInputExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detInputExp table
+ *
+ * @return true on success
+ */
+
+bool detInputExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    const char      *stats
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detInputExpPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    char            **stats
+);
+
+/** Insert a single detInputExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detInputExpRow  *object             ///< detInputExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detInputExpRow on success or NULL on failure.
+ */
+
+detInputExpRow *detInputExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detInputExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detInputExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detInputExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detInputExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detInputExpMetadataFromObject(
+    const detInputExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detInputExpRow pointer or NULL on error
+ */
+
+detInputExpRow *detInputExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detInputExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detInputExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detInputExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detProcessedImfileRow data structure
+ *
+ * Structure for representing a single row of detProcessedImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+} detProcessedImfileRow;
+
+/** Creates a new detProcessedImfileRow object
+ *
+ *  @return A new detProcessedImfileRow object or NULL on failure.
+ */
+
+detProcessedImfileRow *detProcessedImfileRowAlloc(
+    psS32           det_id,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Creates a new detProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **stats,
+    char            **recipe
+);
+
+/** Insert a single detProcessedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detProcessedImfileRow *object             ///< detProcessedImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detProcessedImfileRow on success or NULL on failure.
+ */
+
+detProcessedImfileRow *detProcessedImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detProcessedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detProcessedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detProcessedImfileMetadataFromObject(
+    const detProcessedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detProcessedImfileRow pointer or NULL on error
+ */
+
+detProcessedImfileRow *detProcessedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detProcessedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detProcessedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detStackedImfileRow data structure
+ *
+ * Structure for representing a single row of detStackedImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+} detStackedImfileRow;
+
+/** Creates a new detStackedImfileRow object
+ *
+ *  @return A new detStackedImfileRow object or NULL on failure.
+ */
+
+detStackedImfileRow *detStackedImfileRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Creates a new detStackedImfile table
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detStackedImfile table
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detStackedImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **class_id,
+    char            **uri,
+    char            **stats,
+    char            **recipe
+);
+
+/** Insert a single detStackedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detStackedImfileRow *object             ///< detStackedImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detStackedImfileRow on success or NULL on failure.
+ */
+
+detStackedImfileRow *detStackedImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detStackedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detStackedImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detStackedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detStackedImfileMetadataFromObject(
+    const detStackedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detStackedImfileRow pointer or NULL on error
+ */
+
+detStackedImfileRow *detStackedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detStackedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detStackedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detStackedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detNormalizedImfileRow data structure
+ *
+ * Structure for representing a single row of detNormalizedImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+} detNormalizedImfileRow;
+
+/** Creates a new detNormalizedImfileRow object
+ *
+ *  @return A new detNormalizedImfileRow object or NULL on failure.
+ */
+
+detNormalizedImfileRow *detNormalizedImfileRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Creates a new detNormalizedImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detNormalizedImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **class_id,
+    char            **uri,
+    char            **stats,
+    char            **recipe
+);
+
+/** Insert a single detNormalizedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detNormalizedImfileRow *object             ///< detNormalizedImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detNormalizedImfileRow on success or NULL on failure.
+ */
+
+detNormalizedImfileRow *detNormalizedImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detNormalizedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detNormalizedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detNormalizedImfileMetadataFromObject(
+    const detNormalizedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detNormalizedImfileRow pointer or NULL on error
+ */
+
+detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detNormalizedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detNormalizedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detMasterFrameRow data structure
+ *
+ * Structure for representing a single row of detMasterFrame table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *comment;
+} detMasterFrameRow;
+
+/** Creates a new detMasterFrameRow object
+ *
+ *  @return A new detMasterFrameRow object or NULL on failure.
+ */
+
+detMasterFrameRow *detMasterFrameRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *comment
+);
+
+/** Creates a new detMasterFrame table
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detMasterFrame table
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *comment
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detMasterFramePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **comment
+);
+
+/** Insert a single detMasterFrameRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detMasterFrameRow *object             ///< detMasterFrameRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detMasterFrameRow on success or NULL on failure.
+ */
+
+detMasterFrameRow *detMasterFramePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detMasterFrameRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detMasterFramePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detMasterFrameRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detMasterFrameMetadataFromObject(
+    const detMasterFrameRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detMasterFrameRow pointer or NULL on error
+ */
+
+detMasterFrameRow *detMasterFrameObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detMasterFrameRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detMasterFrameSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detMasterFrameDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detMasterImfileRow data structure
+ *
+ * Structure for representing a single row of detMasterImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    char            *class_id;
+    char            *uri;
+    char            *stats;
+    char            *recipe;
+} detMasterImfileRow;
+
+/** Creates a new detMasterImfileRow object
+ *
+ *  @return A new detMasterImfileRow object or NULL on failure.
+ */
+
+detMasterImfileRow *detMasterImfileRowAlloc(
+    psS32           det_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Creates a new detMasterImfile table
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detMasterImfile table
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detMasterImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    char            **class_id,
+    char            **uri,
+    char            **stats,
+    char            **recipe
+);
+
+/** Insert a single detMasterImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detMasterImfileRow *object             ///< detMasterImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detMasterImfileRow on success or NULL on failure.
+ */
+
+detMasterImfileRow *detMasterImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detMasterImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detMasterImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detMasterImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detMasterImfileMetadataFromObject(
+    const detMasterImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detMasterImfileRow pointer or NULL on error
+ */
+
+detMasterImfileRow *detMasterImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detMasterImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detMasterImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detMasterImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detResidImfileAnalysisRow data structure
+ *
+ * Structure for representing a single row of detResidImfileAnalysis table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *class_id;
+    char            *stats;
+    char            *recipe;
+} detResidImfileAnalysisRow;
+
+/** Creates a new detResidImfileAnalysisRow object
+ *
+ *  @return A new detResidImfileAnalysisRow object or NULL on failure.
+ */
+
+detResidImfileAnalysisRow *detResidImfileAnalysisRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Creates a new detResidImfileAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detResidImfileAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *stats,
+    const char      *recipe
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **exp_id,
+    char            **class_id,
+    char            **stats,
+    char            **recipe
+);
+
+/** Insert a single detResidImfileAnalysisRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detResidImfileAnalysisRow *object             ///< detResidImfileAnalysisRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detResidImfileAnalysisRow on success or NULL on failure.
+ */
+
+detResidImfileAnalysisRow *detResidImfileAnalysisPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detResidImfileAnalysisRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detResidImfileAnalysisRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detResidImfileAnalysisMetadataFromObject(
+    const detResidImfileAnalysisRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detResidImfileAnalysisRow pointer or NULL on error
+ */
+
+detResidImfileAnalysisRow *detResidImfileAnalysisObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detResidImfileAnalysisRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detResidImfileAnalysisSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidImfileAnalysisDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detResidExpAnalysisRow data structure
+ *
+ * Structure for representing a single row of detResidExpAnalysis table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *stats;
+    char            *recipe;
+    bool            accept;
+} detResidExpAnalysisRow;
+
+/** Creates a new detResidExpAnalysisRow object
+ *
+ *  @return A new detResidExpAnalysisRow object or NULL on failure.
+ */
+
+detResidExpAnalysisRow *detResidExpAnalysisRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *stats,
+    const char      *recipe,
+    bool            accept
+);
+
+/** Creates a new detResidExpAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detResidExpAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *stats,
+    const char      *recipe,
+    bool            accept
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **exp_id,
+    char            **stats,
+    char            **recipe,
+    bool            *accept
+);
+
+/** Insert a single detResidExpAnalysisRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detResidExpAnalysisRow *object             ///< detResidExpAnalysisRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detResidExpAnalysisRow on success or NULL on failure.
+ */
+
+detResidExpAnalysisRow *detResidExpAnalysisPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detResidExpAnalysisRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detResidExpAnalysisRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detResidExpAnalysisMetadataFromObject(
+    const detResidExpAnalysisRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detResidExpAnalysisRow pointer or NULL on error
+ */
+
+detResidExpAnalysisRow *detResidExpAnalysisObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detResidExpAnalysisRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detResidExpAnalysisSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidExpAnalysisDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/// @}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // DETRESIDEXPANALYSIS_DB_H
Index: /tags/rel-0_0_2/ippdb/tests/Makefile.am
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/Makefile.am	(revision 7503)
@@ -0,0 +1,67 @@
+# copied from bison-1.875d
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4
+
+DISTCLEANFILES       = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+## ------------ ##
+## package.m4.  ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	{					\
+	  echo '# Signature of the current package.'; \
+	  echo 'm4_define([AT_PACKAGE_NAME],      [@PACKAGE_NAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_TARNAME],   [@PACKAGE_TARNAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_VERSION],   [@PACKAGE_VERSION@])'; \
+	  echo 'm4_define([AT_PACKAGE_STRING],    [@PACKAGE_STRING@])'; \
+	  echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+	} >$(srcdir)/package.m4
+
+## ------------ ##
+## Test suite.  ##
+## ------------ ##
+
+TESTSUITE = $(srcdir)/testsuite
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+	$(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
+	mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+	cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+	test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) $(TESTSUITE)
+
+#
+# END BISON
+#
+
+UNITS = \
+	alloc \
+	init \
+	cleanup \
+	createtable \
+    droptable \
+    insert \
+    pop \
+    insertobject \
+    popobject \
+    insertfits \
+    popfits \
+    selectrowsfits \
+    metadatafromobject \
+    objectfrommetadata
+
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+AM_CFLAGS   = @ippdb_CFLAGS@ $(PSLIB_CFLAGS)$
+AM_LDFLAGS  = $(PSLIB_LIBS)$
+LDADD       = $(top_builddir)/src/libippdb.la$
+
+check_PROGRAMS = dbsetup dbcleanup $(UNITS)
Index: /tags/rel-0_0_2/ippdb/tests/alloc.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/alloc.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/alloc.c	(revision 7503)
@@ -0,0 +1,1193 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        weatherRow      *object;
+
+        object = weatherRowAlloc(32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->temp01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->pressure == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_transparencyRow *object;
+
+        object = skyp_transparencyRowAlloc("a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->trans == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_absorptionRow *object;
+
+        object = skyp_absorptionRowAlloc("a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_emissionRow *object;
+
+        object = skyp_emissionRowAlloc("a string", 32.32, 32.32, 32.32, 32.32, 32.32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->continuum == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        dimmRow         *object;
+
+        object = dimmRowAlloc(32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->sigmax == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigmay == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fwhm == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->expttime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_irRow      *object;
+
+        object = skyp_irRowAlloc(64.64, 64.64, 64.64, 64.64, 32.32, 32.32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_var == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_x == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_y == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        domeRow         *object;
+
+        object = domeRowAlloc(32.32, true, true, true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->open == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->light == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->track == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        telescopeRow    *object;
+
+        object = telescopeRowAlloc("a string", 32.32, 32.32, 64.64, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->guide, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        summitExpRow    *object;
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzPendingExpRow *object;
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string", "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzPendingImfileRow *object;
+
+        object = pzPendingImfileRowAlloc("a string", -32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bytes == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->md5sum, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        newExpRow       *object;
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        newImfileRow    *object;
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawDetrendExpRow *object;
+
+        object = rawDetrendExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawScienceExpRow *object;
+
+        object = rawScienceExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawImfileRow    *object;
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p1PendingExpRow *object;
+
+        object = p1PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2PendingExpRow *object;
+
+        object = p2PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2PendingImfileRow *object;
+
+        object = p2PendingImfileRowAlloc("a string", "a string", "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2DoneExpRow    *object;
+
+        object = p2DoneExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2DoneImfileRow *object;
+
+        object = p2DoneImfileRowAlloc("a string", "a string", "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p3PendingExpRow *object;
+
+        object = p3PendingExpRowAlloc("a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p3_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detRunRow       *object;
+
+        object = detRunRowAlloc("a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->det_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detInputExpRow  *object;
+
+        object = detInputExpRowAlloc(-32, -32, "a string", "a string", "a string", "a string", -32, "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detProcessedImfileRow *object;
+
+        object = detProcessedImfileRowAlloc(-32, "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detStackedImfileRow *object;
+
+        object = detStackedImfileRowAlloc(-32, -32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detNormalizedImfileRow *object;
+
+        object = detNormalizedImfileRowAlloc(-32, -32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detMasterFrameRow *object;
+
+        object = detMasterFrameRowAlloc(-32, -32, "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->comment, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detMasterImfileRow *object;
+
+        object = detMasterImfileRowAlloc(-32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detResidImfileAnalysisRow *object;
+
+        object = detResidImfileAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detResidExpAnalysisRow *object;
+
+        object = detResidExpAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/cleanup.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/cleanup.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/cleanup.c	(revision 7503)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    ippdbCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/createtable.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/createtable.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/createtable.c	(revision 7503)
@@ -0,0 +1,473 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!weatherCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_transparencyCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_absorptionCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_emissionCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!dimmCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_irCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!domeCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!telescopeCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!summitExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzPendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzPendingImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!newExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!newImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawDetrendExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawScienceExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p1PendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2PendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2PendingImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2DoneExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2DoneImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p3PendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detRunCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detInputExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detProcessedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detStackedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detNormalizedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detMasterFrameCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detMasterImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detResidImfileAnalysisCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detResidExpAnalysisCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/dbcleanup.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/dbcleanup.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/dbcleanup.c	(revision 7503)
@@ -0,0 +1,48 @@
+#include <pslib.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS weather");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_transparency");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_absorption");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_emission");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dimm");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_ir");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dome");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS telescope");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawDetrendExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawScienceExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p1PendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p3PendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRun");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detInputExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detStackedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterFrame");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidImfileAnalysis");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidExpAnalysis");
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/dbsetup.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/dbsetup.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/dbsetup.c	(revision 7503)
@@ -0,0 +1,111 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // remove the table if it already exists
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS weather");
+    weatherCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_transparency");
+    skyp_transparencyCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_absorption");
+    skyp_absorptionCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_emission");
+    skyp_emissionCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dimm");
+    dimmCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_ir");
+    skyp_irCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dome");
+    domeCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS telescope");
+    telescopeCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitExp");
+    summitExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingExp");
+    pzPendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingImfile");
+    pzPendingImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newExp");
+    newExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newImfile");
+    newImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawDetrendExp");
+    rawDetrendExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawScienceExp");
+    rawScienceExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawImfile");
+    rawImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p1PendingExp");
+    p1PendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingExp");
+    p2PendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingImfile");
+    p2PendingImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneExp");
+    p2DoneExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneImfile");
+    p2DoneImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p3PendingExp");
+    p3PendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRun");
+    detRunCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detInputExp");
+    detInputExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedImfile");
+    detProcessedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detStackedImfile");
+    detStackedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedImfile");
+    detNormalizedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterFrame");
+    detMasterFrameCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterImfile");
+    detMasterImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidImfileAnalysis");
+    detResidImfileAnalysisCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidExpAnalysis");
+    detResidExpAnalysisCreateTable(dbh);
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/droptable.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/droptable.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/droptable.c	(revision 7503)
@@ -0,0 +1,473 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/init.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/init.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/init.c	(revision 7503)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = ippdbInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/insert.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/insert.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/insert.c	(revision 7503)
@@ -0,0 +1,473 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherInsert(dbh, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyInsert(dbh, "a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionInsert(dbh, "a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionInsert(dbh, "a string", 32.32, 32.32, 32.32, 32.32, 32.32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmInsert(dbh, 32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irInsert(dbh, 64.64, 64.64, 64.64, 64.64, 32.32, 32.32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeInsert(dbh, 32.32, true, true, true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeInsert(dbh, "a string", 32.32, 32.32, 64.64, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsert(dbh, "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsert(dbh, "a string", "a string", "a string", "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsert(dbh, "a string", -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsert(dbh, "a string", "a string", "a string", "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsert(dbh, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsert(dbh, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileInsert(dbh, "a string", "a string", "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileInsert(dbh, "a string", "a string", "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpInsert(dbh, "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsert(dbh, "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsert(dbh, -32, -32, "a string", "a string", "a string", "a string", -32, "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsert(dbh, -32, "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsert(dbh, -32, -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsert(dbh, -32, -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameInsert(dbh, -32, -32, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileInsert(dbh, -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisInsert(dbh, -32, -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisInsert(dbh, -32, -32, "a string", "a string", "a string", true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/insertfits.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/insertfits.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/insertfits.c	(revision 7503)
@@ -0,0 +1,818 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/insertobject.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/insertobject.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/insertobject.c	(revision 7503)
@@ -0,0 +1,690 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        weatherRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = weatherRowAlloc(32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_transparencyRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_transparencyRowAlloc("a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_absorptionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_absorptionRowAlloc("a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_emissionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_emissionRowAlloc("a string", 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        dimmRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = dimmRowAlloc(32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_irRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_irRowAlloc(64.64, 64.64, 64.64, 64.64, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        domeRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = domeRowAlloc(32.32, true, true, true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        telescopeRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = telescopeRowAlloc("a string", 32.32, 32.32, 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        summitExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfileRowAlloc("a string", -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newExpRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawDetrendExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawDetrendExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawScienceExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawScienceExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p1PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p1PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingImfileRowAlloc("a string", "a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneImfileRowAlloc("a string", "a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p3PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p3PendingExpRowAlloc("a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detRunRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunRowAlloc("a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detInputExpRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpRowAlloc(-32, -32, "a string", "a string", "a string", "a string", -32, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detProcessedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfileRowAlloc(-32, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detStackedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfileRowAlloc(-32, -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfileRowAlloc(-32, -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterFrameRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterFrameRowAlloc(-32, -32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterImfileRowAlloc(-32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidImfileAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidExpAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/metadatafromobject.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/metadatafromobject.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/metadatafromobject.c	(revision 7503)
@@ -0,0 +1,1442 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        psMetadata      *md;
+        weatherRow      *object;
+        bool            status;
+
+        object = weatherRowAlloc(32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = weatherMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF32(&status, md, "temp01") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "humi01") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "temp02") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "humi02") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "temp03") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "humi03") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "pressure") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_transparencyRow *object;
+        bool            status;
+
+        object = skyp_transparencyRowAlloc("a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_transparencyMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "trans") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "nstars") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exptime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "sky_bright") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_absorptionRow *object;
+        bool            status;
+
+        object = skyp_absorptionRowAlloc("a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_absorptionMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "disperser_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp1") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp2") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp3") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "nstars") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exptime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "sky_bright") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_emissionRow *object;
+        bool            status;
+
+        object = skyp_emissionRowAlloc("a string", 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_emissionMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "disperser_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp1") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp2") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp3") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "continuum") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exptime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        dimmRow         *object;
+        bool            status;
+
+        object = dimmRowAlloc(32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = dimmMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF32(&status, md, "sigmax") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "sigmay") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "fwhm") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "expttime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_irRow      *object;
+        bool            status;
+
+        object = skyp_irRowAlloc(64.64, 64.64, 64.64, 64.64, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_irMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF64(&status, md, "sky_bright") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "sky_var") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "fov_x") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "fov_y") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        domeRow         *object;
+        bool            status;
+
+        object = domeRowAlloc(32.32, true, true, true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = domeMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF32(&status, md, "az") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "open") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "light") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "track") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        telescopeRow    *object;
+        bool            status;
+
+        object = telescopeRowAlloc("a string", 32.32, 32.32, 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = telescopeMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "guide"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "alt") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "az") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        summitExpRow    *object;
+        bool            status;
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = summitExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingExpRow *object;
+        bool            status;
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzPendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingImfileRow *object;
+        bool            status;
+
+        object = pzPendingImfileRowAlloc("a string", -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzPendingImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "bytes") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "md5sum"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        newExpRow       *object;
+        bool            status;
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = newExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        newImfileRow    *object;
+        bool            status;
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = newImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawDetrendExpRow *object;
+        bool            status;
+
+        object = rawDetrendExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawDetrendExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawScienceExpRow *object;
+        bool            status;
+
+        object = rawScienceExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawScienceExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawImfileRow    *object;
+        bool            status;
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p1PendingExpRow *object;
+        bool            status;
+
+        object = p1PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p1PendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingExpRow *object;
+        bool            status;
+
+        object = p2PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2PendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingImfileRow *object;
+        bool            status;
+
+        object = p2PendingImfileRowAlloc("a string", "a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2PendingImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneExpRow    *object;
+        bool            status;
+
+        object = p2DoneExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2DoneExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneImfileRow *object;
+        bool            status;
+
+        object = p2DoneImfileRowAlloc("a string", "a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2DoneImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p3PendingExpRow *object;
+        bool            status;
+
+        object = p3PendingExpRowAlloc("a string", "a string", "a string", -32, "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p3PendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p3_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detRunRow       *object;
+        bool            status;
+
+        object = detRunRowAlloc("a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detRunMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "det_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detInputExpRow  *object;
+        bool            status;
+
+        object = detInputExpRowAlloc(-32, -32, "a string", "a string", "a string", "a string", -32, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detInputExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedImfileRow *object;
+        bool            status;
+
+        object = detProcessedImfileRowAlloc(-32, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detProcessedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detStackedImfileRow *object;
+        bool            status;
+
+        object = detStackedImfileRowAlloc(-32, -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detStackedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedImfileRow *object;
+        bool            status;
+
+        object = detNormalizedImfileRowAlloc(-32, -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detNormalizedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterFrameRow *object;
+        bool            status;
+
+        object = detMasterFrameRowAlloc(-32, -32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detMasterFrameMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "comment"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterImfileRow *object;
+        bool            status;
+
+        object = detMasterImfileRowAlloc(-32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detMasterImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detResidImfileAnalysisRow *object;
+        bool            status;
+
+        object = detResidImfileAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detResidImfileAnalysisMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detResidExpAnalysisRow *object;
+        bool            status;
+
+        object = detResidExpAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detResidExpAnalysisMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "stats"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "accept") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/objectfrommetadata.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/objectfrommetadata.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/objectfrommetadata.c	(revision 7503)
@@ -0,0 +1,2129 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        psMetadata      *md;
+        weatherRow      *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = weatherObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->temp01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->pressure == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_transparencyRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_transparencyObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->trans == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_absorptionRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_absorptionObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_emissionRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_emissionObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->continuum == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        dimmRow         *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = dimmObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->sigmax == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigmay == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fwhm == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->expttime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_irRow      *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_irObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_var == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_x == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_y == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        domeRow         *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = domeObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->open == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->light == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->track == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        telescopeRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = telescopeObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->guide, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        summitExpRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bytes == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->md5sum, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        newExpRow       *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        newImfileRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawDetrendExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawDetrendExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawScienceExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawScienceExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawImfileRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p1PendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p1PendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneExpRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p3PendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p3PendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p3_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detRunRow       *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->det_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detInputExpRow  *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detStackedImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterFrameRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "comment", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterFrameObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->comment, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detResidImfileAnalysisRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileAnalysisObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detResidExpAnalysisRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "stats", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpAnalysisObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->stats, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/pop.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/pop.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/pop.c	(revision 7503)
@@ -0,0 +1,668 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psF32           temp01;
+        psF32           humi01;
+        psF32           temp02;
+        psF32           humi02;
+        psF32           temp03;
+        psF32           humi03;
+        psF32           pressure;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherPop(dbh, &temp01, &humi01, &temp02, &humi02, &temp03, &humi03, &pressure)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            filter[256];
+        psF64           trans;
+        psS32           nstars;
+        psF64           ra;
+        psF64           decl;
+        psF32           exptime;
+        psF64           sky_bright;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyPop(dbh, (char **)&filter, &trans, &nstars, &ra, &decl, &exptime, &sky_bright)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            disperser_id[256];
+        psF32           atmcomp1;
+        psF32           atmcomp2;
+        psF32           atmcomp3;
+        psS32           nstars;
+        psF64           ra;
+        psF64           decl;
+        psF32           exptime;
+        psF64           sky_bright;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &nstars, &ra, &decl, &exptime, &sky_bright)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            disperser_id[256];
+        psF32           atmcomp1;
+        psF32           atmcomp2;
+        psF32           atmcomp3;
+        psF32           continuum;
+        psF32           exptime;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &continuum, &exptime)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psF32           sigmax;
+        psF32           sigmay;
+        psF32           fwhm;
+        psF64           ra;
+        psF64           decl;
+        psF32           expttime;
+        char            telescope_id[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmPop(dbh, &sigmax, &sigmay, &fwhm, &ra, &decl, &expttime, (char **)&telescope_id)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psF64           sky_bright;
+        psF64           sky_var;
+        psF64           ra;
+        psF64           decl;
+        psF32           fov_x;
+        psF32           fov_y;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irPop(dbh, &sky_bright, &sky_var, &ra, &decl, &fov_x, &fov_y)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psF32           az;
+        bool            open;
+        bool            light;
+        bool            track;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domePop(dbh, &az, &open, &light, &track)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            guide[256];
+        psF32           alt;
+        psF32           az;
+        psF64           ra;
+        psF64           decl;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopePop(dbh, (char **)&guide, &alt, &az, &ra, &decl)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        psS32           bytes;
+        char            md5sum[256];
+        char            class[256];
+        char            class_id[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfilePop(dbh, (char **)&exp_id, &bytes, (char **)&md5sum, (char **)&class, (char **)&class_id, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class[256];
+        char            class_id[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfilePop(dbh, (char **)&exp_id, (char **)&class, (char **)&class_id, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+        char            recipe[256];
+        psS32           p1_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p1_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+        char            recipe[256];
+        psS32           p2_version;
+        psS32           p3_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats, (char **)&recipe, &p2_version, &p3_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            det_type[256];
+        psS32           iteration;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunPop(dbh, (char **)&det_type, &iteration)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        char            stats[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, (char **)&stats)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfilePop(dbh, &det_id, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            comment[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFramePop(dbh, &det_id, &iteration, (char **)&comment)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        char            class_id[256];
+        char            uri[256];
+        char            stats[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfilePop(dbh, &det_id, (char **)&class_id, (char **)&uri, (char **)&stats, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            exp_id[256];
+        char            class_id[256];
+        char            stats[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&class_id, (char **)&stats, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            exp_id[256];
+        char            stats[256];
+        char            recipe[256];
+        bool            accept;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&stats, (char **)&recipe, &accept)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/popfits.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/popfits.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/popfits.c	(revision 7503)
@@ -0,0 +1,818 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFramePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/popobject.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/popobject.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/popobject.c	(revision 7503)
@@ -0,0 +1,566 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        weatherRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = weatherPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_transparencyRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_transparencyPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_absorptionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_absorptionPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_emissionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_emissionPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        dimmRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = dimmPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_irRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_irPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        domeRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = domePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        telescopeRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = telescopePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        summitExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newExpRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawDetrendExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawDetrendExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawScienceExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawScienceExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p1PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p1PendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p3PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p3PendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detRunRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detInputExpRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detProcessedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detStackedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterFrameRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterFramePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidImfileAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileAnalysisPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidExpAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpAnalysisPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/selectrowsfits.c
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/selectrowsfits.c	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/selectrowsfits.c	(revision 7503)
@@ -0,0 +1,692 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencySelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_0_2/ippdb/tests/testsuite.at
===================================================================
--- /tags/rel-0_0_2/ippdb/tests/testsuite.at	(revision 7503)
+++ /tags/rel-0_0_2/ippdb/tests/testsuite.at	(revision 7503)
@@ -0,0 +1,158 @@
+m4_version_prereq([2.59])
+
+AT_INIT
+
+###
+AT_SETUP([Alloc()])
+AT_KEYWORDS([Alloc])
+
+AT_CHECK([alloc])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Init()])
+AT_KEYWORDS([Init])
+
+AT_CHECK([init])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Cleanup()])
+AT_KEYWORDS([Cleanup])
+
+AT_CHECK([cleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([CreateTable()])
+AT_KEYWORDS([CreateTable])
+
+# make sure the table is not there.
+AT_CHECK([dbcleanup])
+AT_CHECK([createtable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([DropTable()])
+AT_KEYWORDS([DropTable])
+
+# make sure the table is not there.
+AT_CHECK([dbsetup])
+AT_CHECK([droptable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Insert()])
+AT_KEYWORDS([Insert])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insert])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Pop()])
+AT_KEYWORDS([Pop])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([pop])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([InsertObject()])
+AT_KEYWORDS([InsertObject])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insertobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([PopObject()])
+AT_KEYWORDS([PopObject])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([InsertFits()])
+AT_KEYWORDS([InsertFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+# run popfis so there is afits file to read
+AT_CHECK([popfits])
+AT_CHECK([insertfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([PopFits()])
+AT_KEYWORDS([PopFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([SelectRowsFits()])
+AT_KEYWORDS([SelectRowsFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([selectrowsfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([MetadataFromObject()])
+AT_KEYWORDS([MetadataFromObject])
+
+AT_CHECK([metadatafromobject])
+
+AT_CLEANUP
+
+###
+AT_SETUP([ObjectFromMetadata()])
+AT_KEYWORDS([ObjectFromMetadata])
+
+AT_CHECK([objectfrommetadata])
+
+AT_CLEANUP
+
+###
Index: /tags/rel-0_0_2/pois/.cvsignore
===================================================================
--- /tags/rel-0_0_2/pois/.cvsignore	(revision 7503)
+++ /tags/rel-0_0_2/pois/.cvsignore	(revision 7503)
@@ -0,0 +1,5 @@
+Makefile Makefile.in
+aclocal.m4 autom4te.cache
+config.* configure
+depcomp install-sh libtool ltmain.sh missing mkinstalldirs compile stamp-h*
+pois.pc
Index: /tags/rel-0_0_2/pois/INSTALL
===================================================================
--- /tags/rel-0_0_2/pois/INSTALL	(revision 7503)
+++ /tags/rel-0_0_2/pois/INSTALL	(revision 7503)
@@ -0,0 +1,62 @@
+$ cvs co jhbuild
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Setup $HOME/.jhbuildrc to install in (say) $HOME/JHB
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+I found that I needed to say
+
+$ jhbuild boostrap
+
+in order to get my autoconf macros recognised
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+$ jhbuild build pslib
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Install
+	ac_pkg_swig.m4      ac_python_devel.m4
+into
+	$HOME/JHB/share/aclocal
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+To build/use the code, you'll have to source some script such as:
+
+export JHTREE=$HOME/JHB/Linux
+export PYTHONPATH=$HOME/Python:$HOME/Pan-STARRS/Pois/python:$HOME/Pan-STARRS/Pois/swig:$JHTREE/lib
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JHTREE/lib
+export PATH=$JHTREE/bin:$(echo $PATH | perl -pe "s|$JHTREE/bin||")
+export PKG_CONFIG_PATH=$JHTREE/lib/pkgconfig:$PKG_CONFIG_PATH
+hash -r
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+You need to set XPA_DIR to somewhere that the xpa libraries can be found;
+or (better) install the xpa stuff in $JHTREE/{lib,include}
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+To build from scratch:
+
+./autogen.sh --prefix=$JHTREE
+
+(note that you set JHTREE above)
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+To be able to display images, ensure that ds9 and xpans are in your path (xpans
+is a utility used to communicate with ds9)
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+When all is setup, say:
+
+$ python
+>>> import pois
+>>> help(pois.run)
+>>> pois.run(fileDir="data", verbose=3, display={'stamps':1, 'reference':1, 'subtracted':1})
Index: /tags/rel-0_0_2/pois/Makefile.am
===================================================================
--- /tags/rel-0_0_2/pois/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/pois/Makefile.am	(revision 7503)
@@ -0,0 +1,5 @@
+SUBDIRS = src #swig
+
+CLEANFILES = *.pyc *~ core core.*
+
+EXTRA_DIST = autogen.sh
Index: /tags/rel-0_0_2/pois/NOTES
===================================================================
--- /tags/rel-0_0_2/pois/NOTES	(revision 7503)
+++ /tags/rel-0_0_2/pois/NOTES	(revision 7503)
@@ -0,0 +1,153 @@
+Installed
+	ac_python_devel.m4
+	ac_pkg_swig.m4
+in ~/JHB/Tree/share/aclocal
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Added
+	AC_PROG_SWIG(1.3.17)
+	SWIG_MULTI_MODULE_SUPPORT
+	SWIG_PYTHON
+
+...	
+
+	AC_CONFIG_FILES(pois.pc Makefile src/Makefile swig/Makefile)
+	
+to configure.ac
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Need to fix swig autoconf so that the configure chooses the target language[s],
+but the same Makefile.am works for both
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Initially running make fails due to:
+	  Makefile:261: .deps/libswigrun..Po: No such file or directory
+	  make: *** No rule to make target `.deps/libswigrun..Po'.  Stop.
+Changing the
+include ./$(DEPDIR)/libswigrun.$(SO).Po
+in Makefile (not Makefile.am) to
+-include ./$(DEPDIR)/libswigrun.$(SO).Po
+and running make successfully bootstrapped things.
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+cp ~/Pan-STARRS/psLib/cvs/src/parseErrorCodes.pl ~/JHB/Tree/bin
+
+(N.b. this is the RHL version)
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+autogen.sh --prefix=$HOME/JHB/Tree 
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Need autoconf for xpa
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Need swig package (1.3.24?)
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Need to add Tree/lib to PYTHONPATH (and also PERL5LIB?)
+
+export PYTHONPATH=$PYTHONPATH:$HOME/Pan-STARRS/Price/python:$HOME/Pan-STARRS/Price/swig:$HOME/JHB/Tree/lib
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+How do I make a .so file LOCALLY, without having to do a make install?
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+Why do things suddenly get installed as _xpa.0.so??
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+If I try to build pois as well as libpois.a, the compiler fails to find pslib
+while building pois.c
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+
+psBinaryOp isn't implemented for & and |
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+psBinaryOp &/| can be faked with a cast to long, but this will fail for some
+unsigned types.  Casting to unsigned will fail on some machines. Sigh.
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+psImageSubset SDRS should allow -ve row0/col0 as well as row1/col1
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+psBinaryOp fails if e.g. data1 is U8 and the (scalar) data2 is U32.
+Shouldn't it handle promotions?
+
+The SDRS reads:
+ Operations between data structures with different types (e.g., psS32
+ and psF32) are not allowed and must raise an error (it is the
+ responsibility of calling functions to perform type conversions).
+
+but I think that integers should all be handled via the usual C rules
+(which are only tricky for unsigned).
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+The file src/libpois.a has to exist
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+psArray* psArrayAlloc(psU32 nalloc)
+
+fails to initialise the array to NULLs (not specified in the SDRS)
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+"bool psListAdd(psList *list, int location, void *data);
+
+the first function, psListAdd, adds an entry to the list and returns a
+boolean giving the success or failure of the operation."
+
+(SDRS).  It'd be better to define
+
+psList *psListAdd(psList *list, int location, void *data);
+and to make
+    psListAdd(NULL, location, data);
+equivalent to
+    psListAlloc(data);
+
+That makes the code to append to a list simpler; instead of
+
+     list = NULL;
+     while(foo) {
+        ...
+	foo = psFooAlloc();
+	if(list == NULL) {
+	   list = psListAlloc(foo);
+	} else {
+	   bool ok = psListAdd(list, PS_LIST_TAIL, foo);
+	   assert(ok);
+	}
+	psFree(foo);
+     }
+
+I can simply write:
+
+     list = NULL;
+     while(foo) {
+        ...
+	foo = psFooAlloc();
+        psListAdd(list, PS_LIST_TAIL, foo);
+	psFree(foo);
+     }
+
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
Index: /tags/rel-0_0_2/pois/autogen.sh
===================================================================
--- /tags/rel-0_0_2/pois/autogen.sh	(revision 7503)
+++ /tags/rel-0_0_2/pois/autogen.sh	(revision 7503)
@@ -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=pois
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+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 -I m4 || 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: /tags/rel-0_0_2/pois/configure.ac
===================================================================
--- /tags/rel-0_0_2/pois/configure.ac	(revision 7503)
+++ /tags/rel-0_0_2/pois/configure.ac	(revision 7503)
@@ -0,0 +1,45 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+
+AC_INIT([pois], [0.0.2], [http://pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([autogen.sh])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_LANG(C)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES(PSLIB, pslib >= 0.9.0) 
+
+dnl Set CFLAGS for build
+pois_CFLAGS="-Wall -Werror -g -O3 -std=c99"
+AC_SUBST([pois_CFLAGS])
+
+AC_C_BIGENDIAN([PS_BIGENDIAN=1],[PS_BIGENDIAN=0])
+
+dnl AC_PROG_SWIG(1.3.24)
+dnl SWIG_MULTI_MODULE_SUPPORT
+dnl SWIG_PYTHON
+
+dnl lifted from autoconf 2.59's configure.ac
+AC_PATH_PROG([PERL], perl, no)
+AC_SUBST([PERL])
+if test "$PERL" = no; then
+  AC_MSG_ERROR([perl is not found])
+fi
+$PERL -e 'require 5.005_03;' || {
+   AC_MSG_ERROR([Perl 5.005_03 or better is required])
+}
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+dnl swig/Makefile
+dnl python/Makefile
+
+AC_OUTPUT
Index: /tags/rel-0_0_2/pois/m4/ac_pkg_swig.m4
===================================================================
--- /tags/rel-0_0_2/pois/m4/ac_pkg_swig.m4	(revision 7503)
+++ /tags/rel-0_0_2/pois/m4/ac_pkg_swig.m4	(revision 7503)
@@ -0,0 +1,160 @@
+dnl @synopsis AC_PROG_SWIG([major.minor.micro])
+dnl
+dnl This macro searches for a SWIG installation on your system. If
+dnl found you should call SWIG via $(SWIG). You can use the optional
+dnl first argument to check if the version of the available SWIG is
+dnl greater than or equal to the value of the argument. It should have
+dnl the format: N[.N[.N]] (N is a number between 0 and 999. Only the
+dnl first N is mandatory.)
+dnl
+dnl If the version argument is given (e.g. 1.3.17), AC_PROG_SWIG checks
+dnl that the swig package is this version number or higher.
+dnl
+dnl In configure.in, use as:
+dnl
+dnl             AC_PROG_SWIG(1.3.17)
+dnl             SWIG_ENABLE_CXX
+dnl             SWIG_MULTI_MODULE_SUPPORT
+dnl             SWIG_PYTHON
+dnl
+dnl @category InstalledPackages
+dnl @author Sebastian Huber <sebastian-huber@web.de>
+dnl @author Alan W. Irwin <irwin@beluga.phys.uvic.ca>
+dnl @author Rafael Laboissiere <rafael@laboissiere.net>
+dnl @author Andrew Collier <abcollier@yahoo.com>
+dnl @version 2004-09-20
+dnl @license GPLWithACException
+
+AC_DEFUN([AC_PROG_SWIG],[
+        AC_PATH_PROG([SWIG],[swig])
+        if test -z "$SWIG" ; then
+                AC_MSG_WARN([cannot find 'swig' program. You should look at http://www.swig.org])
+                SWIG='echo "Error: SWIG is not installed. You should look at http://www.swig.org" ; false'
+        elif test -n "$1" ; then
+                AC_MSG_CHECKING([for SWIG version])
+                [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`]
+                AC_MSG_RESULT([$swig_version])
+                if test -n "$swig_version" ; then
+                        # Calculate the required version number components
+                        [required=$1]
+                        [required_major=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_major" ; then
+                                [required_major=0]
+                        fi
+                        [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
+                        [required_minor=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_minor" ; then
+                                [required_minor=0]
+                        fi
+                        [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
+                        [required_patch=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_patch" ; then
+                                [required_patch=0]
+                        fi
+                        # Calculate the available version number components
+                        [available=$swig_version]
+                        [available_major=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_major" ; then
+                                [available_major=0]
+                        fi
+                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+                        [available_minor=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_minor" ; then
+                                [available_minor=0]
+                        fi
+                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+                        [available_patch=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_patch" ; then
+                                [available_patch=0]
+                        fi
+                        if test $available_major -ne $required_major \
+                                -o $available_minor -ne $required_minor \
+                                -o $available_patch -lt $required_patch ; then
+                                AC_MSG_WARN([SWIG version >= $1 is required.  You have $swig_version.  You should look at http://www.swig.org])
+                                SWIG='echo "Error: SWIG version >= $1 is required.  You have '"$swig_version"'.  You should look at http://www.swig.org" ; false'
+                        else
+                                AC_MSG_NOTICE([SWIG executable is '$SWIG'])
+                                SWIG_LIB=`$SWIG -swiglib`
+                                AC_MSG_NOTICE([SWIG library directory is '$SWIG_LIB'])
+                        fi
+                else
+                        AC_MSG_WARN([cannot determine SWIG version])
+                        SWIG='echo "Error: Cannot determine SWIG version.  You should look at http://www.swig.org" ; false'
+                fi
+        fi
+        AC_SUBST([SWIG_LIB])
+])
+
+# SWIG_ENABLE_CXX()
+#
+# Enable SWIG C++ support.  This affects all invocations of $(SWIG).
+AC_DEFUN([SWIG_ENABLE_CXX],[
+        AC_REQUIRE([AC_PROG_SWIG])
+        AC_REQUIRE([AC_PROG_CXX])
+        SWIG="$SWIG -c++"
+])
+
+# SWIG_MULTI_MODULE_SUPPORT()
+#
+# Enable support for multiple modules.  This effects all invocations
+# of $(SWIG).  You have to link all generated modules against the
+# appropriate SWIG runtime library.  If you want to build Python
+# modules for example, use the SWIG_PYTHON() macro and link the
+# modules against $(SWIG_LANG_LIBS).
+#
+AC_DEFUN([SWIG_MULTI_MODULE_SUPPORT],[
+        AC_REQUIRE([AC_PROG_SWIG])
+        #SWIG="$SWIG -noruntime"  # Not needed for SWIG >= 1.3.24
+])
+
+# SWIG_PYTHON([use-shadow-classes = {no, yes}])
+#
+# Checks for Python and provides the $(SWIG_PYTHON_CPPFLAGS),
+# and $(SWIG_PYTHON_OPT) output variables.
+#
+# $(SWIG_PYTHON_OPT) contains all necessary SWIG options to generate
+# code for Python.  Shadow classes are enabled unless the value of the
+# optional first argument is exactly 'no'.  If you need multi module
+# support (provided by the SWIG_MULTI_MODULE_SUPPORT() macro) use
+# $(SWIG_PYTHON_LIBS) to link against the appropriate library.  It
+# contains the SWIG Python runtime library that is needed by the type
+# check system for example.
+AC_DEFUN([SWIG_PYTHON],[
+        AC_REQUIRE([AC_PROG_SWIG])
+        AC_REQUIRE([AC_PYTHON_DEVEL])
+        test "x$1" != "xno" || swig_shadow=" -noproxy"
+        AC_SUBST([SWIG_PYTHON_OPT],[-python$swig_shadow])
+        AC_SUBST([SWIG_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS])
+        AC_SUBST([SWIG_PYTHON_LIBS],[$PYTHON_LIBS])
+
+        AC_SUBST([SWIG_TARGET_OPT],     [$SWIG_PYTHON_OPT])
+        AC_SUBST([SWIG_TARGET_CPPFLAGS],[$SWIG_PYTHON_CPPFLAGS])
+        AC_SUBST([SWIG_TARGET_LIBS],    [$SWIG_PYTHON_LIBS])
+])
+
+
+dnl @synopsis AC_LIB_WAD
+dnl
+dnl This macro searches for installed WAD library.
+dnl
+AC_DEFUN([AC_LIB_WAD],
+[
+        AC_REQUIRE([AC_PYTHON_DEVEL])
+        AC_ARG_ENABLE(wad,
+        AC_HELP_STRING([--enable-wad], [enable wad module]),
+        [
+                case "${enableval}" in
+                        no)     ;;
+                        *)      if test "x${enableval}" = xyes;
+                                then
+                                        check_wad="yes"
+                                fi ;;
+                esac
+        ], [])
+
+        if test -n "$check_wad";
+        then
+                AC_CHECK_LIB(wadpy, _init, [WADPY=-lwadpy], [], $PYTHON_LDFLAGS $PYTHON_EXTRA_LIBS)
+                AC_SUBST(WADPY)
+        fi
+])
Index: /tags/rel-0_0_2/pois/m4/ac_python_devel.m4
===================================================================
--- /tags/rel-0_0_2/pois/m4/ac_python_devel.m4	(revision 7503)
+++ /tags/rel-0_0_2/pois/m4/ac_python_devel.m4	(revision 7503)
@@ -0,0 +1,64 @@
+dnl @synopsis AC_PYTHON_DEVEL
+dnl
+dnl Checks for Python and tries to get the include path to 'Python.h'.
+dnl It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) output
+dnl variable.
+dnl
+dnl @category InstalledPackages
+dnl @author Sebastian Huber <sebastian-huber@web.de>
+dnl @author Alan W. Irwin <irwin@beluga.phys.uvic.ca>
+dnl @author Rafael Laboissiere <laboissiere@psy.mpg.de>
+dnl @author Andrew Collier <colliera@nu.ac.za>
+dnl @version 2004-07-14
+dnl @license GPLWithACException
+
+AC_DEFUN([AC_PYTHON_DEVEL],[
+	#
+	# should allow for checking of python version here...
+	#
+	AC_REQUIRE([AM_PATH_PYTHON])
+
+	# Check for Python include path
+	AC_MSG_CHECKING([for Python include path])
+	python_path=`echo $PYTHON | sed "s,/bin.*$,,"`
+	for i in "$python_path/include/python$PYTHON_VERSION/" "$python_path/include/python/" "$python_path/" ; do
+		python_path=`find $i -type f -name Python.h -print | sed "1q"`
+		if test -n "$python_path" ; then
+			break
+		fi
+	done
+	python_path=`echo $python_path | sed "s,/Python.h$,,"`
+	AC_MSG_RESULT([$python_path])
+	if test -z "$python_path" ; then
+		AC_MSG_ERROR([cannot find Python include path])
+	fi
+	AC_SUBST([PYTHON_CPPFLAGS],[-I$python_path])
+
+	# Check for Python library path
+	AC_MSG_CHECKING([for Python library path])
+	python_path=`echo $PYTHON | sed "s,/bin.*$,,"`
+	for i in "$python_path/lib/python$PYTHON_VERSION/config/" "$python_path/lib/python$PYTHON_VERSION/" "$python_path/lib/python/config/" "$python_path/lib/python/" "$python_path/" ; do
+		python_path=`find $i -type f -name libpython$PYTHON_VERSION.* -print | sed "1q"`
+		if test -n "$python_path" ; then
+			break
+		fi
+	done
+	python_path=`echo $python_path | sed "s,/libpython.*$,,"`
+	AC_MSG_RESULT([$python_path])
+	if test -z "$python_path" ; then
+		AC_MSG_ERROR([cannot find Python library path])
+	fi
+	AC_SUBST([PYTHON_LDFLAGS],["-L$python_path -lpython$PYTHON_VERSION"])
+	#
+	python_site=`echo $python_path | sed "s/config/site-packages/"`
+	AC_SUBST([PYTHON_SITE_PKG],[$python_site])
+	#
+	# libraries which must be linked in when embedding
+	#
+	AC_MSG_CHECKING(python extra libraries)
+	PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \
+                conf = distutils.sysconfig.get_config_var; \
+                print conf('LOCALMODLIBS')+' '+conf('LIBS')"
+	AC_MSG_RESULT($PYTHON_EXTRA_LIBS)`
+	AC_SUBST(PYTHON_EXTRA_LIBS)
+])
Index: /tags/rel-0_0_2/pois/python/.cvsignore
===================================================================
--- /tags/rel-0_0_2/pois/python/.cvsignore	(revision 7503)
+++ /tags/rel-0_0_2/pois/python/.cvsignore	(revision 7503)
@@ -0,0 +1,2 @@
+Makefile Makefile.in
+*.pyc
Index: /tags/rel-0_0_2/pois/python/ds9.py
===================================================================
--- /tags/rel-0_0_2/pois/python/ds9.py	(revision 7503)
+++ /tags/rel-0_0_2/pois/python/ds9.py	(revision 7503)
@@ -0,0 +1,163 @@
+#
+# XPA
+#
+import os, re, math, sys, time
+
+try: import xpaSwig as xpa
+except: print "Cannot import xpa"
+
+class Ds9Error(IOError):
+    """Some problem talking to ds9"""
+
+def ds9Cmd(cmd):
+   """Issue a ds9 command, raising errors as appropriate"""
+   
+   try:
+      xpa.XPASet(None, "ds9", cmd, "", "", 0)
+   except IOError:
+      raise Ds9Error, "XPA: (%s)" % cmd
+
+def initDS9(execDs9 = 1):
+   try:
+      ds9Cmd("iconify no; raise")
+   except IOError:
+      if execDs9:
+         print "ds9 doesn't appear to be running, I'll exec it for you"
+         if not re.search('xpa', os.environ['PATH']):
+            raise Ds9Error, 'You need the xpa binaries in your path to use ds9 with python'
+
+         os.system('ds9 &')
+         for i in range(0,10):
+            try:
+               ds9Cmd("frame 0; scale histequ; scale mode minmax")
+               return
+            except IOError:
+               print "waiting for ds9...\r",
+               time.sleep(0.5)
+            else:
+               break
+
+         print "                  \r",
+         sys.stdout.flush();
+
+      raise Ds9Error
+
+def mtv(data, meta = None, frame = 0, init = 1, WCS = ""):
+   """Display an OTA or Cell on a DS9 display"""
+	
+   if frame == None:
+      return
+   
+   if init:
+      for i in range(0,3):
+         try:
+            initDS9(i == 0)
+         except IOError:
+            print "waiting for ds9...\r", ; sys.stdout.flush();
+            time.sleep(0.5)
+         else:
+            break
+         
+   ds9Cmd("frame %d" % frame)
+
+   if 1:
+      xpa_cmd = "xpaset ds9 fits"
+      pfd = os.popen(xpa_cmd, "w")
+   else:
+      pfd = file("foo.fits", "w")
+      
+   xpa.rhlWriteFits(pfd.fileno(), data, meta, WCS)
+
+   try:
+       pass
+   except:
+       print "Error"
+       pass
+
+   try:
+       pfd.close()
+   except:
+       pass
+#
+# Graphics commands
+#
+def erase(frame = 0):
+   """Erase the specified DS9 frame"""
+   if frame == None:
+      return
+
+   ds9Cmd("frame %d; regions delete all" % frame)
+
+def dot(symb, c, r, frame = 0, size = 2, ctype = 'green'):
+   """Draw a symbol onto the specfied DS9 frame at (row,col) = (r,c) [0-based coordinates]
+Possible values are:
+	+	Draw a +
+	x	Draw an x
+        o	Draw a circle
+Any other value is interpreted as a string to be drawn
+"""
+   if frame == None:
+      return
+
+   cmd = "frame %d; regions physical; " % frame
+   r += 1; c += 1;                      # ds9 uses 1-based coordinates
+   if (symb == '+'):
+      cmd += 'regions line %g %g %g %g # color=%s; ' % (c, r+size, c, r-size, ctype)
+      cmd += 'regions line %g %g %g %g ' % (c-size, r, c+size, r)
+   elif (symb == 'x'):
+      size = size/math.sqrt(2)
+      cmd += 'regions line %g %g %g %g # color=%s; ; ' % (c+size, r+size, c-size, r-size, ctype)
+      cmd += 'regions line %g %g %g %g ' % (c-size, r+size, c+size, r-size)
+   elif (symb == 'o'):
+      cmd += 'regions circle %g %g %g ' % (c, r, size)
+   else:
+      cmd += 'regions text %g %g \"%s\"' % (c, r, symb)
+
+   cmd += ' # color=%s' % ctype
+
+   ds9Cmd(cmd)
+
+def line(points, frame = 0, symbs = False, ctype = 'green'):
+   """Draw a set of symbols or connect the points, a list of (row,col)
+If symbs is True, draw points at the specified points using the desired symbol,
+otherwise connect the dots.  Ctype is the name of a colour (e.g. 'red')"""
+   
+   if frame == None:
+      return
+
+   if symbs:
+      for (r, c) in points:
+         dot(symbs, c, r, frame = frame, size = 0.5, ctype = ctype)
+   else:
+      cmd = "frame %d; regions image; regions line " % (frame)
+
+      for (r, c) in points:
+         r += 1; c += 1;                   # ds9 uses 1-based coordinates
+         cmd += '%g %g ' % (c, r)
+         
+      cmd += ' # color=%s' % ctype
+         
+      ds9Cmd(cmd)
+#
+# Zoom and Pan
+#
+def zoom(zoomfac = None, rowc = None, colc = None, frame = 0):
+   """Zoom frame by specified amount, optionally panning also"""
+
+   if frame == None:
+      return
+
+   if (rowc and not colc) or (not rowc and colc):
+      raise Ds9Error, "Please specify row and column center to pan about"
+   
+   if zoomfac == None and rowc == None:
+      zoomfac = 2
+
+   cmd = ""
+   if zoomfac != None:
+      cmd += "zoom to %d; " % zoomfac
+
+   if rowc != None:
+      cmd += "pan to %g %g physical; " % (colc + 1, rowc + 1) # ds9 is 1-indexed. Grrr
+
+   ds9Cmd(cmd)
Index: /tags/rel-0_0_2/pois/python/pois.py
===================================================================
--- /tags/rel-0_0_2/pois/python/pois.py	(revision 7503)
+++ /tags/rel-0_0_2/pois/python/pois.py	(revision 7503)
@@ -0,0 +1,360 @@
+#
+# POIS: Pan-STARRS (or Pricey's) Optimal Image Subtraction
+#
+import os, re, sys, time
+import ds9
+import ps
+import psLibSwig as psLib; from psLibSwig import psTraceSetLevel, psTrace
+import poisSwig as pois
+
+def getTime():
+    return time.clock()
+
+def displayImage(image, frame = 0, caption = None, ctype = "green", init = False):
+    global init_display
+    if init_display:
+        init = True
+        
+    ds9.mtv(image, init = init, frame = frame)
+    if caption:
+        ds9.dot(caption, 10, image.numRows + 5, ctype = ctype, frame = frame)
+
+    init_display = False
+
+def run(showLeaks = True, *args, **nargs):
+    #"""Wrapper for run, with a check on memory leaks after out-of-scope
+    #objects have been garbage collected."""
+    #
+    # Only look for leaks of memory allocated after this point
+    #
+    id0 = psLib.psMemGetId()
+    #
+    # Do the work
+    #
+    value = _run(*args, **nargs)
+    #
+    # Look for otherwise-undetected problems
+    #
+    ps.raisePsError()
+
+    nleak = psLib.psMemCheckLeaks(id0, None, None, False)
+    if nleak:
+        if showLeaks != False:
+            ps.memCheckLeaks(sys.stderr, id0 = id0, addresses = True)
+            
+        raise IOError, "Memory Leak (%d pointers); id0 = %d" % (nleak, id0)
+
+    return value
+
+def _run(fileDir = ".", refFile = "test1.fits", inFile = "test2.fits", outFile=None,
+         section = "[100:500,100:500]",
+         testing = False, verbose = 0, psLib_verbose = 0,
+         display = {}, setTrace = True):
+    """POIS: Pan-STARRS (or Pricey's) Optimal Image Subtraction.
+
+Attempts to fit for a general convolution kernel that will match the PSFs between two images.
+Arguments:
+   fileDir	Directory for input and output files (default: .)
+   refFile      Reference image (default: test1.fits)
+   inFile       File to be analysed (default: test2.fits)
+   outFile	Write output to this file (default: none)
+   section	Section of datafiles to process, or None (=> all) (default: [100:500,100:500])
+
+   testing	Write some extra test information/files (default: False)
+   verbose      Verbosity passed to psTrace (default: 0).0, psLib_verbose = 0,
+   setTrace	Should the code fiddle with trace levels? (default: True)
+
+   display	Dictionary of things that can be displayed; use display="help" for help (default: {})
+
+E.g.
+   pois.run(fileDir="data", verbose=3, display={'stamps':1, 'reference':1, 'subtracted':1})
+"""
+
+    startTime = getTime();
+
+    if setTrace:
+        psTraceSetLevel(".", 0)
+        psTraceSetLevel("pois", verbose)
+        psTraceSetLevel("pois.config", verbose)
+        psTraceSetLevel("pois.time", verbose + 2)
+        psTraceSetLevel("pois.solution", 0)
+        psTraceSetLevel("pois.kernelBasisFunctions", verbose)
+        psTraceSetLevel("pois.calculateEquation", verbose)
+        psTraceSetLevel("pois.convolveImage", verbose)
+        psTraceSetLevel("pois.findStamps", verbose)
+        psTraceSetLevel("pois.parseConfig", verbose)
+        psTraceSetLevel("pois.makeMask", verbose)
+        psTraceSetLevel("pois.extractKernel", verbose)
+        psTraceSetLevel("pois.calculateDeviations", verbose)
+
+        psTraceSetLevel("ps.utils", psLib_verbose)
+
+        if verbose > 3:
+            print "Trace levels:"
+            psLib.psTracePrintLevels()
+
+    # Set logging level
+    psLib.psLogSetDestination("dest:stderr")
+
+    #
+    # Set missing keys in display
+    keys = ['all', 'pause']            # permitted keywords in display
+    subkeys = ['convolved', 'input', 'kernel', 'mask', 'reference', 'stamps', 'subtracted']
+
+    if display == "help":
+        print """Usage:
+        display={'key1':1, 'key2':True, 'key3':0, ...}   (1==True, 0==False)
+Keys:
+    all		Display everything that can be displayed
+    pause	Pause after every iteration over the stamp list
+
+    convolved	Show the kernel-convolved reference image
+    input	Show the input image
+    kernel	Show the convolution kernel
+    mask	Show the image mask
+    reference	Show the reference image
+    stamps	Overlay the reference image with the centroids of stamp stars (circle bad ones in red)
+    subtracted	Show the subtracted image
+        """
+        return            
+
+    for k in (keys + subkeys):
+        if not k in display:
+            display[k] = False
+
+    # Are illegal keys present in display[]?
+    illegal = []
+    for k in display.keys():
+        if not k in keys + subkeys:
+            illegal.append(k)
+
+    if illegal:
+        raise ("Unrecognised keywords %s are present in display[]" % (illegal.__str__()))
+
+    # Deal with display values
+    if display['all']:
+        for k in subkeys:
+            display[k] = True
+
+    if display['stamps']:
+        display['input'] = True
+
+    global init_display;
+    init_display = True            # have we initialised the display?
+
+    # Set desired variables from config file
+    if not re.search("/$", fileDir):
+        fileDir += "/"
+
+    config = pois.poisConfigAlloc()      # Configuration values
+    config.inFile = fileDir + inFile
+    config.refFile = fileDir + refFile
+    try:
+        config.outFile = fileDir + outFile
+    except:
+        config.outFile = outFile
+
+    # Read inputs
+    imageRegion = psLib.psRegionAlloc(0, 0, 0, 0)
+
+    fd = psLib.psFitsAlloc(config.refFile)
+    refImage = psLib.psFitsReadImage(None, fd, imageRegion, 0)
+    del fd
+    psTrace("pois", 3, ("Read %s: %dx%d\n" % (config.refFile, refImage.numCols, refImage.numRows)))
+
+    fd = psLib.psFitsAlloc(config.inFile)
+    inImage = psLib.psFitsReadImage(None, fd, imageRegion, 0)
+    del fd
+    psTrace("pois", 3, ("Read %s: %dx%d\n" % (config.inFile, inImage.numCols, inImage.numRows)))
+    
+    del imageRegion
+
+    if section:
+        inImage = psLib.psImageSubsection(inImage, section)
+        refImage = psLib.psImageSubsection(refImage, section)
+
+    if display['reference']:
+        displayImage(refImage, 1, "Reference Image", "red")
+    if display['input']:
+        displayImage(inImage, 2, "Input Image", "red")
+
+    if (refImage.numCols != inImage.numCols) or (refImage.numRows != inImage.numRows):
+	raise IOError, \
+              ("Reference and input images must have the same dimensions: %dx%d vs %dx%d\n" %
+               (refImage.numCols, refImage.numRows, inImage.numCols, inImage.numRows))
+
+    config.xImage = refImage.numCols
+    config.yImage = refImage.numRows
+
+    psTrace("pois.time", 1, ("Inputs read at %f sec\n" % (getTime() - startTime)))
+
+    # Add a background offset to get the image all above zero
+    # An image that goes negative (e.g., a background-subtracted image) produces
+    # horrible results --- the matrix goes NaN because the noise is determined
+    # as the square root of the value.
+    if config.background != 0.0:
+            psBinaryOp(refImage, refImage, "+", psScalarAlloc(config.background, PS_TYPE_F32))
+            psBinaryOp(inImage, inImage, "+", psScalarAlloc(config.background, PS_TYPE_F32))
+
+    # Generate the kernel parameters
+    kernelBasisFunctions = pois.poisKernelBasisFunctions(config)
+
+    # Generate a mask
+    mask = pois.poisMakeMask(None, refImage, inImage, config)
+
+    stamps = None                       # Array of stamps
+    stampMask = None                    # Mask for stamps
+    solution = None                     # Solution vector
+
+    psLib.psMemCheckCorruption(1)
+
+    # Iterate for a good solution
+    for iterNum in range(0, config.numIter):
+	psTrace("pois", 1, ("Iteration %d...\n" % (iterNum)))
+
+	# Find stamps
+        stamps = pois.poisFindStamps(stamps, refImage, mask, config);
+        
+	numStamps = 0;
+        for s in range(0, stamps.n):
+	    stamp = pois.poisStamp_Cast(stamps.get_data(s)) # Stamp of interest
+            if stamp:
+                if stamp.status == pois.POIS_STAMP_BAD:
+                    ctype = "red"; dotType = "x"
+                else:
+                    ctype = "green"; dotType = "+"
+                    numStamps += 1
+                    
+                if display['stamps']:
+                    ds9.dot(dotType, stamp.x, stamp.y, size = 5, ctype = ctype, frame = 1)
+
+	psTrace("pois.time", 1, ("%d stamps found at %f sec\n" % (numStamps, getTime() - startTime)))
+        if numStamps == 0:
+            raise IOError, "No stamps found.  Check input parameters"
+
+	# Calculate equation
+	pois.poisCalculateEquation(stamps, refImage, inImage, kernelBasisFunctions, config)
+	psTrace("pois.time", 1, ("Equation calculated at %f sec\n" % (getTime() - startTime)))
+
+	# Solve the equation
+	solution = pois.poisSolveEquation(solution, stamps, config)
+	psTrace("pois.time", 1, ("Matrix equation solved at %f sec\n" % (getTime() - startTime)))
+
+        if testing:
+            # Print solution
+            print "Solution:"
+            for i in range(0, solution.n - 1):
+                kernel = kernelBasisFunctions.get_data(i) # The i-th kernel basis function
+                kernel = pois.poisKernelBasis_Cast(kernel)
+                print "%d: (%d,%d) x^%d y^%d - %f" % \
+                      (i, kernel.u, kernel.v, kernel.xOrder, kernel.yOrder, solution.get_data_F64()[i])
+
+
+        if testing or display['kernel']:
+            kernelImage = pois.poisExtractKernel(solution, kernelBasisFunctions, 0.0, 0.0, config)
+
+            if display['kernel']:
+                displayImage(kernelImage, 5, "Kernel")
+
+            if testing:
+                # Save the kernel postage stamp
+                kernelName = "kernel%d.fits" % iterNum
+                kernelFile = psLib.psFitsAlloc(kernelName)
+                psLib.psFitsWriteImage(kernelFile, None, kernelImage, 0, None)
+                psTrace("pois", 2, ("Kernel written to %s\n" %kernelName))
+
+	# Calculate deviations for the stamps
+	deviations = pois.poisCalculateDeviations(None, stamps, refImage, inImage, mask,
+						       kernelBasisFunctions, solution, config)
+        # Have we converged?
+        rejected = pois.poisRejectStamps(stamps, mask, deviations, config)
+
+        if rejected == None:
+            break
+        
+        if display['stamps']:
+            iter = psLib.psListIteratorAlloc(rejected, psLib.PS_LIST_HEAD, False)
+            while True:
+                stamp = pois.poisStamp_Cast(psLib.psListGetAndIncrement(iter))
+                if not stamp:
+                    break
+                ds9.dot("o", stamp.x, stamp.y, size = 5, ctype = "red", frame = 1)
+
+        if display['pause']:
+            if raw_input("Iteration %d completed: continue? (q to abort) " % iterNum) == "q":
+                raise "XXX"
+
+    # If there was rejection on the last round, re-solve the equation using only the good stamps
+    if iterNum == config.numIter:
+        psTrace("pois", 2, ("Failed to converge in %d iterations\n" % (config.numIter)))
+	solution = pois.poisSolveEquation(solution, stamps, config);
+
+    # Clear the mask for bad stamp areas
+    if display['mask']:
+        displayImage(mask, 0, "Stamps mask")
+
+    psLib.psBinaryOp(mask, mask, "&", psLib.psScalarAlloc(~pois.POIS_MASK_STAMP, psLib.PS_TYPE_U8))
+
+    # Convolve the input image
+    convImage = pois.poisConvolveImage(refImage, mask, solution, kernelBasisFunctions, config)
+    psTrace("pois.time", 1, ("Convolution completed at %f sec\n" % (getTime() - startTime)))
+
+    if testing and config.outFile:
+        # Save the convolved image
+        convName = "%s.conv" % config.outFile
+        convFile = psLib.psFitsAlloc(convName)
+        psLib.psFitsWriteImage(convFile, None, convImage, 0, None)
+        psTrace("pois", 2, ("Convolved image written to %s\n" % (convName)))
+
+    if display['convolved']:
+        displayImage(convImage, 4, "Convolved image", "red")
+
+    # Do the subtraction
+    subImage = psLib.psImageAlloc(config.xImage, config.yImage, psLib.PS_TYPE_F32)
+    psLib.psBinaryOp(subImage, inImage, "-", convImage)
+    psTrace("pois.time", 1, ("Subtraction completed at %f sec\n" % (getTime() - startTime)))
+
+    if testing:
+        stats = psLib.psStatsAlloc(psLib.PS_STAT_SAMPLE_MEAN | psLib.PS_STAT_SAMPLE_STDEV)
+	psLib.psImageStats(stats, subImage, mask, 0)
+	print "Subtracted image statistics: %f %f\n" % (stats.sampleMean, stats.sampleStdev)
+	#psFree(stats);
+
+    # Mask out the edges
+    X0 = config.xKernel; X1 = mask.numCols - config.xKernel # non-masked area is (X0,Y0) -- (X1-1, Y1-1)
+    Y0 = config.yKernel; Y1 = mask.numRows - config.yKernel
+
+    tmp = psLib.psImageSubset(mask, 0, 0, mask.numCols, Y0)
+    psLib.psBinaryOp(tmp, tmp, "|", psLib.psScalarAlloc(pois.POIS_MASK_BAD, psLib.PS_TYPE_U8))
+
+    tmp = psLib.psImageSubset(mask, 0, Y1, mask.numCols, mask.numRows)
+    psLib.psBinaryOp(tmp, tmp, "|", psLib.psScalarAlloc(pois.POIS_MASK_BAD, psLib.PS_TYPE_U8))
+
+    tmp = psLib.psImageSubset(mask, 0, Y0, X0, Y1)
+    psLib.psBinaryOp(tmp, tmp, "|", psLib.psScalarAlloc(pois.POIS_MASK_BAD, psLib.PS_TYPE_U8))
+
+    tmp = psLib.psImageSubset(mask, X1, Y0, mask.numCols, Y1)
+    psLib.psBinaryOp(tmp, tmp, "|", psLib.psScalarAlloc(pois.POIS_MASK_BAD, psLib.PS_TYPE_U8))
+    del tmp
+
+    pois.poisImageSetValInMask(subImage, subImage, mask, 0, pois.POIS_MASK_BAD)
+
+    if False:
+        for y in range(config.yKernel, subImage.numRows - config.yKernel):
+            for x in range(config.xKernel, subImage.numCols - config.xKernel):
+                if mask.data.U8[y][x]:
+                    psLib.subImage.data.F32[y][x] = 0.0
+
+
+    if display['subtracted']:
+        displayImage(subImage, 3, "Subtracted image", "red")
+
+    if config.outFile:
+        subFile = psLib.psFitsAlloc(config.outFile)
+        psLib.psFitsWriteImage(subFile, None, subImage, 0, None)
+        psTrace("pois", 2, ("Subtracted image written to %s\n" % (config.outFile)))
+
+    psTrace("pois.time", 1, ("I/O completed at %f sec\n" % (getTime() - startTime)))
+
+if not run.__doc__:
+    run.__doc__ = _run.__doc__
Index: /tags/rel-0_0_2/pois/python/ps.py
===================================================================
--- /tags/rel-0_0_2/pois/python/ps.py	(revision 7503)
+++ /tags/rel-0_0_2/pois/python/ps.py	(revision 7503)
@@ -0,0 +1,151 @@
+import psLibSwig as psLib
+import sys
+
+def logcntl(what = "NM"):
+    """Control logging; disable if what == \"\""""
+    
+    if what == "":
+        psLib.psLogSetDestination("none")   # turn off logging
+    else:
+        psLib.psLogSetDestination("dest:stderr")
+        psLib.psLogSetFormat(what)
+
+logcntl("NM")
+
+class PsError(StandardError):
+    """An error signalled via a call to psError"""
+
+def raisePsError(clear = True):
+    """Raise the most recently signalled psError (the psError stack is cleared if clear is True)"""
+    
+    err = psLib.psErrorLast()
+    if err.code != 0:
+        if clear:                       # clear the error?
+            psLib.psErrorClear()
+        raise PsError, "psError was called without an error return: (%s) %s" % (err.name, err.msg)
+
+def errStack():
+    """Print the current error stack"""
+    
+    i = 0;
+    while 1:
+        err = psLib.psErrorGetForSwig(i)
+        if err == None :
+            return
+        
+        errString = psLib.psErrorCodeString(err.code)
+        if not errString:
+            errString = "(unknown code: %d)" % err.code
+        print "%s\t%-40s %s" %(err.name, errString + ':', err.msg)
+        i += 1
+
+def printMD(meta, match=None, file=sys.stdout, hdr=""):
+    """Print a set of psMetadata"""
+
+    for i in range(0,meta.list.size):
+        md = psLib.psMetadataGet(meta, i)
+
+        if md.type == psLib.PS_META_STR:
+            fmt = "%s"
+        elif md.type == psLib.PS_META_S32:
+            fmt = "%d"
+        elif md.type == psLib.PS_META_F32 or md.type == psLib.PS_META_F64:
+            fmt = "%g"
+        psLib.psMetadataItemPrint(file, md.name + " " + fmt + "\n", md)
+
+def memCheckLeaks(fd = None, id0 = 0, getList = 0, unique = 0, addresses = 0, persistent = False):
+    """Check for memory leaks.
+
+    If file (an fd or a filename) is provided, write the list of
+    leaked pointers to it.  If id0 is specified, only check for leaked
+    memory allocated after that memory id. 
+
+    If getList is true, return a list of tuples for each leaked block:
+        (isMagic0, id, file, lineNo, refCounter, isMagic)
+
+    If unique is true, print/return a list of places where memory is leaking, along with
+    the number of pointers leaked at each point.  If addresses is true, include the addresses
+    of the pointers
+    """
+
+    if (unique and addresses):
+        raise "Please choose unique _or_ addresses"
+
+    if isinstance(fd, str):
+        fd = file(fd, "w")
+
+    if addresses or getList or unique:
+        arr = []
+    else:
+        arr = None
+
+    if addresses or unique:
+        nleak = psLib.psMemCheckLeaks(id0, arr, None, persistent)
+    else:
+        nleak = psLib.psMemCheckLeaks(id0, arr, fd, persistent)
+
+    if fd == None and not (addresses or unique):
+        if nleak == 0:
+            print "No leaks"
+        else:
+            print "%d leaked pointers" % (nleak)
+
+    if addresses or unique:
+        un = {}
+        for el in arr:
+            if addresses:
+                key = "%s:%d:%d" % (el[2], el[3], el[6])
+                un[key] = el[1]
+            else:
+                key = "%s:%d:" % (el[2], el[3])
+
+                if un.get(key):
+                    un[key] += 1
+                else:
+                    un[key] = 1
+
+        keys = un.keys()
+
+        if unique:
+            def cmpKeys(a,b):               # sort keys by file then linenumber
+                a=a.split(':')
+                b=b.split(':')
+                c= cmp(a[0],b[0])
+                if c:
+                    return c
+                else:
+                    return int(a[1]) - int(b[1])
+        else:
+            def cmpKeys(a,b):               # sort keys by ID
+                return un[a] - un[b]
+
+        keys.sort(cmpKeys)
+
+        arr = []
+        for el in keys:
+            arr.append((el, un[el]))
+
+        if fd == None and not getList:
+            fd = sys.stdout
+
+        if fd != None:
+            if len(arr) == 0:
+                fd.write("No leaks\n")
+            else:
+                if unique:
+                    fd.write("%-20s Nptr\n" % "file:line")
+                    for el in arr:
+                        fd.write("%-20s %d\n" % el)
+                else:
+                    fd.write("%-15s %4s   %-10s   ID\n" % ("file", "line", "ADDR"))
+                    for el in arr:
+                        el = el[0].split(":") + [el[1]]
+                        fd.write("%-15s %4s   0x%08x   %d\n" % (el[0], el[1], int(el[2]), el[3]))
+
+    try:
+        if fd != sys.stderr and fd != sys.stdout:
+            fd.close();
+    except: pass
+
+    if getList:
+        return arr
Index: /tags/rel-0_0_2/pois/src/.cvsignore
===================================================================
--- /tags/rel-0_0_2/pois/src/.cvsignore	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/.cvsignore	(revision 7503)
@@ -0,0 +1,5 @@
+Makefile Makefile.in
+.deps .libs
+errorCodes.c poisErrorCodes.h
+*.la *.lo
+pois
Index: /tags/rel-0_0_2/pois/src/Makefile.am
===================================================================
--- /tags/rel-0_0_2/pois/src/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/Makefile.am	(revision 7503)
@@ -0,0 +1,58 @@
+AM_CFLAGS = $(PSLIB_CFLAGS) $(pois_CFLAGS)
+AM_LDFLAGS = $(PSLIB_LIBS)
+
+pois_CPPFLAGS = $(PSLIB_CFLAGS) $(pois_CFLAGS)
+pois_LDFLAGS = $(PSLIB_LIBS)
+
+noinst_HEADERS = pois.h poisErrorCodes.h
+bin_PROGRAMS = pois
+
+BUILT_SOURCES = \
+	poisErrorCodes.h errorCodes.c
+
+pois_SOURCES = \
+	pois.c \
+	poisAddPenalty.c \
+	poisCalculateDeviations.c \
+	poisCalculateEquation.c \
+	poisCheckKernel.c \
+	poisConvolveImage.c \
+	poisExtractKernel.c \
+	poisFindStamps.c \
+	poisKernelBasisFunctions.c \
+	poisMakeMask.c \
+	poisReadStamps.c \
+	poisRejectStamps.c \
+	poisParseConfig.c \
+	poisSolveEquation.c \
+	poisStamp.c \
+	errorCodes.c
+###	poisMaskOps.c
+
+#
+# Error codes
+#
+poisErrorCodes.h : errorCodes.dat errorCodes-skl.h
+	$(PERL) parseErrorCodes.pl --data=errorCodes.dat --outfile=poisErrorCodes.h \
+			errorCodes-skl.h
+errorCodes.c : errorCodes.dat errorCodes-skl.c poisErrorCodes.h
+	$(PERL) parseErrorCodes.pl --data=errorCodes.dat --outfile=errorCodes.c \
+			errorCodes-skl.c
+
+EXTRA_DIST = \
+	parseErrorCodes.pl \
+	errorCodes.dat \
+	errorCodes-skl.h \
+	errorCodes-skl.c \
+	poisErrorCodes.h
+
+#
+# test program
+#
+test: pois test1.fits test2.fits
+	-$(RM) testout.fits stamp*.fits kernel*.fits
+	./pois -v test1.fits test2.fits testout.fits
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /tags/rel-0_0_2/pois/src/errorCodes-skl.c
===================================================================
--- /tags/rel-0_0_2/pois/src/errorCodes-skl.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/errorCodes-skl.c	(revision 7503)
@@ -0,0 +1,32 @@
+/*
+ * The lines between
+ *   //~Start TEMPLATE
+ * and
+ *   //~End
+ * will be machine generated from errorCodes.dat
+ */
+#include "pslib.h"
+#include "poisErrorCodes.h"
+
+void poisErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { POIS_ERR_BASE, "First value we use; lower values belong to psLib" },
+       //~Start       {POIS_ERR_$1, "$2"},
+       {POIS_ERR_UNKNOWN, "Unknown POIS error code"},
+       {POIS_ERR_NOT_IMPLEMENTED, "Desired feature is not yet implemented"},
+       {POIS_ERR_FITS, "Problem in FITS I/O"},
+       {POIS_ERR_FITS_WCS, "Error interpreting FITS WCS information"},
+       //~End
+    };
+    static int nerror = POIS_ERR_NERROR - POIS_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+	p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /tags/rel-0_0_2/pois/src/errorCodes-skl.h
===================================================================
--- /tags/rel-0_0_2/pois/src/errorCodes-skl.h	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/errorCodes-skl.h	(revision 7503)
@@ -0,0 +1,23 @@
+#if !defined(POIS_ERROR_CODES_H)
+#define POIS_ERROR_CODES_H
+/*
+ * The lines between
+ *   //~Start TEMPLATE
+ * and
+ *   //~End
+ * will be machine generated from errorCodes.dat
+ */
+typedef enum {
+    POIS_ERR_BASE = 512,
+    //~Start    POIS_ERR_$1,
+    POIS_ERR_UNKNOWN,
+    POIS_ERR_NOT_IMPLEMENTED,
+    POIS_ERR_FITS,
+    POIS_ERR_FITS_WCS,
+    //~End
+    POIS_ERR_NERROR
+} poisErrorCode;
+
+void poisErrorRegister(void);
+
+#endif
Index: /tags/rel-0_0_2/pois/src/errorCodes.dat
===================================================================
--- /tags/rel-0_0_2/pois/src/errorCodes.dat	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/errorCodes.dat	(revision 7503)
@@ -0,0 +1,11 @@
+#
+# This file is used to generate poisErrorClasses.h
+#
+BASE = 512		First value we use; lower values belong to psLib
+UNKNOWN			Unknown POIS error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+FITS			Problem in FITS I/O
+FITS_WCS		Error interpreting FITS WCS information
+SWIG			Problem in SWIG bindings
+UNSUPPORTED_TYPE	The datatype is not supported for this operation
+
Index: /tags/rel-0_0_2/pois/src/parseErrorCodes.pl
===================================================================
--- /tags/rel-0_0_2/pois/src/parseErrorCodes.pl	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/parseErrorCodes.pl	(revision 7503)
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+
+# Provides functions for handling long command line options
+use Getopt::Long;
+
+my @ErrorCodes        = ();
+my @ErrorDescriptions = ();
+
+my $data = "psErrorCodes.dat";
+
+# Assign variables based on the presence of command line options to the script
+GetOptions(
+    "data=s"  => \$data,
+    "outfile=s"  => \$outputfile,
+    "verbose" => \$verbose,
+    "help"    => \$help
+	   )  or exit(1);
+
+if ($help) {
+    print "Usage: parseErrorCodes ", "[--data=$data] ", "[--outfile=$file] ", "[--help] ",
+      "[--verbose] [filename [filename ...]]\n\n";
+    exit(0);
+}
+
+if(defined($outputfile)) {
+   if (@ARGV > 1) {
+      die "You may only specify one input filename if you use --outfile\n";
+   }
+}
+
+print "Using data file '$data'\n" if $verbose;
+unless ( open( DATAFILE, "<", $data ) ) {
+    die "Can not open data file $data.";
+}
+
+print "Datafile:\n" if $verbose;
+while (<DATAFILE>) {
+    chop;
+    if (/^\s*\#/) {
+        print "C $_\n" if $verbose;
+    }
+    else {
+        if (/^\s*(\w+)\s+([\%\w].*)/) {
+            my $ErrorCode        = $1;
+            my $ErrorDescription = $2;
+            print "  $ErrorCode: '$ErrorDescription'\n" if $verbose;
+            push( @ErrorCodes,        $ErrorCode );
+            push( @ErrorDescriptions, $ErrorDescription );
+        } else {
+            print "I $_\n" if $verbose;
+        }
+    }
+}
+close(DATAFILE);
+
+my $found = $#ErrorCodes + 1;
+print "\nFound $found error codes.\n" if $verbose;
+
+foreach (@ARGV) {
+    my $filename = $_;
+    my @result   = ();
+
+    die "Failed to open input file '$filename'"
+      if !open( INFILE, "<", $filename );
+
+    print "\nOutput File:\n" if $verbose;
+    while (<INFILE>) {
+        chop;
+        push( @result, $_ );
+        if (/^\s*\/\/~Start(.*)$/) {
+            $line = $1;
+            for ( $n = 0 ; $n < $found ; $n++ ) {
+                $_ = $line;
+                s/\$1/$ErrorCodes[$n]/g;
+                s/\$2/$ErrorDescriptions[$n]/g;
+                s/\$n/$n/g;
+                push( @result, $_ );
+                print "$_\n" if $verbose;
+            }
+
+            $break = 0;
+            while ( ( $break == 0 ) && ( $_ = <INFILE> ) ) {
+                if (/^\s*\/\/~End/) {
+                    $break = 1;
+                }
+            }
+            chop;
+            push( @result, $_ );
+        }
+    }
+
+    close(INFILE);
+
+    if (defined($outputfile)) {
+       $ofile = $outputfile;
+    } else {
+       $ofile = $filename;
+    }
+    die "Failed to overwrite input file"
+	if !open( OUTFILE, ">", $ofile );
+
+    foreach (@result) {
+        print OUTFILE "$_\n";
+        print "$_\n" if $verbose;
+    }
+
+    close(OUTFILE);
+
+}
Index: /tags/rel-0_0_2/pois/src/pois.c
===================================================================
--- /tags/rel-0_0_2/pois/src/pois.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/pois.c	(revision 7503)
@@ -0,0 +1,336 @@
+/*
+ * POIS: Pan-STARRS (or Pricey's) Optimal Image Subtraction
+ *
+ * Attempts to fit for a general convolution kernel that will match the PSFs between two images.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include "pslib.h"
+#include "pois.h"
+
+#define MAXCHAR 80
+
+// Gets the current time.
+double getTime(void)
+{
+    struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+    return(tv.tv_sec + 1.e-6 * tv.tv_usec);
+}
+
+
+int main(int argc, char **argv)
+{
+    double startTime = getTime();
+
+    // Set trace levels
+    psTraceSetLevel(".", 0);
+
+    // Parse the command line
+    poisConfig *config = poisParseConfig(argc, argv); // Configuration values
+
+    if (config->verbose) {
+        psTraceSetLevel("pois", 10);
+        psTraceSetLevel("pois.config", 10);
+        psTraceSetLevel("pois.time", 10);
+        psTraceSetLevel("pois.solution", 0);
+        psTraceSetLevel("pois.kernelBasisFunctions", 10);
+        psTraceSetLevel("pois.calculateEquation", 10);
+        psTraceSetLevel("pois.convolveImage", 10);
+        psTraceSetLevel("pois.findStamps", 10);
+        psTraceSetLevel("pois.parseConfig", 10);
+        psTraceSetLevel("pois.makeMask", 10);
+        psTraceSetLevel("pois.extractKernel", 10);
+        psTraceSetLevel("pois.calculateDeviations", 10);
+        psTraceSetLevel("pois.checkKernel", 10);
+        if (config->stampFile) {
+            psTraceSetLevel("pois.checkStamp", 10);
+        } else {
+            psTraceSetLevel("pois.checkStamp", 0);
+        }
+        // Set logging level
+        psLogSetLevel(9);
+    }
+
+    // Read inputs
+    psMetadata *header = NULL;          // Header for output image
+
+    psRegion imageRegion = {0, 0, 0, 0};
+    psFits *reference = psFitsOpen(config->refFile, "r");
+    if (config->reverse) {
+        header = psFitsReadHeader(NULL, reference);
+    }
+    psImage *refImage = psFitsReadImage(NULL, reference, imageRegion, 0);
+    if (refImage == NULL) {
+        psErrorStackPrint(stderr, "Fatal error: unable to read %s\n", config->refFile);
+        exit(EXIT_FAILURE);
+    }
+    psTrace("pois", 3, "Read %s: %dx%d\n", config->refFile, refImage->numCols, refImage->numRows);
+    psFitsClose(reference);
+    // Convert to 32-bit floating point, in necessary
+    if (refImage->type.type != PS_TYPE_F32) {
+        psTrace("pois", 3, "Converting %s to floating point in memory....\n", config->refFile);
+        psImage *temp = psImageCopy(NULL, refImage, PS_TYPE_F32);
+        psFree(refImage);
+        refImage = temp;
+    }
+
+    psFits *input = psFitsOpen(config->inFile, "r");
+    if (! config->reverse) {
+        header = psFitsReadHeader(NULL, input);
+    }
+    psImage *inImage = psFitsReadImage(NULL, input, imageRegion, 0);
+    if (inImage == NULL) {
+        psErrorStackPrint(stderr, "Fatal error: unable to read %s\n", config->inFile);
+        exit(EXIT_FAILURE);
+    }
+    psTrace("pois", 3, "Read %s: %dx%d\n", config->inFile, inImage->numCols, inImage->numRows);
+    psFitsClose(input);
+    // Convert to 32-bit floating point, in necessary
+    if (inImage->type.type != PS_TYPE_F32) {
+        psTrace("pois", 3, "Converting %s to floating point in memory....\n", config->inFile);
+        psImage *temp = psImageCopy(NULL, inImage, PS_TYPE_F32);
+        psFree(inImage);
+        inImage = temp;
+    }
+
+    if ((refImage->numCols != inImage->numCols) || (refImage->numRows != inImage->numRows)) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Reference and input images must have the same dimensions: %dx%d vs %dx%d\n",
+                refImage->numCols, refImage->numRows, inImage->numCols, inImage->numRows);
+        exit(EXIT_FAILURE);
+    }
+    config->xImage = refImage->numCols;
+    config->yImage = refImage->numRows;
+
+    psTrace("pois.time", 1, "Inputs read at %f sec\n", getTime() - startTime);
+
+    // Add a background offset to get the image all above zero
+    // An image that goes negative (e.g., a background-subtracted image) produces
+    // horrible results --- the matrix goes NaN because the noise is determined
+    // as the square root of the value.
+    if (config->background != 0.0) {
+        (void)psBinaryOp(refImage, refImage, "+", psScalarAlloc(config->background, PS_TYPE_F32));
+        (void)psBinaryOp(inImage, inImage, "+", psScalarAlloc(config->background, PS_TYPE_F32));
+    }
+
+    // Generate the kernel parameters
+    psArray *kernelBasisFunctions = poisKernelBasisFunctions(config);
+
+    // Generate a mask
+    psImage *mask = poisMakeMask(NULL, refImage, inImage, config);
+
+#ifdef TESTING
+    // Write the mask out
+    psFits *maskFile = psFitsOpen("mask.fits", "w");
+    if (!psFitsWriteImage(maskFile, NULL, mask, 0)) {
+        psErrorStackPrint(stderr, "Unable to write mask: mask.fits\n");
+    }
+    psTrace("pois", 1, "Mask written to mask.fits\n");
+    psFitsClose(maskFile);
+#endif
+
+    psArray *stamps = NULL;             // Array of stamps
+    psVector *solution = NULL;          // Solution vector
+
+    FILE *stampsFP = NULL;              // File pointer for stamps
+    if (config->stampFile) {
+        psTrace("pois", 5, "Opening stamp file, %s\n", config->stampFile);
+        stampsFP = fopen(config->stampFile, "r");
+        if (! stampsFP) {
+            psError(PS_ERR_IO, true, "Unable to open stamps file, %s!\n", config->stampFile);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // Iterate for a good solution
+    bool badStamps = true;              // Do we have bad stamps, such that we need to continue to iterate?
+    for (int iterNum = 0; iterNum < config->numIter && badStamps; iterNum++) {
+        psTrace("pois", 1, "Iteration %d...\n", iterNum);
+
+        // Find stamps
+        if (config->stampFile) {
+            stamps = poisReadStamps(stamps, &stampsFP, refImage, mask, config);
+        } else {
+            stamps = poisFindStamps(stamps, refImage, mask, config);
+        }
+        int numStamps = 0;
+        for (int s = 0; s < stamps->n; s++) {
+            poisStamp *stamp = stamps->data[s]; // Stamp of interest
+            if (stamp->status != POIS_STAMP_BAD) {
+                numStamps++;
+            }
+        }
+        psTrace("pois.time", 1, "%d stamps found at %f sec\n", numStamps, getTime() - startTime);
+        if (numStamps == 0) {
+            fprintf(stderr, "No stamps found.  Check input parameters.\n");
+            exit(EXIT_FAILURE);
+        }
+
+        // Calculate equation
+        (void)poisCalculateEquation(stamps, refImage, inImage, kernelBasisFunctions, config);
+        psTrace("pois.time", 1, "Equation calculated at %f sec\n", getTime() - startTime);
+
+        // Solve the equation
+        solution = poisSolveEquation(solution, stamps, config);
+        psTrace("pois.time", 1, "Matrix equation solved at %f sec\n", getTime() - startTime);
+
+#ifdef TESTING
+        // Print solution
+        psTrace("pois.solution", 5, "Solution:\n");
+        for (int i = 0; i < solution->n - 1; i++) {
+            poisKernelBasis *kernel = kernelBasisFunctions->data[i]; // The i-th kernel basis function
+            psTrace("pois.solution", 7, "%d: (%d,%d) x^%d y^%d --> %f\n", i, kernel->u, kernel->v,
+                    kernel->xOrder, kernel->yOrder, solution->data.F64[i]);
+        }
+#endif
+
+#ifdef TESTING
+        // Save the kernel postage stamp
+        char kernelName[MAXCHAR];               // File name for kernel
+        snprintf(kernelName, MAXCHAR, "kernel%d.fits", iterNum);
+        psImage *kernelImage = poisExtractKernel(solution, kernelBasisFunctions, 0.0, 0.0, config);
+        psFits *kernelFile = psFitsOpen(kernelName, "w");
+        if (!psFitsWriteImage(kernelFile, NULL, kernelImage, 0)) {
+            psErrorStackPrint(stderr, "Unable to write kernel: %s\n", kernelName);
+        }
+        psTrace("pois", 1, "Kernel written to %s\n", kernelName);
+        psFitsClose(kernelFile);
+        psFree(kernelImage);
+#endif
+
+        // Calculate deviations for the stamps
+        psVector *deviations = poisCalculateDeviations(NULL, stamps, refImage, inImage, mask,
+                                                       kernelBasisFunctions, solution, config);
+        badStamps = poisRejectStamps(stamps, mask, deviations, config);
+
+        psFree(deviations);
+    } // Iterating for good solution
+
+    // If there was rejection on the last round, re-solve the equation using only the good stamps
+    if (badStamps) {
+        solution = poisSolveEquation(solution, stamps, config);
+    }
+
+    if (stampsFP) {
+        fclose(stampsFP);
+    }
+
+    // Undo the mask for bad stamp area
+    for (int j = 0; j < mask->numRows; j++) {
+        for (int i = 0; i < mask->numCols; i++) {
+            if (mask->data.U8[j][i] & POIS_MASK_STAMP) {
+                mask->data.U8[j][i] &= ~POIS_MASK_STAMP;
+            }
+        }
+    }
+
+    // Check the result
+    (void)poisCheckKernel(solution, kernelBasisFunctions, config);
+
+    // Convolve the input image
+    psImage *convImage = poisConvolveImage(refImage, mask, solution, kernelBasisFunctions, config);
+    psTrace("pois.time", 1, "Convolution completed at %f sec\n", getTime() - startTime);
+
+#ifdef TESTING
+    // Save the convolved image
+    char convName[MAXCHAR];             // Filename for convolved image
+    snprintf(convName, MAXCHAR, "%s.conv", config->outFile);
+    psFits *convFile = psFitsOpen(convName, "w");
+    if (!psFitsWriteImage(convFile, NULL, convImage, 0)) {
+        psErrorStackPrint(stderr, "Unable to write convolved image: %s\n", convName);
+    }
+    psFitsClose(convFile);
+    psTrace("pois", 1, "Convolved image written to %s\n", convName);
+#endif
+
+    // Do the subtraction
+    psImage *subImage = psImageAlloc(config->xImage, config->yImage, PS_TYPE_F32);
+    if (config->reverse) {
+        (void)psBinaryOp(subImage, convImage, "-", inImage);
+    } else {
+        (void)psBinaryOp(subImage, inImage, "-", convImage);
+    }
+    psTrace("pois.time", 1, "Subtraction completed at %f sec\n", getTime() - startTime);
+
+#ifdef TESTING
+    {
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        (void)psImageStats(stats, subImage, mask, 0);
+        psTrace("pois", 5, "Subtracted image statistics: %f %f\n", stats->sampleMean, stats->sampleStdev);
+        psFree(stats);
+    }
+#endif
+
+    // Mask out the edges
+    for (int y = config->yKernel; y < subImage->numRows - config->yKernel; y++) {
+        for (int x = config->xKernel; x < subImage->numCols - config->xKernel; x++) {
+
+            for (int x = 0; x < config->xKernel; x++) {
+                subImage->data.F32[y][x] = 0.0;
+            }
+            for (int x = subImage->numCols - config->xKernel; x < subImage->numCols; x++) {
+                subImage->data.F32[y][x] = 0.0;
+            }
+        }
+    }
+    for (int y = 0; y < config->yKernel; y++) {
+        for (int x = 0; x < subImage->numCols; x++) {
+            subImage->data.F32[y][x] = 0.0;
+        }
+    }
+    for (int y = subImage->numRows - config->yKernel; y < subImage->numRows; y++) {
+        for (int x = 0; x < subImage->numCols; x++) {
+            subImage->data.F32[y][x] = 0.0;
+        }
+    }
+
+
+    // Mask out bad pixels
+    for (int y = config->yKernel; y < subImage->numRows - config->yKernel; y++) {
+        for (int x = config->xKernel; x < subImage->numCols - config->xKernel; x++) {
+            if (mask->data.U8[y][x]) {
+                subImage->data.F32[y][x] = 0.0;
+            }
+        }
+    }
+
+    if (config->mask) {
+        char maskName[80];              // Name of mask image
+        sprintf(maskName, "%s.mask", config->outFile);
+        psFits *maskFile = psFitsOpen(maskName, "w");
+        if (!psFitsWriteImage(maskFile, NULL, mask, 0)) {
+            psErrorStackPrint(stderr, "Unable to write image: %s\n", maskName);
+        }
+        psTrace("pois", 1, "Mask image written to %s\n", maskName);
+        psFitsClose(maskFile);
+    }
+
+
+    int numNaN = psImageClipNaN(subImage, 0.0);
+    if (numNaN > 0) {
+        printf("Masked %d NAN pixels to zero.\n", numNaN);
+    }
+
+    psFits *subFile = psFitsOpen(config->outFile, "w");
+    if (!psFitsWriteImage(subFile, header, subImage, 0)) {
+        psErrorStackPrint(stderr, "Unable to write subtracted image: %s\n", config->outFile);
+    }
+    psFitsClose(subFile);
+    psTrace("pois", 1, "Subtracted image written to %s\n", config->outFile);
+    psTrace("pois.time", 1, "I/O completed at %f sec\n", getTime() - startTime);
+
+    // Clean up
+    psFree(header);
+    psFree(kernelBasisFunctions);
+    psFree(refImage);
+    psFree(inImage);
+    psFree(convImage);
+    psFree(subImage);
+    psFree(config);
+}
+
Index: /tags/rel-0_0_2/pois/src/pois.h
===================================================================
--- /tags/rel-0_0_2/pois/src/pois.h	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/pois.h	(revision 7503)
@@ -0,0 +1,234 @@
+#if !defined(POIS_H)
+#define POIS_H
+
+#include "pslib.h"
+
+#define abs(x) ((x) >= 0 ? (x) : (-(x)))
+
+/************************************************************************************************************/
+
+/* Configuration options */
+typedef struct {
+    int verbose;			// Verbosity level
+    char *refFile;			// Reference image
+    char *inFile;			// Input image filename
+    char *outFile;			// Output convolved image filename
+    int xKernel, yKernel;		// Half the size of the kernel in x and y
+    int xImage, yImage;			// Size of the images in x and y
+    float threshold;			// Threshold for pixel to include in calculation
+    int footprint;			// Footprint size for stamp
+    int nsx, nsy;			// Number of stamps in x and y
+    float refSat, inSat;		// Saturation values for reference and input images
+    float refBad, inBad;		// Bad values for reference and input images
+    float background;			// Value to add to the background in images to get above zero
+    int spatialOrder;			// Order of spatial variations in the kernel
+    int numIter;			// Number of rejection iterations
+    float sigmaRej;			// Limit (in std dev) for rejection
+    float penalty;			// Penalty value
+    char *stampFile;			// Filename for file with stamps
+    bool reverse;			// Reverse the subtraction?
+    bool mask;				// Output the mask?
+} poisConfig;
+
+/************************************************************************************************************/
+
+// A kernel basis function
+typedef struct {
+    int u, v;				// Offset
+    int xOrder, yOrder;			// x^xOrder * y^yOrder
+} poisKernelBasis;
+
+/************************************************************************************************************/
+
+typedef enum {
+    POIS_STAMP_USED,			// Use this stamp
+    POIS_STAMP_RESET,			// This stamp has been rejected --- reset
+    POIS_STAMP_RECALC,			// Having been reset, this stamp needs to be recalculated
+    POIS_STAMP_BAD			// No stamp in this region
+} poisStampStatus;
+
+// A stamp
+typedef struct {
+    int x, y;				// Position
+    psImage *matrix;			// Associated matrix
+    psVector *vector;			// Associated vector
+    poisStampStatus status;		// Status of stamp
+} poisStamp;
+
+/************************************************************************************************************/
+
+// poisStamp.c
+
+// Construct a stamp
+poisStamp *poisStampAlloc(void);
+
+// Destroy a stamp
+void poisStampFree(poisStamp *stamp);
+
+// Return true if the stamp is OK
+bool poisCheckStamp(const psImage *image, // Image to check for threshold
+		    const psImage *mask, // Mask to check for bad pixels
+		    int x, int y,	// Pixel coordinates
+		    const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+typedef enum {
+    POIS_MASK_OK       = 0x00,		// Fine
+    POIS_MASK_BAD      = 0x01,		// Bad pixels
+    POIS_MASK_NEAR_BAD = 0x02,		// Near bad pixels
+    POIS_MASK_STAMP    = 0x04		// Masking a stamp
+} poisMask;
+
+/************************************************************************************************************/
+
+// poisParseConfig.c
+
+// Print usage information
+void help(void);
+
+// Parse the command line arguments
+poisConfig *poisParseConfig(int argc,	// Number of command-line arguments
+			    char **argv	// Command-line arguments
+    );
+
+/************************************************************************************************************/
+
+// poisKernelBasisFunctions.c
+
+// Generate the kernel basis functions for ease of reference in the construction
+psArray *poisKernelBasisFunctions(const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisMakeMask.c
+
+// Generate a mask of bad pixels in the reference and input images --- don't use these pixels for stamps!
+psImage *poisMakeMask(psImage *mask,	// Mask to update, or NULL
+		      const psImage *refImage, // Reference image
+		      const psImage *inImage, // Input image
+		      const poisConfig *config // Configuration
+		      );
+
+/************************************************************************************************************/
+
+// poisFindStamps.c
+
+// Find stamps
+psArray *poisFindStamps(psArray *stamps, // Existing list of stamps, or NULL
+			const psImage *image, // Image for which to find stamps
+			const psImage *mask, // Mask image
+			const poisConfig *config	// Configuration values
+    );
+
+/************************************************************************************************************/
+
+// poisExtractKernel.c
+
+// Create a kernel image from the solution vector
+psImage *poisExtractKernel(const psVector *solution, // Vector containing the kernel elements
+			   const psArray *kernelParams, // The kernel parameters
+			   float x,	// The relative (-1 to 1) x position for which to get the kernel
+			   float y,	// The relative (-1 to 1) y position for which to get the kernel
+			   const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisCalculateEquation.c
+
+// Calculate the matrix and vector for the matrix equation
+int poisCalculateEquation(psArray *stamps, // The stamps
+			  const psImage *refImage, // The reference image
+			  const psImage *inImage, // The input image
+			  const psArray *kernelParams, // Kernel parameters
+			  const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisConvolveImage.c
+
+// Convolve an image by the kernel
+psImage *poisConvolveImage(const psImage *in, // Input image, to be convolved
+			   const psImage *mask,	// Mask image
+			   const psVector *solution, // The solution vector, containing the coefficients
+			   const psArray *kernelParams, // The kernel parameters
+			   const poisConfig *config // The configuration
+    );
+
+/************************************************************************************************************/
+
+// poisCalculateDeviations.c
+
+// Calculate deviations from the best fit for the stamps
+psVector *poisCalculateDeviations(psVector *deviations,	// Output array of deviations, or NULL
+				  psArray *stamps, // Array of stamps
+				  psImage *refImage, // Reference image
+				  psImage *inImage, // Input image
+				  psImage *mask, // Mask image
+				  psArray *kernelParams, // Array of kernel parameters
+				  psVector *solution, // Solution vector
+				  poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisRejectStamps.c
+
+// Reject stamps, based on the deviations
+bool poisRejectStamps(psArray *stamps,	// Array of stamps
+		      psImage *mask,	// Mask image
+		      const psVector *deviations, // Vector of deviations for the stamps
+		      const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisSolveEquation.c
+
+// Solve the matrix equation
+psVector *poisSolveEquation(psVector *solution,	// Solution vector, or NULL
+			    const psArray *stamps, // Array of stamps
+			    const poisConfig *config
+    );
+
+/************************************************************************************************************/
+
+// poisAddPenalty.c
+
+// Generate penalty matrix and vector
+void poisAddPenalty(psImage *matrix,	// The matrix
+		    psVector *vector,	// The vector
+		    float penalty,	// Penalty value
+		    const psArray *kernels, // Kernel basis functions
+		    const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisReadStamps.c
+
+// Read stamps from a file
+psArray *poisReadStamps(psArray *stamps,// Stamps
+			FILE **stampFP, // File pointer for stamps file
+			const psImage *image, // Image for which to find stamps
+			const psImage *mask, // Mask image
+			const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+// poisCheckKernel.c
+
+// Check kernel for power at large radii
+bool poisCheckKernel(const psVector *solution,// Kernel solution
+		     const psArray *kernels, // Kernel basis functions
+		     const poisConfig *config // Configuration
+    );
+
+/************************************************************************************************************/
+
+#endif
Index: /tags/rel-0_0_2/pois/src/poisAddPenalty.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisAddPenalty.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisAddPenalty.c	(revision 7503)
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+void poisAddPenalty(psImage *matrix,	// The matrix
+		    psVector *vector,	// The vector
+		    float penalty,	// Penalty value
+		    const psArray *kernels, // Kernel basis functions
+		    const poisConfig *config // Configuration
+    )
+{
+    // Check inputs
+    assert(matrix);
+    assert(vector);
+    assert(kernels);
+    assert(config);
+    assert(matrix->numRows == matrix->numCols);
+    assert(vector->n == matrix->numRows);
+    assert(vector->n == kernels->n + 1);// Additional space for constant background
+    assert(matrix->type.type == PS_TYPE_F64);
+    assert(vector->type.type == PS_TYPE_F64);
+
+    int numKernels = kernels->n;
+
+    for (int j = 0; j < numKernels; j++) {
+	poisKernelBasis *kernel = kernels->data[j];
+	if (kernel->xOrder == 0 && kernel->yOrder == 0) {
+	    for (int k = j + 1; k < numKernels; k++) {
+		poisKernelBasis *compare = kernels->data[k];
+		if (compare->xOrder == 0 && compare->yOrder == 0 &&
+		    (compare->u == kernel->u + 1 || compare->u == kernel->u - 1 ||
+		     compare->v == kernel->v + 1 || compare->v == kernel->v - 1)) {
+		    matrix->data.F64[j][k] -= penalty;
+		    matrix->data.F64[k][j] -= penalty;
+		}
+	    }
+	}
+	    
+	if (abs(kernel->u) == config->xKernel) {
+	    if (abs(kernel->v) == config->yKernel) {
+		vector->data.F64[j] -= 3.0 * penalty;
+	    } else {
+		vector->data.F64[j] -= 5.0 * penalty;
+	    }
+	} else {
+	    if (abs(kernel->v) == config->yKernel) {
+		vector->data.F64[j] -= 5.0 * penalty;
+	    } else {
+		vector->data.F64[j] -= 8.0 * penalty;
+	    }
+	}
+	
+    }
+
+
+}
Index: /tags/rel-0_0_2/pois/src/poisCalculateDeviations.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisCalculateDeviations.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisCalculateDeviations.c	(revision 7503)
@@ -0,0 +1,99 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+#define MAXCHAR 80
+
+psVector *poisCalculateDeviations(psVector *deviations, // Output array of deviations, or NULL
+                                  psArray *stamps, // Array of stamps
+                                  psImage *refImage, // Reference image
+                                  psImage *inImage, // Input image
+                                  psImage *mask, // Mask image
+                                  psArray *kernelParams, // Array of kernel parameters
+                                  psVector *solution, // Solution vector
+                                  poisConfig *config // Configuration
+    )
+{
+    // Check inputs
+    assert(!deviations || deviations->type.type == PS_TYPE_F32);
+    assert(!deviations || deviations->n == stamps->n);
+    assert(stamps);
+    assert(refImage->type.type == PS_TYPE_F32);
+    assert(inImage->type.type == PS_TYPE_F32);
+    assert(refImage->numRows == inImage->numRows);
+    assert(refImage->numCols == inImage->numCols);
+    assert(mask->type.type == PS_TYPE_U8);
+    assert(refImage->numRows == mask->numRows);
+    assert(refImage->numCols == mask->numCols);
+    assert(solution->n == kernelParams->n + 1);
+    assert(solution->type.type == PS_TYPE_F64);
+    assert(config);
+
+    if (!deviations) {
+        deviations = psVectorAlloc(stamps->n, PS_TYPE_F32);
+    }
+
+    int footprint = config->footprint;  // Size of stamp footprint
+    int xSize = footprint + config->xKernel; // (Half) size of subimage in x
+    int ySize = footprint + config->yKernel; // (Half) size of subimage in y
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN); // Statistics
+
+    psImage *subStamp = psImageAlloc(2 * xSize, 2 * ySize, PS_TYPE_F32); // Subtraction of stamp
+    for (int s = 0; s < stamps->n; s++) {
+        poisStamp *stamp = stamps->data[s]; // The coordinates of the stamp of interest
+        int x = stamp->x;               // Stamp x coord
+        int y = stamp->y;               // Stamp y coord
+        if (stamp->status == POIS_STAMP_USED) {
+            psRegion stampRegion = {x - xSize, x + xSize, y - ySize, y + ySize};
+            psImage *refStamp = psImageSubset(refImage, stampRegion);
+            psImage *inStamp = psImageSubset(inImage, stampRegion);
+            psImage *maskStamp = psImageSubset(mask, stampRegion);
+            psImage *convRefStamp = poisConvolveImage(refStamp, maskStamp, solution, kernelParams, config);
+            // Calculate chi^2
+            (void)psBinaryOp(subStamp, inStamp, "-", convRefStamp);
+            (void)psBinaryOp(subStamp, subStamp, "*", subStamp);
+            (void)psBinaryOp(subStamp, subStamp, "/", inStamp);
+            psRegion stampTrim = { config->xKernel, config->xKernel + 2 * footprint, config->yKernel,
+                                   config->yKernel + 2 * footprint };
+            psImage *subStampTrim = psImageSubset(subStamp, stampTrim);
+            psImage *maskStampTrim = psImageSubset(maskStamp, stampTrim);
+            // Copy image to workaround bug 305
+            psImage *tempImage = psImageCopy(NULL, subStampTrim, PS_TYPE_F32);
+            psImage *tempMask = psImageCopy(NULL, maskStampTrim, PS_TYPE_U8);
+
+            (void)psImageStats(stats, tempImage, tempMask, POIS_MASK_BAD | POIS_MASK_NEAR_BAD);
+
+            psFree(tempImage);
+            psFree(tempMask);
+
+            deviations->data.F32[s] = sqrtf(stats->sampleMean / 2.0);
+            psTrace("pois.calculateDeviations", 5, "Deviation of stamp %d (%d,%d) is %f\n", s, x, y,
+                    deviations->data.F32[s]);
+
+#ifdef TESTING
+            char stampName[MAXCHAR];            // File name for stamp
+            snprintf(stampName, MAXCHAR, "stamp%d.fits", s);
+            psFits *stampFile = psFitsOpen(stampName, "w");
+            if (!psFitsWriteImage(stampFile, NULL, subStampTrim, 0)) {
+                psErrorStackPrint(stderr, "Unable to write stamp: %s\n", stampName);
+            }
+            psFitsClose(stampFile);
+#endif
+
+#if 0
+            psFree(convRefStamp);
+            psFree(maskStampTrim);
+            psFree(subStampTrim);
+            psFree(maskStamp);
+            psFree(refStamp);
+            psFree(inStamp);
+#endif
+        }
+    }
+
+    psFree(stats);
+    psFree(subStamp);
+
+    return deviations;
+}
Index: /tags/rel-0_0_2/pois/src/poisCalculateEquation.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisCalculateEquation.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisCalculateEquation.c	(revision 7503)
@@ -0,0 +1,177 @@
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+/* Calculate the matrix and vector for the matrix equation */
+int poisCalculateEquation(psArray *stamps, // The stamps
+                          const psImage *refImage, // The reference image
+                          const psImage *inImage, // The input image
+                          const psArray *kernelParams, // Kernel parameters
+                          const poisConfig *config // Configuration
+    )
+{
+    // Check inputs
+    assert(stamps);
+    assert(refImage);
+    assert(inImage);
+    assert(kernelParams);
+    assert(config);
+
+    // In addition to the kernel parameters, need one extra parameter to fit the (assumed constant) difference
+    // in the background levels.
+    int numSolveParams = kernelParams->n + 1;
+    int bgIndex = kernelParams->n;      // Index in matrix for the background (it's the last one, after the
+                                        // kernel parameters)
+    float **reference = refImage->data.F32; // The reference pixels
+    float **input = inImage->data.F32;  // The input pixels
+    int nPix = 0;                       // Number of pixels used in calculation (for interest)
+    float nx = 0.5 * (float)refImage->numCols; // Half-size of input image in x
+    float ny = 0.5 * (float)refImage->numRows; // Half-size of input image in y
+    int numKernelParams = kernelParams->n; // Number of kernel parameters
+
+    psPolynomial2D *poly = psPolynomial2DAlloc(config->spatialOrder + 1, config->spatialOrder + 1,
+                                               PS_POLYNOMIAL_ORD); // Polynomial for evaluation
+    for (int j = 0; j < config->spatialOrder + 1; j++) {
+        for (int i = 0; i < config->spatialOrder + 1; i++) {
+            poly->coeff[j][i] = 1.0;
+            poly->mask[j][i] = 1;       // Mask all coefficients; unmask to evaluate
+        }
+    }
+
+    psImage *polyValues = psImageAlloc(config->spatialOrder + 1, config->spatialOrder + 1,
+                                       PS_TYPE_F64); // Evaluations for each of the polynomial orders
+
+    /* Iterate over the stamps */
+    for (int s = 0; s < stamps->n; s++) {
+        poisStamp *stamp = stamps->data[s]; // The stamp
+
+        if (stamp->status == POIS_STAMP_RECALC) {
+            psTrace("pois.calculateEquation", 5, "Calculating for stamp %d: %d,%d\n", s, stamp->x, stamp->y);
+
+            // Initialise the matrix and vector if required
+            if (! stamp->matrix) {
+                psTrace("pois.calculateEquation", 9, "Allocating matrix: %dx%d\n", numSolveParams,
+                        numSolveParams);
+                stamp->matrix = psImageAlloc(numSolveParams, numSolveParams, PS_TYPE_F64);
+            }
+            if (! stamp->vector) {
+                psTrace("pois.calculateEquation", 3, "Allocating vector: %d\n", numSolveParams);
+                stamp->vector = psVectorAlloc(numSolveParams, PS_TYPE_F64);
+            }
+
+            // Dereference, for convenience
+            psImage *matrix = stamp->matrix;
+            psVector *vector = stamp->vector;
+
+            // Will assert on the type, since can't do much about it if it's wrong.
+            assert(matrix->type.type == PS_TYPE_F64);
+            assert(vector->type.type == PS_TYPE_F64);
+
+            // Initialise the matrix and vector
+            for (int j = 0; j < numSolveParams; j++) {
+                for (int i = 0; i < numSolveParams; i++) {
+                    matrix->data.F64[j][i] = 0.0;
+                }
+                vector->data.F64[j] = 0.0;
+            }
+
+            // Evaluate the polynomial for each order
+            for (int j = 0; j < config->spatialOrder + 1; j++) {
+                for (int i = 0; i < config->spatialOrder + 1 - j; i++) {
+                    poly->mask[j][i] = 0;
+                    polyValues->data.F64[j][i] = psPolynomial2DEval(poly, ((double)stamp->x - nx)/nx,
+                                                                    ((double)stamp->y - ny)/ny);
+                    poly->mask[j][i] = 1;
+                }
+            }
+
+            // Iterate over the pixels
+            for (int y = stamp->y - config->footprint; y < stamp->y + config->footprint; y++) {
+                for (int x = stamp->x - config->footprint; x < stamp->x + config->footprint; x++) {
+                    float invNoise2 = 1.0/reference[y][x]; // The inverse of the noise, squared.
+                    nPix++;
+
+                    /* Iterate over the first convolution */
+                    for (int k1 = 0; k1 < numKernelParams; k1++) {
+                        poisKernelBasis *kernel1 = kernelParams->data[k1]; // Kernel basis #1
+
+                        int u1 = kernel1->u; // Offset in x
+                        int v1 = kernel1->v; // Offset in y
+                        int i1 = kernel1->xOrder; // Polynomial order in x
+                        int j1 = kernel1->yOrder; // Polynomial order in y
+                        // First convolution
+                        float conv1 = polyValues->data.F64[j1][i1] * reference[y - v1][x - u1];
+
+                        // Assuming that the first kernel component is 0 order in x and y, and 0 offset
+                        if (k1 != 0) {
+                            conv1 -= reference[y][x];
+                        }
+
+                        /* Iterate over the second convolution */
+                        for (int k2 = k1; k2 < numKernelParams; k2++) {
+                            poisKernelBasis *kernel2 = kernelParams->data[k2]; // Kernel basis #2
+
+                            int u2 = kernel2->u; // Offset in x
+                            int v2 = kernel2->v; // Offset in y
+                            int i2 = kernel2->xOrder; // Polynomial order in x
+                            int j2 = kernel2->yOrder; // Polynomial order in y
+                            // Second convolution
+                            float conv2 = polyValues->data.F64[j2][i2] * reference[y - v2][x - u2];
+
+                            // Assuming that the first kernel component is 0 order in x and y, and 0 offset
+                            if (k2 != 0) {
+                                conv2 -= reference[y][x];
+                            }
+
+                            // Add into the matrix element
+                            matrix->data.F64[k1][k2] += conv1 * // First convolution
+                                conv2 * // Second convolution
+                                invNoise2; // Divide by sigma^2, ~ poisson.
+                        } // Second convolution
+
+                        // Add into the vector element
+                        vector->data.F64[k1] += input[y][x] * // Input image, no convolution
+                            conv1 *     // Convolved reference image
+                            invNoise2; // Divide by sigma^2, ~ poisson.
+
+                        /* Background term */
+                        matrix->data.F64[k1][bgIndex] += conv1 * // Convolved reference image
+                            invNoise2; // Divide by sigma^2, ~ poisson.
+
+                    } // First convolution
+
+                    /* Background only terms */
+                    matrix->data.F64[bgIndex][bgIndex] += invNoise2;
+                    vector->data.F64[bgIndex] += input[y][x] * invNoise2;
+                }
+            } // Iterating over pixels in stamp
+
+            // Fill in other side of symmetric matrix
+            for (int k1 = 0; k1 < kernelParams->n; k1++) {
+                for (int k2 = 0; k2 < k1; k2++) {
+                    matrix->data.F64[k1][k2] = matrix->data.F64[k2][k1];
+                }
+                matrix->data.F64[bgIndex][k1] = matrix->data.F64[k1][bgIndex];
+            }
+
+            for (int k = 0; k < kernelParams->n; k++) {
+                poisKernelBasis *kernel = kernelParams->data[k]; // Kernel basis #2
+                int u = kernel->u; // Offset in x
+                int v = kernel->v; // Offset in y
+                matrix->data.F64[k][k] += config->penalty * (float)(u*u + v*v);
+            }
+
+            stamp->status = POIS_STAMP_USED; // We've calculated it now, so don't need to do so again.
+
+        } // Legitimate stamps
+
+    } // Iterating over stamps
+
+    psFree(polyValues);
+    psFree(poly);
+
+    // Return number of pixels over which matrix has been calculated
+    return nPix;
+}
Index: /tags/rel-0_0_2/pois/src/poisCheckKernel.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisCheckKernel.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisCheckKernel.c	(revision 7503)
@@ -0,0 +1,63 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pois.h"
+
+bool poisCheckKernel(const psVector *solution,// Kernel solution
+		     const psArray *kernels, // Kernel basis functions
+		     const poisConfig *config // Configuration
+    )
+{
+    psTrace("pois.checkKernel", 1, "Checking kernel....\n");
+
+    // Get kernel value as a function of radius
+    psVector *radKernel = psVectorAlloc((2*config->xKernel + 1) * (2*config->yKernel + 1), PS_TYPE_F32);
+    psVector *radii = psVectorAlloc((2*config->xKernel + 1) * (2*config->yKernel + 1), PS_TYPE_F32); // Radius
+    for (int i = 0; i < solution->n - 1; i++) {
+	poisKernelBasis *kernel = kernels->data[i]; // The kernel basis function
+	if (kernel->xOrder == 0 && kernel->yOrder == 0) {
+	    int u = kernel->u;		// x offset
+	    int v = kernel->v;		// y offset
+	    float distance = sqrtf((float)(u*u) + (float)(v*v)); // Distance from the centre
+	    radKernel->data.F32[i] = solution->data.F64[i];
+	    radii->data.F32[i] = distance;
+	}
+    }
+
+    psVector *sortIndex = psVectorSortIndex(NULL, radii); // Indices from sort
+    float distance = 0.0;		// Distance from the centre that's being examined
+    float sum = 0.0;			// Sum of kernel for that distance
+    int num = 0;			// Number of pixels at that distance
+    int centreIndex = sortIndex->data.U32[0]; // Index of the centre pixel
+    int numSuspect = 0;			// Number of radii considered suspect
+    for (int i = 0; i < (2*config->xKernel + 1) * (2*config->yKernel + 1); i++) {
+	unsigned int index = sortIndex->data.U32[i];
+	if (radii->data.F32[index] != distance) {
+	    psTrace("pois.checkKernel", 7, "Radius: %f\tMean: %f\n", distance, sum/(float)num);
+	    if (fabs(distance*sum/(float)num) > radKernel->data.F32[centreIndex]) {
+		numSuspect++;
+	    }
+	    sum = 0.0;
+	    num = 0;
+	    distance = radii->data.F32[index];
+	}
+	sum += radKernel->data.F32[index];
+	num++;
+    }
+    psTrace("pois.checkKernel", 7, "Radius: %f\tMean: %f\n", distance, sum/(float)num);
+    if (fabs(distance*sum/(float)num) > radKernel->data.F32[centreIndex]) {
+	numSuspect++;
+    }
+
+    psFree(radKernel);
+    psFree(radii);
+    psFree(sortIndex);
+
+    psTrace("pois.checkKernel", 5, "%d radii considered suspect.\n", numSuspect);
+
+    if (numSuspect > 0) {
+	psWarning("There is significant power at large radii.  The kernel may be bad.\n");
+	return false;
+    }
+
+    return true;
+}
Index: /tags/rel-0_0_2/pois/src/poisConvolveImage.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisConvolveImage.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisConvolveImage.c	(revision 7503)
@@ -0,0 +1,102 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+
+/* Convolve an image by the kernel */
+psImage *poisConvolveImage(const psImage *input, // Input image, to be convolved
+                           const psImage *mask, // Mask image
+                           const psVector *solution, // The solution vector, containing the coefficients
+                           const psArray *kernelParams, // The kernel parameters
+                           const poisConfig *config // The configuration
+    )
+{
+    assert(solution->n == kernelParams->n + 1); // Extra parameter for background
+    assert(solution->type.type == PS_TYPE_F64);
+    assert(input->numCols == mask->numCols && input->numRows == mask->numRows);
+    assert(mask->type.type == PS_TYPE_U8);
+    assert(input->type.type == PS_TYPE_F32);
+
+    int nx = input->numCols;            // The size of the image in x
+    int ny = input->numRows;            // The size of the image in y
+    float nxHalf = 0.5 * (float)nx;     // Half the size of the image
+    float nyHalf = 0.5 * (float)ny;     // Half the size of the image
+    int xKernel = config->xKernel;      // The half-size of the kernel in x
+    int yKernel = config->yKernel;      // The half-size of the kernel in y
+    float background = solution->data.F64[solution->n-1]; // The difference in background
+    int numParams = kernelParams->n;    // Number of kernel parameters
+
+    psImage *convolved = psImageAlloc(nx, ny, PS_TYPE_F32); // The convolved image, to be returned
+
+    psPolynomial2D *poly = psPolynomial2DAlloc(config->spatialOrder + 1, config->spatialOrder + 1,
+                                               PS_POLYNOMIAL_ORD ); // Polynomial
+    for (int i = 0; i < config->spatialOrder + 1; i++) {
+        for (int j = 0; j < config->spatialOrder + 1; j++) {
+            poly->coeff[i][j] = 1.0;
+            poly->mask[i][j] = 1;       // Mask all coefficients; unmask to evaluate
+        }
+    }
+
+    // Convolve only the middle part
+    for (int y = yKernel; y < ny - yKernel; y++) {
+        for (int x = xKernel; x < nx - xKernel; x++) {
+            if (! mask->data.U8[y][x] & (POIS_MASK_BAD | POIS_MASK_NEAR_BAD)) {
+                double conv = background; // Convolved value
+
+                float imageX = (x - nxHalf) / nxHalf; // Normalised position in x
+                float imageY = (y - nyHalf) / nyHalf; // Normalised position in y
+
+                // Iterate over the kernel basis functions
+                for (int k = 0; k < numParams; k++) {
+                    poisKernelBasis *kernel = kernelParams->data[k]; // The kernel basis function
+                    int u = kernel->u;
+                    int v = kernel->v;
+                    int xOrder = kernel->xOrder;
+                    int yOrder = kernel->yOrder;
+
+                    double polyVal = 1.0;
+                    if (xOrder != 0 || yOrder != 0) {
+                        // Evaluate the polynomial
+                        poly->mask[xOrder][yOrder] = 0;
+                        polyVal = psPolynomial2DEval(poly, imageX, imageY);
+                        poly->mask[xOrder][yOrder] = 1;
+                    }
+
+                    if (k == 0) {
+                        conv += solution->data.F64[k] * input->data.F32[y - v][x - u] * polyVal;
+                    } else {
+                        conv += solution->data.F64[k] *
+                            (input->data.F32[y - v][x - u] * polyVal - input->data.F32[y][x]);
+                    }
+                }
+                convolved->data.F32[y][x] = (float)conv;
+            }
+        }
+
+        /* Pad the rest with zeros */
+        for (int x = 0; x < xKernel; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+        for (int x = nx - xKernel; x < nx; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+    }
+
+    for (int y = 0; y < yKernel; y++) {
+        for (int x = 0; x < nx; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+    }
+    for (int y = ny - yKernel; y < ny; y++) {
+        for (int x = 0; x < nx; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+    }
+
+    psFree(poly);
+
+    return convolved;
+}
+
+
Index: /tags/rel-0_0_2/pois/src/poisExtractKernel.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisExtractKernel.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisExtractKernel.c	(revision 7503)
@@ -0,0 +1,70 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+/* Create a kernel image from the solution vector */
+psImage *poisExtractKernel(const psVector *solution, // Vector containing the kernel elements
+			   const psArray *kernelParams, // The kernel parameters
+			   float x,	// The relative (-1 to 1) x position for which to get the kernel
+			   float y,	// The relative (-1 to 1) y position for which to get the kernel
+			   const poisConfig *config // Configuration
+    )
+{
+    assert(solution->n == kernelParams->n + 1);
+    assert(solution->type.type == PS_TYPE_F64);
+    assert(config);
+    assert(x >= -1.0 && x <= 1.0);
+    assert(y >= -1.0 && y <= 1.0);
+
+    int xKernel = config->xKernel;	// Half-size of kernel in x
+    int yKernel = config->yKernel;	// Half-size of kernel in y
+    float kernelSum = 0.0;		// Sum of the kernel
+
+    /* Allocate the image */
+    psImage *image = psImageAlloc(2*xKernel + 1, 2*yKernel + 1, PS_TYPE_F32); // The kernel image
+    for (int j = 0; j < (2*yKernel + 1); j++) {
+	for (int i = 0; i < (2*xKernel + 1); i++) {
+	    image->data.F32[j][i] = 0.0;
+	}
+    }
+
+    psPolynomial2D *poly = psPolynomial2DAlloc(config->spatialOrder + 1, config->spatialOrder + 1,
+					       PS_POLYNOMIAL_ORD); // Polynomial for evaluation
+    for (int j = 0; j < config->spatialOrder + 1; j++) {
+	for (int i = 0; i < config->spatialOrder + 1; i++) {
+	    poly->coeff[j][i] = 1.0;
+	    poly->mask[j][i] = 1;	// Mask all coefficients; unmask to evaluate
+	}
+    }
+
+    psTrace("pois.extractKernel", 4, "Evaluating kernel at %f,%f\n", x, y);
+    /* Iterate over the kernel parameters */
+    for (int k = 0; k < kernelParams->n; k++) {
+	poisKernelBasis *kernel = kernelParams->data[k]; // The kernel basis function
+
+	int u = kernel->u;
+	int v = kernel->v;
+	int xOrder = kernel->xOrder;
+	int yOrder = kernel->yOrder;
+
+	// Evaluate polynomial
+	poly->mask[xOrder][yOrder] = 0;
+	double evaluation = solution->data.F64[k] * psPolynomial2DEval(poly, x, y);
+	poly->mask[xOrder][yOrder] = 1;
+
+	image->data.F32[v + yKernel][u + xKernel] += (float)evaluation;
+	kernelSum += (float)evaluation;
+    }
+
+    // Subtract from the centre pixel to remove the effect of the flux-conservation treatment
+    // This assumes that the "reference" is the centre pixel: u = 0, v = 0, xOrder = 0, yOrder = 0
+    image->data.F32[yKernel][xKernel] -= kernelSum - (float)solution->data.F64[0];
+
+    psTrace("pois.extractKernel", 4, "Kernel sum: %f\n", kernelSum);
+
+    psFree(poly);
+
+    return image;
+}
+
Index: /tags/rel-0_0_2/pois/src/poisFindStamps.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisFindStamps.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisFindStamps.c	(revision 7503)
@@ -0,0 +1,81 @@
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include "pslib.h"
+#include "pois.h"
+
+
+psArray *poisFindStamps(psArray *stamps, // Existing list of stamps, or NULL
+                        const psImage *image, // Image for which to find stamps
+                        const psImage *mask, // Mask image
+                        const poisConfig *config // Configuration values
+    )
+{
+    int nx = config->xImage;            // Size of image in x
+    int ny = config->yImage;            // Size of image in y
+
+    assert(image->numCols == nx && image->numRows == ny);
+    assert(mask->numCols == nx && mask->numRows == ny);
+    assert(image->type.type == PS_TYPE_F32);
+    assert(mask->type.type == PS_TYPE_U8);
+    assert(!stamps || stamps->n == config->nsx * config->nsy);
+
+    int footprint = config->footprint;  // Footprint for stamps
+    int borderx = footprint + config->xKernel; // Border around image in x
+    int bordery = footprint + config->yKernel; // Border around image in y
+    int nsx = config->nsx;              // Number of stamps in x
+    int nsy = config->nsy;              // Number of stamps in y
+
+    psF32 **pixels = image->data.F32;   // The image pixels
+
+    if (stamps == NULL) {
+        stamps = psArrayAlloc(nsx * nsy);
+        // Initialise
+        for (int i = 0; i < nsx * nsy; i++) {
+            stamps->data[i] = poisStampAlloc();
+        }
+    }
+
+    // Iterate over the image sections
+    int num = 0;
+    for (int i = 0; i < nsx; i++) {
+        for (int j = 0; j < nsy; j++) {
+            poisStamp *stamp = stamps->data[num]; // The stamp
+
+            // Only find a new stamp if we need to
+            if ((stamp->x == 0 && stamp->y == 0) || stamp->status == POIS_STAMP_RESET) {
+                // Find maximum non-masked value in the image section, but don't include a footprint around
+                // the edge
+                float max = - INFINITY;         // Negative infinity
+                int bestx = 0, besty = 0;       // Position of maximum
+
+                for (int y = bordery + j * (ny - 2.0 * bordery) / nsy;
+                     y < bordery + (j + 1) * (ny - 2.0 * bordery) / nsy; y++) {
+                    for (int x = borderx + i * (nx - 2.0 * borderx) / nsx;
+                         x < borderx + (i + 1) * (nx - 2.0 * borderx) / nsx; x++) {
+                        if (pixels[y][x] > max && poisCheckStamp(image, mask, x, y, config)) {
+                            max = pixels[y][x];
+                            bestx = x;
+                            besty = y;
+                        }
+                    }
+                } // Done iterating over this image section
+
+                // Store the stamp
+                if (max != - INFINITY) {
+                    stamp->x = bestx;
+                    stamp->y = besty;
+                    stamp->status = POIS_STAMP_RECALC;
+                } else {
+                    // No stamp in this section
+                    stamp->status = POIS_STAMP_BAD;
+                }
+            } // Finding a new stamp
+
+            psTrace("pois.findStamps", 7, "Stamp %d: %d,%d\n", num, stamp->x, stamp->y);
+            num++;
+        }
+    } // Done iterating over sections
+
+    return stamps;
+}
Index: /tags/rel-0_0_2/pois/src/poisKernelBasisFunctions.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisKernelBasisFunctions.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisKernelBasisFunctions.c	(revision 7503)
@@ -0,0 +1,77 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+psArray *poisKernelBasisFunctions(const poisConfig *config // Configuration
+    )
+{
+    assert(config);
+
+    psTrace("pois.kernelBasisFunctions", 1, "Setting kernel basis functions....\n");
+
+    int xKernelHalfSize = config->xKernel; // Half size of kernel in x
+    int yKernelHalfSize = config->yKernel; // Half size of kernel in y
+    int spatialOrder = config->spatialOrder; // Order of spatial variations
+	
+    // Number of basis functions
+    int nBF = (2 * xKernelHalfSize + 1) * (2 * yKernelHalfSize + 1) * (spatialOrder + 1) *
+	(spatialOrder + 2) / 2;
+
+    psArray *array = psArrayAlloc(nBF);	// Array to hold the basis functions
+
+    int num = 0;			// Kernel parameter number
+
+    // Put the (u,v) = (0,0) component right at the start of the list for convenience
+    for (int order = 0; order <= spatialOrder; order++) {
+	for (int i = 0; i <= order; i++) {
+	    int j = order - i;
+	    poisKernelBasis *kernel = psAlloc(sizeof(poisKernelBasis));
+	    kernel->u = 0;
+	    kernel->v = 0;
+	    kernel->xOrder = i;
+	    kernel->yOrder = j;
+
+	    // Stuff into the array
+	    array->data[num] = kernel;
+
+	    psTrace("pois.kernelBasisFunctions", 3, "--> %d: (%d,%d) x^%d y^%d\n", i, kernel->u,
+		    kernel->v, kernel->xOrder, kernel->yOrder);
+	    num++;
+	}
+    }
+
+    // Iterate over (u,v)
+    for (int u = -xKernelHalfSize; u <= xKernelHalfSize; u++) {
+	for (int v = -yKernelHalfSize; v <= yKernelHalfSize; v++) {
+
+	    // Already did (u,v) = (0,0): it's at the start, so skip it now.
+	    if ((u != 0) || (v != 0)) {
+
+		// Iterate over spatial order
+		for (int order = 0; order <= spatialOrder; order++) {
+
+		    for (int i = 0; i <= order; i++) {
+			int j = order - i;
+			poisKernelBasis *kernel = psAlloc(sizeof(poisKernelBasis));
+
+			kernel->u = u;
+			kernel->v = v;
+			kernel->xOrder = i;
+			kernel->yOrder = j;
+
+			// Stuff into the array
+			array->data[num] = kernel;
+
+			psTrace("pois.kernelBasisFunctions", 3, "--> %d: (%d,%d) x^%d y^%d\n", num,
+				kernel->u, kernel->v, kernel->xOrder, kernel->yOrder);
+			num++;
+		    }
+		} // Iterating over spatial order
+	    } // Except (u,v) = (0,0)
+	}
+    } // Iterating over (u,v)
+
+    return array;
+}
+
Index: /tags/rel-0_0_2/pois/src/poisMakeMask.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisMakeMask.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisMakeMask.c	(revision 7503)
@@ -0,0 +1,63 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+psImage *poisMakeMask(psImage *mask,	// Mask to update, or NULL
+		      const psImage *refImage, // Reference image
+		      const psImage *inImage, // Input image
+		      const poisConfig *config // Configuration
+    )
+{
+    assert(!mask || mask->type.type == PS_TYPE_U8);
+    assert(!mask || (mask->numCols == refImage->numCols && mask->numRows == refImage->numRows));
+    assert(refImage->numCols == inImage->numCols && refImage->numRows == inImage->numRows);
+    assert(refImage->type.type == PS_TYPE_F32);
+    assert(inImage->type.type == PS_TYPE_F32);
+    assert(config);
+
+    int nx = config->xImage;		// Size in x
+    int ny = config->yImage;		// Size in y
+
+    psTrace("pois.makeMask", 5, "Creating mask: %dx%d\n", nx, ny);
+
+    // Allocate the mask image, if necessary
+    if (mask == NULL) {
+	mask = psImageAlloc(nx, ny, PS_TYPE_U8);
+	// Initialise to zero
+	for (int j = 0; j < ny; j++) {
+	    for (int i = 0; i < nx; i++) {
+		mask->data.U8[j][i] = POIS_MASK_OK;
+	    }
+	}
+    }
+
+    // Iterate over the images
+    for (int y = config->yKernel; y < ny - config->yKernel; y++) {
+	for (int x = config->xKernel; x < nx - config->xKernel; x++) {
+	    // Mask all around something that's saturated in the reference, since it gets convolved
+	    // But only mask single pixel in the input image, since it doesn't get convolved
+	    if (refImage->data.F32[y][x] >= config->refSat || refImage->data.F32[y][x] <= config->refBad ||
+		isnan(refImage->data.F32[y][x])) {
+		mask->data.U8[y][x] |= POIS_MASK_BAD;
+		int xlow = MAX(0, x - config->xKernel);
+		int xhigh = MIN(nx, x + config->xKernel);
+		int ylow = MAX(0, y - config->yKernel);
+		int yhigh = MIN(ny, y + config->yKernel);
+		for (int v = ylow; v <= yhigh; v++) {
+		    for (int u = xlow; u <= xhigh; u++) {
+			mask->data.U8[v][u] |= POIS_MASK_NEAR_BAD;
+		    }
+		}
+	    } else if (inImage->data.F32[y][x] >= config->inSat || inImage->data.F32[y][x] <= config->inBad ||
+		isnan(inImage->data.F32[y][x])) {
+		mask->data.U8[y][x] |= POIS_MASK_BAD;
+	    }
+	}
+    }
+
+    return mask;
+}
Index: /tags/rel-0_0_2/pois/src/poisMaskOps.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisMaskOps.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisMaskOps.c	(revision 7503)
@@ -0,0 +1,92 @@
+/*
+ * Some utility routines that should be in psLib
+ */
+#include <stdio.h>
+#include <memory.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+psImage *poisImageSetVal(psImage *restrict in,	     // Input image
+			 const psC64 val	     // set to this value
+    )
+{
+    assert (in != NULL);
+
+    switch (in->type.type) {
+      case PS_TYPE_U8:
+	{
+	    const int numCols = in->numCols;
+	    psU8 *row0 = in->data.U8[0];
+	    for (int c = 0; c < numCols; c++) {
+		row0[c] = val;
+	    }
+	    for (int r = 1; r < in->numRows; r++) {
+		memcpy(in->data.U8[r], row0, numCols*sizeof(psU8));
+	    }
+	}
+	break;
+      case PS_TYPE_F32:
+	{
+	    const int numCols = in->numCols;
+	    psF32 *row0 = in->data.F32[0];
+	    for (int c = 0; c < numCols; c++) {
+		row0[c] = val;
+	    }
+	    for (int r = 1; r < in->numRows; r++) {
+		memcpy(in->data.F32[r], row0, numCols*sizeof(psF32));
+	    }
+	}
+	break;
+      default:
+	psError(POIS_ERR_UNSUPPORTED_TYPE, true,
+		"Type %d is not supported", in->type.type);
+	return NULL;
+    }
+
+    return in;
+}
+
+psImage *poisImageSetValInMask(psImage *out,			// Image to update, or NULL
+			       const psImage *restrict in,	// Input image
+			       const psImage *mask,		// mask for image
+			       const psC64 val,			// set to this value
+			       const unsigned long bits		// set pixels where mask & bits != 0
+    )
+{
+    assert(mask != NULL);
+    assert(mask->type.type == PS_TYPE_U8);
+    const int numCols = mask->numCols;
+    const int numRows = mask->numRows;
+    assert(in != NULL);
+    assert(in->type.type == PS_TYPE_F32);
+    assert(in->numCols == numCols && in->numRows == numRows);
+
+    // Allocate the output image, if necessary
+    if (out == NULL) {
+	out = psImageCopy(NULL, in, in->type.type);
+    }
+
+    switch (in->type.type) {
+      case PS_TYPE_F32:
+	{
+	    const int numCols = in->numCols;
+	    for (int r = 0; r < in->numRows; r++) {
+		psF32 *row = out->data.F32[r]; // n.b. a copy of in
+		psU8 *mrow = mask->data.U8[r];
+		for (int c = 0; c < numCols; c++) {
+		    if (mrow[c] & bits) {
+			row[c] = val;
+		    }
+		}
+	    }
+	}
+	break;
+      default:
+	psError(POIS_ERR_UNSUPPORTED_TYPE, true,
+		"Type %d is not supported", in->type.type);
+	return NULL;
+    }
+
+    return out;
+}
Index: /tags/rel-0_0_2/pois/src/poisParseConfig.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisParseConfig.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisParseConfig.c	(revision 7503)
@@ -0,0 +1,187 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "pois.h"
+
+void help(void)
+{
+    fprintf (stderr, "POIS: Pan-STARRS (or Pricey's) Optimal Image Subtraction\n"
+	     "Usage: pois [-h] [-v] [-k X Y] [-t T] [-s S1 S2] [-b B1 B2] [-B BG] [-o N] [-n NSTAMPS] [-S STAMPS] [-i ITER] [-r REJ] [-m] REF IN OUT\n"
+	     "where\n"
+	     "\t-h         Help (this info)\n"
+	     "\t-v         Increase verbosity level\n"
+	     "\t-k X Y     Kernel half-size in x and y (5)\n"
+	     "\t-t T       Threshold for stamps on reference image (0)\n"
+	     "\t-s S1 S2   Saturation level for reference (S1) and input (S2) (50000)\n"
+	     "\t-b B1 B2   Bad level for both images (0)\n"
+	     "\t-B BG      Level to add to background to get above zero (0)\n"
+	     "\t-o ORDER   Order of spatial variations in the kernel (0)\n"
+	     "\t-n NSTAMPS Number of stamps in each dimension (5)\n"
+	     "\t-S STAMPS  File from which to read stamps\n"
+	     "\t-R         Reverse the sense of the subtraction (REF - IN)\n"
+	     "\t-i ITER    Number of rejection iterations (10)\n"
+	     "\t-r REJ     Rejection level in standard deviations (2.5)\n"
+	     "\t-m         Output mask frame (OUT.mask)\n"
+	     "\tREF        Reference image\n"
+	     "\tIN         Input image (to be matched to REF)\n"
+	     "\tOUT        Output image\n"
+	);
+}
+
+poisConfig *poisParseConfig(int argc, // Number of command-line arguments
+			    char **argv // Command-line arguments
+    )
+{
+    poisConfig *config;			// Configuration values
+
+    /* Variables for getopt */
+    int opt;   /* Option, from getopt */
+    extern char *optarg;   /* Argument accompanying switch */
+    extern int optind;   /* getopt variables */
+
+    /* Initialise configuration */
+    config = (poisConfig *) psAlloc(sizeof(poisConfig));
+    config->verbose = 0;
+    config->refFile = NULL;
+    config->inFile = NULL;
+    config->outFile = NULL;
+    config->xKernel = 5;
+    config->yKernel = 5;
+    config->footprint = 15;
+    config->threshold = 0.0;
+    config->refSat = 60000.0;
+    config->inSat = 60000.0;
+    config->refBad = 0.0;
+    config->inBad = 0.0;
+    config->background = 0.0;
+    config->spatialOrder = 0;
+    config->nsx = 5;
+    config->nsy = 5;
+    config->numIter = 10;
+    config->sigmaRej = 2.5;
+    config->penalty = 0.0;
+    config->stampFile = NULL;
+    config->reverse = false;
+    config->mask = false;
+
+    /* Parse command-line arguments using getopt */
+    while ((opt = getopt(argc, argv, "hvRmk:f:q:t:s:o:b:B:n:i:r:p:S:")) != -1) {
+        switch (opt) {
+	  case 'h':
+	    help();
+	    exit(EXIT_SUCCESS);
+	  case 'v':
+            config->verbose++;
+            break;
+	  case 'k':
+            if ((sscanf(argv[optind-1], "%d", &config->xKernel) != 1) ||
+                (sscanf(argv[optind++], "%d", &config->yKernel) != 1)) {
+                /*
+                  Note: incrementing optind, so I can read more than
+                  one parameter.
+                */
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    break;
+	  case 'f':
+	    if (sscanf(optarg, "%d", &config->footprint) != 1) {
+		help();
+                exit(EXIT_FAILURE);
+	    }
+	    break;
+	  case 't':
+	    if (sscanf(optarg, "%f", &config->threshold) != 1) {
+		help();
+                exit(EXIT_FAILURE);
+	    }
+	    break;
+	  case 's':
+            if ((sscanf(argv[optind-1], "%f", &config->refSat) != 1) ||
+                (sscanf(argv[optind++], "%f", &config->inSat) != 1)) {
+                /*
+                  Note: incrementing optind, so I can read more than
+                  one parameter.
+                */
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    break;
+	  case 'o':
+	    if (sscanf(optarg, "%d", &config->spatialOrder) != 1) {
+		help();
+                exit(EXIT_FAILURE);
+	    }
+	    break;
+	  case 'b':
+            if ((sscanf(argv[optind-1], "%f", &config->refBad) != 1) ||
+                (sscanf(argv[optind++], "%f", &config->inBad) != 1)) {
+                /*
+                  Note: incrementing optind, so I can read more than
+                  one parameter.
+                */
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    break;
+	  case 'B':
+	    if (sscanf(optarg, "%f", &config->background) != 1) {
+		help();
+                exit(EXIT_FAILURE);
+	    }
+	    break;
+	  case 'n':
+            if (sscanf(argv[optind-1], "%d", &config->nsx) != 1) {
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    config->nsy = config->nsx;
+	    break;
+	  case 'i':
+            if (sscanf(argv[optind-1], "%d", &config->numIter) != 1) {
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    break;
+	  case 'r':
+            if (sscanf(argv[optind-1], "%f", &config->sigmaRej) != 1) {
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    break;
+	  case 'p':
+            if (sscanf(argv[optind-1], "%f", &config->penalty) != 1) {
+                help();
+                exit(EXIT_FAILURE);
+            }
+	    break;
+	  case 'S':
+	    config->stampFile = argv[optind-1];
+	    config->threshold = -INFINITY;
+	    break;
+	  case 'R':
+	    config->reverse = true;
+	    break;
+	  case 'm':
+            config->mask = true;
+            break;
+	  default:
+	    help();
+	}
+    }
+
+    /* Get the remaining, mandatory values */
+    argc -= optind;
+    argv += optind;
+    if (argc < 3) {
+        help();
+        exit(EXIT_FAILURE);
+    }
+
+    config->refFile = argv[0];
+    config->inFile = argv[1];
+    config->outFile = argv[2];
+
+    return config;
+}
+
Index: /tags/rel-0_0_2/pois/src/poisReadStamps.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisReadStamps.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisReadStamps.c	(revision 7503)
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pois.h"
+
+#define MAXLINE 128                     // Maximum length of a line in the stamps file
+
+
+psArray *poisReadStamps(psArray *stamps,// Stamps
+                        FILE **stampFP, // File pointer for stamps file
+                        const psImage *image, // Image for which to find stamps
+                        const psImage *mask, // Mask image
+                        const poisConfig *config // Configuration
+    )
+{
+    FILE *fp = *stampFP;                // File pointer
+
+    if (stamps == NULL) {
+        stamps = psArrayAlloc(config->nsx);
+        // Initialise
+        for (int i = 0; i < config->nsx; i++) {
+            stamps->data[i] = poisStampAlloc();
+        }
+    }
+
+    for (int i = 0; i < stamps->n; i++) {
+        poisStamp *stamp = stamps->data[i];
+
+        if (stamp->status == POIS_STAMP_RESET) {
+            if (fp) {
+                float x, y;             // Position of stamp
+                int xPixel, yPixel;     // Integer position
+                int result = 0;         // Result of fscanf
+                char line[MAXLINE];     // The line from the stamps file
+                char *lineBack;         // The line back from fgets
+                do {
+                    lineBack = fgets(line, MAXLINE, fp);
+                    result = sscanf(line, "%f %f", &x, &y);
+
+                    // Add 0.5 for rounding, and subtract 1.0 for unit-offset to zero-offset
+                    xPixel = (int)(x - 0.5);
+                    yPixel = (int)(y - 0.5);
+                } while (result == 2 && lineBack && ! poisCheckStamp(image, mask, xPixel, yPixel, config));
+                if (result == 2 && lineBack) {
+                    stamp->x = xPixel;
+                    stamp->y = yPixel;
+                    stamp->status = POIS_STAMP_RECALC;
+                    psTrace("pois.readStamps", 7, "Stamp %d: %d,%d\n", i, stamp->x, stamp->y);
+                } else {
+                    // We've run out of stamps in the file --- probably got EOF
+                    stamp->status = POIS_STAMP_BAD;
+                }
+            } else {
+                // Stamp file isn't open!
+                stamp->status = POIS_STAMP_BAD;
+            }
+        }
+    }
+
+    return stamps;
+}
+
Index: /tags/rel-0_0_2/pois/src/poisRejectStamps.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisRejectStamps.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisRejectStamps.c	(revision 7503)
@@ -0,0 +1,66 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+#define SQUARE(x) ((x)*(x))
+
+bool poisRejectStamps(psArray *stamps,  // Array of stamps
+                      psImage *mask,    // Mask image
+                      const psVector *deviations, // Vector of deviations for the stamps
+                      const poisConfig *config // Configuration
+    )
+{
+    assert(stamps);
+    assert(mask);
+    assert(deviations);
+    assert(config);
+    assert(stamps->n == deviations->n);
+    assert(mask->numCols == config->xImage && mask->numRows == config->yImage);
+    assert(mask->type.type == PS_TYPE_U8);
+    assert(deviations->type.type == PS_TYPE_F32);
+
+    bool masked = false;                // Have we masked any stamps?
+
+    // Don't want the standard deviation, but rather the deviation from zero
+    double sum = 0.0;
+    int numDev = 0;
+    for (int i = 0; i < deviations->n; i++) {
+        poisStamp *stamp = stamps->data[i];     // The stamp
+        // Only interested in the stamps that we're actually using
+        if (stamp->status == POIS_STAMP_USED) {
+            sum += SQUARE(deviations->data.F32[i]);
+            numDev++;
+        }
+    }
+    float meanDev = sqrtf(sum / (float)numDev);
+    float limit = meanDev * config->sigmaRej;
+    psTrace("pois.rejectStamps", 2, "Mean RMS deviation: %f\n", meanDev);
+    psTrace("pois.rejectStamps", 2, "Rejection limit: %f\n", limit);
+
+    // Reject stamps
+    for (int s = 0; s < deviations->n; s++) {
+        poisStamp *stamp = stamps->data[s];
+        if (stamp->status == POIS_STAMP_USED &&
+            (fabsf(deviations->data.F32[s]) > limit || ! isfinite(deviations->data.F32[s]))) {
+            masked = true;
+            psTrace("pois.rejectStamps", 1, "Rejecting stamp %d (%d,%d): %f\n", s, stamp->x, stamp->y,
+                    deviations->data.F32[s]);
+
+            // Mask out the stamp in the image so you don't find it again
+            for (int y = stamp->y - config->footprint; y < stamp->y + config->footprint; y++) {
+                for (int x = stamp->x - config->footprint; x < stamp->x + config->footprint; x++) {
+                    mask->data.U8[y][x] |= POIS_MASK_STAMP;
+                }
+            }
+
+            // Set stamp for replacement
+            stamp->x = 0;
+            stamp->y = 0;
+            stamp->status = POIS_STAMP_RESET;
+
+        } // Bad stamps
+    } // Iterating over stamps
+
+    return masked;
+}
Index: /tags/rel-0_0_2/pois/src/poisSolveEquation.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisSolveEquation.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisSolveEquation.c	(revision 7503)
@@ -0,0 +1,69 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pois.h"
+
+psVector *poisSolveEquation(psVector *solution,	// Solution vector, or NULL
+			    const psArray *stamps, // Array of stamps
+			    const poisConfig *config
+    )
+{
+    assert(stamps);
+    assert(config);
+    assert(!solution || solution->type.type == PS_TYPE_F64);
+
+    int size = (2 * config->xKernel + 1) * (2 * config->yKernel + 1) * (config->spatialOrder + 1) *
+	(config->spatialOrder + 2) / 2 + 1; // Size of matrix and vector
+
+    assert(!solution || solution->n == size);
+
+    psImage *matrix = psImageAlloc(size, size, PS_TYPE_F64);
+    psVector *vector = psVectorAlloc(size, PS_TYPE_F64);
+    for (int j = 0; j < size; j++) {
+	for (int i = 0; i < size; i++) {
+	    matrix->data.F64[j][i] = 0.0;
+	}
+	vector->data.F64[j] = 0.0;
+    }
+
+    for (int s = 0; s < stamps->n; s++) {
+	poisStamp *stamp = stamps->data[s]; // Stamp of interest
+	psImage *stampMatrix = stamp->matrix; // Corresponding matrix
+	psVector *stampVector = stamp->vector; // Corresponding vector
+
+	// Check inputs
+	assert(matrix->type.type == PS_TYPE_F64);
+	assert(vector->type.type == PS_TYPE_F64);
+	assert(matrix->numCols == size && matrix->numRows == size);
+	assert(vector->n == size);
+
+	if (stamp->status == POIS_STAMP_USED) {
+	    (void)psBinaryOp(matrix, matrix, "+", stampMatrix);
+	    (void)psBinaryOp(vector, vector, "+", stampVector);
+	}
+    }
+
+    // Solution via LU Decomposition
+    psVector *permutation = NULL; // Permutation vector for LU decomposition
+    psImage *luMatrix = psMatrixLUD(NULL, &permutation, matrix); // LU decomposed matrix
+    solution = psMatrixLUSolve(solution, luMatrix, vector, permutation); // Solution in x
+    psTrace("pois.solveEquation", 1, "Background difference is %f\n", solution->data.F64[solution->n - 1]);
+
+#if 0
+    printf("Matrix:\n");
+    for (int i = 0; i < matrix->numCols; i++) {
+	for (int j = 0; j < matrix->numRows; j++) {
+	    printf("%f ", matrix->data.F64[i][j]);
+	}
+	printf("\n");
+    }
+#endif
+
+    // Clean up
+    psFree(luMatrix);
+    psFree(permutation);
+    psFree(matrix);
+    psFree(vector);
+
+    return solution;
+}
Index: /tags/rel-0_0_2/pois/src/poisStamp.c
===================================================================
--- /tags/rel-0_0_2/pois/src/poisStamp.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/src/poisStamp.c	(revision 7503)
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pois.h"
+
+poisStamp *poisStampAlloc(void)
+{
+    poisStamp *stamp = (poisStamp*)psAlloc(sizeof(poisStamp));
+    stamp->x = 0;
+    stamp->y = 0;
+    stamp->status = POIS_STAMP_RESET;
+    stamp->matrix = NULL;
+    stamp->vector = NULL;
+    psMemSetDeallocator(stamp, (psFreeFunc)poisStampFree);
+
+    return stamp;
+}
+
+void poisStampFree(poisStamp *stamp)
+{
+    if (stamp->matrix) {
+	psFree(stamp->matrix);
+    }
+    if (stamp->vector) {
+	psFree(stamp->vector);
+    }
+    psFree(stamp);
+}
+
+
+// Return true if the stamp is OK
+bool poisCheckStamp(const psImage *image, // Image to check for threshold
+		    const psImage *mask, // Mask to check for bad pixels
+		    int x, int y,	// Pixel coordinates
+		    const poisConfig *config // Configuration
+    )
+{
+    if (x < config->footprint + config->xKernel ||
+	y < config->footprint + config->yKernel ||
+	x > config->xImage - config->footprint - config->xKernel ||
+	y > config->yImage - config->footprint - config->yKernel ||
+	image->data.F32[y][x] < config->threshold) {
+	psTrace("pois.checkStamp", 9, "Rejecting stamp at %d,%d (border/threshold)\n", x, y);
+	return false;
+    }
+
+    // Check the footprint
+    bool ok = true;			// Is the footprint OK?
+    int footprint = config->footprint;	// Footprint size
+    for (int v = y - footprint; v <= y + footprint && ok; v++) {
+	for (int u = x - footprint; u <= x + footprint && ok; u++) {
+	    if (mask->data.U8[v][u] &
+		(POIS_MASK_BAD | POIS_MASK_NEAR_BAD | POIS_MASK_STAMP)) {
+		psTrace("pois.checkStamp", 9, "Rejecting stamp at %d,%d (bad footprint)\n", x, y);
+		ok = false;
+	    }
+	}
+    }
+    
+    return ok;
+}
+
Index: /tags/rel-0_0_2/pois/swig/.cvsignore
===================================================================
--- /tags/rel-0_0_2/pois/swig/.cvsignore	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/.cvsignore	(revision 7503)
@@ -0,0 +1,9 @@
+Makefile Makefile.in
+*.pm *.pyc
+*_wrap.c
+poisSwig.py psLibSwig.py xpaSwig.py
+swigrun.py
+xpa.py
+.Makefile.depend
+*.la *.lo
+.deps .libs
Index: /tags/rel-0_0_2/pois/swig/Makefile.am
===================================================================
--- /tags/rel-0_0_2/pois/swig/Makefile.am	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/Makefile.am	(revision 7503)
@@ -0,0 +1,82 @@
+AM_CFLAGS = @pois_CFLAGS@
+
+AM_CFLAGS += $(PSLIB_CFLAGS)
+AM_CFLAGS += $(SWIG_TARGET_CPPFLAGS)
+AM_CFLAGS += -DPS_ALLOW_MALLOC
+AM_CFLAGS += -I../src
+
+LIBS       = $(AM_LIBS)
+LIBS      += $(SWIG_TARGET_LIBS)
+
+SWIG += -w451
+SWIG += $(SWIG_TARGET_OPT)
+SWIG += $(PSLIB_CFLAGS)
+SWIG += -Drestrict= -Dinline=
+
+AM_CFLAGS += -I../src
+#
+# Libraries to install
+#
+SWIG_LIB_NAMES =     poisSwig       psLibSwig       xpaSwig
+lib_LTLIBRARIES = libpoisSwig.la libpsLibSwig.la libxpaSwig.la
+
+BUILT_SOURCES = \
+	psLibSwig.py psLibSwig_wrap.c \
+	xpaSwig.py xpaSwig_wrap.c
+#
+# PSLib wrappers
+#
+libpsLibSwig_la_LDFLAGS  = -module
+libpsLibSwig_la_LIBADD   = $(PSLIB_LIBS)
+
+libpsLibSwig_la_SOURCES = \
+	psLibSwig_wrap.c \
+	psLibForSwig.c psLibForSwig.h
+
+psLibSwig.py psLibSwig_wrap.c : psLibSwig.i p_psSwig.i
+	$(SWIG) $(PS_INCLUDES) psLibSwig.i
+#
+# Now for XPA
+#
+libxpaSwig_la_LIBADD       = $(PSLIB_LIBS)
+libxpaSwig_la_LIBADD      += -lxpa
+
+libxpaSwig_la_LDFLAGS = -module
+libxpaSwig_la_SOURCES = \
+	xpaSwig_wrap.c \
+	simpleFits.c
+
+xpaSwig.py xpaSwig_wrap.c : xpaSwig.i p_psSwig.i
+	$(SWIG) $(PS_INCLUDES) xpaSwig.i
+	@case "$(SWIG)" in \
+	  *-perl*) \
+		perl -pi -e 's/([a-zA-Z0-9_]+Array)/_xpa_\1/g' xpaSwig_wrap.c;; \
+	esac
+#
+# And the pois modules
+#
+libpoisSwig_la_LDFLAGS = -module
+libpoisSwig_la_SOURCES = \
+	poisSwig_wrap.c
+libpoisSwig_la_LIBADD = ../src/libpois.la
+
+poisSwig.py poisSwig_wrap.c : poisSwig.i ../src/pois.h p_psSwig.i
+	$(SWIG) $(PS_INCLUDES) -I../src poisSwig.i
+	@case "$(SWIG)" in \
+	  *-perl*) \
+		perl -pi -e 's/([a-zA-Z0-9_]+Array)/_pois_\1/g' poisLib_wrap.c;; \
+	esac
+#
+# Patch up name of .so file for the sake of python
+#
+install-exec-hook:
+	cd $(prefix)/lib && \
+	for f in $(SWIG_LIB_NAMES); do \
+	   $(LN_S) -f `perl -ne "if(/dlname='([^']+)'/) { print \\$$1; exit }" lib$$f.la` _$$f.so; \
+	done
+
+uninstall-local:
+	for f in $(SWIG_LIB_NAMES); do $(RM) $(prefix)/lib/_$$f.so; done
+
+CLEANFILES = Makefile Makefile.in \
+	*_wrap.c *.pyc poisSwig.py psLibSwig.py xpaSwig.py *~ core core.*
Index: /tags/rel-0_0_2/pois/swig/p_psSwig.i
===================================================================
--- /tags/rel-0_0_2/pois/swig/p_psSwig.i	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/p_psSwig.i	(revision 7503)
@@ -0,0 +1,267 @@
+// -*- panstarrs-c -*-
+%nodefault;
+
+%include "exception.i"
+#if !defined(SWIGXML)
+%include "typemaps.i"
+#endif
+%include "carrays.i"
+
+%array_class(int, intArray);
+%array_class(float, floatArray);
+%array_class(double, doubleArray);
+
+/************************************************************************************************************/
+
+#if defined(SWIGPYTHON)
+%pythoncode {
+class PsUtilsError(StandardError):
+	"""An error generated by psUtils"""
+}
+#endif
+
+/************************************************************************************************************/
+
+%{
+#  include "pslib.h"
+
+static void *exception(int exception_type, const char *fmt, ...)
+{
+    char message[256];
+    va_list ap;
+
+    va_start(ap, fmt);
+    vsnprintf(message, 256, fmt, ap);
+    va_end(ap);
+
+    SWIG_exception(exception_type, message)
+
+ fail:					// used by SWIG_exception
+    return NULL;
+} 
+
+static void *ps_exception(void)
+{
+    psErr *err = psErrorLast();
+    char message[256];
+    snprintf(message, 256, "(%s) %s", err->name, err->msg);
+    psFree(err);
+
+    SWIG_exception(SWIG_RuntimeError, message)
+
+ fail:					// used by SWIG_exception
+    return NULL;
+} 
+
+#if 0
+static PyObject *set_thisown(PyObject *resultobj, int val)
+{
+    PyObject *n = PyInt_FromLong(val);
+    PyObject_SetAttrString(resultobj, (char *)"thisown", n);
+    Py_DECREF(n);
+
+    return resultobj;
+}
+#endif
+
+%}
+
+/************************************************************************************************************/
+/*
+ * Typemaps
+ */
+%typemap(python, out) psStatus {
+    if (result == 0) {
+	Py_INCREF(Py_None); resultobj = Py_None;
+    } else {
+	ps_exception();
+	return NULL;
+    }
+}
+
+%typemap(python,in) FILE * {
+    if ($input == Py_None) {
+	$1 = NULL;
+    } else if (!PyFile_Check($input)) {
+	PyErr_SetString(PyExc_TypeError, "Need a file!");
+	goto fail;
+    } else {
+	$1 = PyFile_AsFile($input);
+    }
+}
+
+%typemap(python,in) char * {
+    if ($input == Py_None) {
+	$1 = NULL;
+    } else if (!PyString_Check($input)) {
+	PyErr_SetString(PyExc_TypeError, "Need a string!");
+	goto fail;
+    } else {
+	$1 = PyString_AsString($input);
+    }
+}
+
+/************************************************************************************************************/
+/*
+ * Tell our extension language that this function returns a new object (that can't be NULL)
+ */
+%define NEWOBJECT(func)
+    %newobject func;
+    NOTNULL(func);
+%enddef
+
+/************************************************************************************************************/
+/*
+ * Tell our extension language that it should call psFree to delete an object
+ */
+%define EXPORT_TYPE(type)
+    %extend type {
+	~type() {
+	    if (psMemGetRefCounter(self) == 1) {
+		const psMemBlock *mb = ((psMemBlock *)self) - 1;
+
+#if !defined(PS_NO_TRACE)
+		p_psTrace("ps.utils.memory", 6, "Freeing %p (%ld: %s:%d)\n", self, (long)mb->id,
+			  mb->file, mb->lineno);
+#endif
+	    }
+	    psFree(self);
+	}
+    }
+
+    %{
+        type *type ## _Cast(void *ptr) {
+	    return (type *)ptr;
+	}
+    %}
+    type *type ## _Cast(void *ptr);
+
+    NEWOBJECT(type ## Alloc);
+%enddef
+
+/************************************************************************************************************/
+/*
+ * Throw an exception if func returns NULL
+ */
+%define NOTNULL(func)
+    %exception func {
+        $action;
+	if (result == NULL) {
+	    $cleanup;
+#if defined(SWIGPERL) 
+	    ps_exception();
+#else
+	    return ps_exception();
+#endif
+	}
+    }
+%enddef
+
+/*
+ * Throw an exception if func returns a negative value
+ */
+%define NOTNEGATIVE(func)
+    %exception func {
+        $action;
+	if (result < 0) {
+	    $cleanup;
+#if defined(SWIGPERL) 
+	    ps_exception();
+#else
+	    return ps_exception();
+#endif
+	}
+    }
+%enddef
+
+/*
+ * Throw an exception if func returns non-zero
+ */
+%define ISZERO(func)
+    %exception func {
+        $action;
+	if (result != 0) {
+	    $cleanup;
+#if defined(SWIGPERL) 
+	    ps_exception();
+#else
+	    return ps_exception();
+#endif
+	}
+    }
+%enddef
+
+/************************************************************************************************************/
+/*
+ * If an argument is passed in, and also returned by the function, we need to declare
+ * the function %newobject, and also ensure that the input function has its refCounter
+ * incremented (as perl/python etc. will think that it's gone out of scope)
+ *
+ * This macro defined a typemap to handle the refcounter.
+ *
+ * Do this as an argout map as we don't get another reference to the image if there's an error
+ */
+%define TYPE_INOUT(type, argname)
+    %typemap(argout) type argname {
+        if ($1 != NULL) {
+	    const psMemBlock *mb = ((psMemBlock *)$1) - 1;
+	    p_psTrace("ps.utils.memory", 4, "Incrementing reference counter for %p (%ld %s:%d)\n",
+		      $1, (long)mb->id, mb->file, mb->lineno);
+	    psMemIncrRefCounter($1);
+	}
+    }
+%enddef
+
+%define CLEAR_TYPE_INOUT(type, argname)
+    %typemap(argout) type argname;
+%enddef
+
+/************************************************************************************************************/
+
+%exception;
+
+/************************************************************************************************************/
+/*
+ * Accept a python list of floats if the routine wants an array
+ */
+%typemap(in) (const float *floatArr, int nel) {
+    /* Check if is a list */
+    if (PyList_Check($input)) {
+	$2 = PyList_Size($input);
+	if ($2 == 0) {
+	    $1 = NULL;
+	} else {
+	    $1 = psAlloc($2*sizeof(float));
+	
+	    for (int i = 0; i < $2; i++) {
+		PyObject *o = PyList_GetItem($input,i);
+		if (PyFloat_Check(o))
+		    $1[i] = PyFloat_AsDouble(PyList_GetItem($input, i));
+		else {
+		    PyErr_SetString(PyExc_TypeError, "list must contain floats");
+		    psFree($1);
+		    return NULL;
+		}
+	    }
+	}
+    } else {
+	PyErr_SetString(PyExc_TypeError,"not a list");
+	return NULL;
+    }
+}
+
+%typemap(freearg) (const float *floatArr, int nel) {
+    psFree($1);
+}
+
+/************************************************************************************************************/
+
+%apply double *OUTPUT { double *irow };
+%apply double *OUTPUT { double *icol };
+%apply double *OUTPUT { double *xPos };
+%apply double *OUTPUT { double *yPos };
+%apply double *OUTPUT { double *alpha };
+%apply double *OUTPUT { double *delta };
+%apply double *OUTPUT { double *alt };
+%apply double *OUTPUT { double *az };
+%apply double *OUTPUT { double *lst };
Index: /tags/rel-0_0_2/pois/swig/poisSwig.i
===================================================================
--- /tags/rel-0_0_2/pois/swig/poisSwig.i	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/poisSwig.i	(revision 7503)
@@ -0,0 +1,33 @@
+// -*- C -*-
+/*
+ */
+%module poisSwig
+
+%{
+#include "pois.h"
+%}
+
+%include "p_psSwig.i"
+%import "psLibSwig.i"
+
+EXPORT_TYPE(poisConfig);
+EXPORT_TYPE(poisStamp);
+EXPORT_TYPE(poisKernelBasis);
+NEWOBJECT(poisKernelBasisFunctions);
+NEWOBJECT(poisMakeMask);
+NEWOBJECT(poisFindStamps);
+NEWOBJECT(poisExtractKernel);
+NEWOBJECT(poisConvolveImage);
+NEWOBJECT(poisCalculateDeviations);
+NEWOBJECT(poisSolveEquation);
+%newobject poisRejectStamps;
+NEWOBJECT(poisImageSetVal);
+NEWOBJECT(poisImageSetValInMask);
+
+TYPE_INOUT(psArray *, stampsIO);
+TYPE_INOUT(psImage *, IO);
+TYPE_INOUT(psVector *, solutionIO)
+%include "pois.h"
+CLEAR_TYPE_INOUT(psArray *, stampsIO);
+CLEAR_TYPE_INOUT(psImage *, IO);
+CLEAR_TYPE_INOUT(psVector *, solutionIO)
Index: /tags/rel-0_0_2/pois/swig/psLibForSwig.c
===================================================================
--- /tags/rel-0_0_2/pois/swig/psLibForSwig.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/psLibForSwig.c	(revision 7503)
@@ -0,0 +1,41 @@
+/*
+ * Wrappers for psLib
+ */
+#include <stdio.h>
+#include "pslib.h"
+
+void
+psErrorStackPrintForSwig(void)
+{
+    psErrorStackPrint(stdout, "");
+}
+
+const psErr *psErrorGetForSwig(int i)
+{
+    const psErr *err = psErrorGet(i);
+
+    return (err->code == PS_ERR_NONE ? NULL : err);
+}
+
+/************************************************************************************************************/
+
+#if defined(psTrace)
+#   undef psTrace
+#endif
+
+void psTrace(const char *comp,		// component being traced
+	     int level,			// desired trace level
+	     char *str)			// output string
+{
+    p_psTrace(comp, level, str);
+}
+
+/************************************************************************************************************/
+/*
+ * SWIG doesn't like varargs functions; so provide non-varargs versions of psMetadataAdd
+ */
+bool psMetadataAddBool(psMetadata* restrict md, int where, const char *name, const char *comment, int val)
+{
+    return psMetadataAdd(md, where, name, PS_TYPE_BOOL, comment, val);
+}
+
Index: /tags/rel-0_0_2/pois/swig/psLibForSwig.h
===================================================================
--- /tags/rel-0_0_2/pois/swig/psLibForSwig.h	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/psLibForSwig.h	(revision 7503)
@@ -0,0 +1,14 @@
+#if !defined(PS_LIB_FOR_SWIG_H)
+#define PS_LIB_FOR_SWIG_H
+
+void psErrorStackPrintForSwig(void);
+const psErr *psErrorGetForSwig(int i);
+
+#if defined(psTrace)
+#   undef psTrace
+#endif
+void psTrace(const char *comp, int level, char *str);
+
+bool psMetadataAddBool(psMetadata* restrict md, int where, const char *name, const char *comment, int val);
+
+#endif
Index: /tags/rel-0_0_2/pois/swig/psLibSwig.i
===================================================================
--- /tags/rel-0_0_2/pois/swig/psLibSwig.i	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/psLibSwig.i	(revision 7503)
@@ -0,0 +1,352 @@
+// -*- C -*-
+%module psLibSwig
+
+%include "p_psSwig.i"
+
+%{
+#  include "pslib.h"
+#if 0
+#  include "Private/p_psMemory.h"	/* not available from prototype psLib include files (in src dir) */
+#else
+#  define P_PS_MEMMAGIC (void *)0xdeadbeef // Magic number in psMemBlock header
+#endif
+#  include "psLibForSwig.h"
+%}
+
+/************************************************************************************************************/
+/*
+ * Typemaps
+ */
+%typemap(in) FILE * {
+#if defined(SWIGPERL)
+   if (!SvOK($input)) {
+      $1 = NULL;      
+   } else {
+      $1 = PerlIO_findFILE(IoIFP(sv_2io($input)));
+   }
+#elif defined(SWIGPYTHON)
+    if ($input == Py_None) {
+	$1 = NULL;
+    } else if (!PyFile_Check($input)) {
+	PyErr_SetString(PyExc_TypeError, "Need a file!");
+	goto fail;
+    } else {
+	$1 = PyFile_AsFile($input);
+    }
+#endif
+}
+
+/************************************************************************************************************/
+
+%typemap(in) psMemBlock ***arr (psMemBlock **arr) {
+#if defined(SWIGPERL)
+   if (!SvOK($input)) {
+      $1 = NULL;
+   } else {
+      arr = NULL;
+      $1 = &arr;
+   }
+
+#elif defined(SWIGPYTHON)
+    if ($input == Py_None) {
+	$1 = NULL;
+    } else {
+	if (!PyList_Check($input)) {
+	    SWIG_exception(SWIG_RuntimeError, "\"$1_name\" isn't a list");
+	}
+	
+	arr = NULL;
+	$1 = &arr;
+    }
+#endif
+}
+
+%typemap(in) psMetadata ** (psMetadata *) {
+    if
+#if defined(SWIGPYTHON)
+       ($input == Py_None)
+#else
+       ($input == NULL)
+#endif
+    {
+	$1 = NULL;
+    } else {
+        static $*1_type arr[1];
+        if ((SWIG_ConvertPtr($input, (void **) &$1, $*1_descriptor, 1)) == -1) return NULL;
+	arr[0] = ($*1_type)$1;
+	$1 = arr;
+    }
+}
+
+/*
+ * Return a (psMemBlock ***) as a list of tuples; they are _appended_ to the pre-existing list. E.g.
+ *
+ *     arr = []; IPP.psMemCheckLeaks(0, arr, None, False)
+ *     for el in arr:
+ *         print el
+ */
+%typemap(argout) psMemBlock ***arr {
+    if ($1 != NULL) {
+	psMemBlock **arr = *$1;
+	
+	for (int i = 0; i < result; i++) {
+	    const psMemBlock *m = arr[i];
+#if defined(SWIGPERL)
+	    SV *sv = newSVpvf("%d:%ld:%s:%ld:%d:%d:%ld",
+			      (m->startblock == P_PS_MEMMAGIC) ? 1 : 0, (long)m->id,
+			      m->file, (long)m->lineno, (int)m->refCounter,
+			      (m->endblock == P_PS_MEMMAGIC) ? 1 : 0, (long)(m + 1));
+
+	    if (SvROK($input)) {
+	       AV *av = (AV *)SvRV($input);
+	       if (SvTYPE(av) != SVt_PVAV) {
+		  SWIG_croak("Type error in argument $argnum of $symname; Expected an array of psMemBlock");
+	       }
+
+	       av_push(av, sv);
+	    } else {
+	       SWIG_croak("Type error in argument $argnum of $symname; Expected a reference to an array of psMemBlock");
+	    }
+#elif defined(SWIGPYTHON)
+	    const int nel = 7;
+	    PyObject *ty = PyTuple_New(nel);
+	    int j = 0;
+	    PyTuple_SET_ITEM(ty, j++, PyInt_FromLong(m->startblock == P_PS_MEMMAGIC));
+	    PyTuple_SET_ITEM(ty, j++, PyInt_FromLong(m->id));
+	    PyTuple_SET_ITEM(ty, j++, PyString_FromString(m->file));
+	    PyTuple_SET_ITEM(ty, j++, PyInt_FromLong(m->lineno));
+	    PyTuple_SET_ITEM(ty, j++, PyInt_FromLong(m->refCounter));
+	    PyTuple_SET_ITEM(ty, j++, PyInt_FromLong(m->endblock == P_PS_MEMMAGIC));
+	    PyTuple_SET_ITEM(ty, j++, PyInt_FromLong((long)(m + 1)));
+	    assert (j == nel);
+	    
+	    PyList_Append($input, ty);
+#endif
+	}
+	
+	psFree(arr);
+    }
+}
+
+/************************************************************************************************************/
+%{
+typedef struct { float re, im; } swig_psC32;  ///< 32-bit complex floating point
+typedef struct { double re, im; } swig_psC64; ///< 64-bit complex floating point
+%}
+
+#if 0
+%include <stdint.h>			// SWIG has trouble with nested typedefs in this file
+					// at least on Darwin machines
+#else
+typedef unsigned char  uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int   uint32_t;
+typedef unsigned long  uint64_t;
+typedef char           int8_t;
+typedef short          int16_t;
+typedef int            int32_t;
+typedef long           int64_t;
+#endif
+
+typedef int psBool;
+typedef void *psPtr;
+typedef uint8_t psU8;                  ///< 8-bit unsigned int
+typedef uint16_t psU16;                ///< 16-bit unsigned int
+typedef uint32_t psU32;                ///< 32-bit unsigned int
+typedef uint64_t psU64;                ///< 64-bit unsigned int
+typedef int8_t psS8;                   ///< 8-bit signed int
+typedef int16_t psS16;                 ///< 16-bit signed int
+typedef int32_t psS32;                 ///< 32-bit signed int
+typedef int64_t psS64;                 ///< 64-bit signed int
+typedef float psF32;                   ///< 32-bit floating point
+typedef double psF64;                  ///< 64-bit floating point
+typedef struct { float re, im; } swig_psC32;  ///< 32-bit complex floating point
+typedef struct { double re, im; } swig_psC64; ///< 64-bit complex floating point
+
+%typemap(in) psC32 = float;
+%typemap(in) psC64 = double;
+
+typedef enum {
+    PS_TYPE_S8 = 0x0101,               ///< Character.
+    PS_TYPE_S16 = 0x0102,              ///< Short integer.
+    PS_TYPE_S32 = 0x0104,              ///< Integer.
+    PS_TYPE_S64 = 0x0108,              ///< Long integer.
+    PS_TYPE_U8 = 0x0301,               ///< Unsigned character.
+    PS_TYPE_U16 = 0x0302,              ///< Unsigned short integer.
+    PS_TYPE_U32 = 0x0304,              ///< Unsigned integer.
+    PS_TYPE_U64 = 0x0308,              ///< Unsigned long integer.
+    PS_TYPE_F32 = 0x0404,              ///< Single-precision Floating point.
+    PS_TYPE_F64 = 0x0408,              ///< Double-precision floating point.
+    PS_TYPE_C32 = 0x0808,              ///< Complex numbers consisting of single-precision floating point.
+    PS_TYPE_C64 = 0x0810              ///< Complex numbers consisting of double-precision floating point.
+} psElemType;
+
+typedef enum {
+    PS_DIMEN_SCALAR,            ///< Scalar.
+    PS_DIMEN_VECTOR,            ///< Vector.
+    PS_DIMEN_TRANSV,            ///< Transposed vector.
+    PS_DIMEN_IMAGE,             ///< Image.
+    PS_DIMEN_OTHER              ///< Something else that's not supported for arithmetic.
+} psDimen;
+
+typedef struct {
+    psElemType type;            ///< Primitive type.
+    psDimen dimen;              ///< Dimensionality.
+}
+psType;
+
+%include "psErrorCodes.h"
+%include "psLogMsg.h"
+%include "psMemory.h"
+%ignore psTraceFree;
+%include "psTrace.h"
+
+EXPORT_TYPE(psList);
+%include "psList.h"
+
+EXPORT_TYPE(psImage);
+EXPORT_TYPE(psRegion);
+%typemap(memberin) psImage * {
+   psFree($1);
+   $1 = $input;
+#if defined(SWIGPERL)
+   p_psMemIncrRefCounter($1, "memberin(psImage *)", 0);
+#endif
+}
+NEWOBJECT(psImageReadSection);
+NEWOBJECT(psImageSubset);
+NEWOBJECT(psImageSubsection);
+NEWOBJECT(psImageCopy);
+NOTNULL(psImageTrim);
+%include "psImage.h"
+%include "psImageIO.h"
+%include "psImageExtraction.h"
+
+EXPORT_TYPE(psMetadata)
+%typemap(memberin) psMetadata * {
+   psFree($1);
+   $1 = $input;
+#if defined(SWIGPERL)
+   p_psMemIncrRefCounter($1, "memberin(psMetadata *)", 0);
+#endif
+}
+
+%include "psMetadata.h"
+
+%apply int *OUTPUT { psU32 *nFail }
+TYPE_INOUT(psMetadata *, md);
+NEWOBJECT(psMetadataParseConfig);
+NOTNULL(psMetadataLookup);
+%include "psMetadataIO.h"
+CLEAR_TYPE_INOUT(psMetadata *, md);
+
+%extend psMetadataItem {
+    const char *get_STR(void) {
+       if (self->type != PS_META_STR) {
+	  return NULL;
+       } else {
+	  return self->data.V;
+       }
+    }
+}
+
+%include "psLibForSwig.h"
+
+EXPORT_TYPE(psHash);
+%include "psHash.h"
+
+EXPORT_TYPE(psErr);
+NEWOBJECT(psErrorGet);
+NEWOBJECT(psErrorLast);
+%include "psError.h"
+
+EXPORT_TYPE(psArray);
+%extend psArray {
+    const void *get_data(int i) {
+	return self->data[i];
+    }
+
+    const psCell *get_cell(int i) {
+        return (psCell *)(self->data[i]);
+    }
+    const psChip *get_chip(int i) {
+        return (psChip *)(self->data[i]);
+    }
+    const psReadout *get_readout(int i) {
+        return (psReadout *)(self->data[i]);
+    }
+}
+%include "psArray.h"
+
+// N.b. assumes e.g. %array_class(int, intArray);
+%define GET_CARRAY(structType, ctype, typeName)
+   %extend structType {
+      ctype ## Array *get_data_ ## typeName(void) {
+	 if (self->type.type == PS_TYPE_ ## typeName) {
+	    return self->data.typeName;
+	 } else {
+	    psError(PS_ERR_BAD_PARAMETER_TYPE, 1, "psVector is of type %d, not PS_TYPE_" "typeName",
+		    self->type.type);
+	    return NULL;
+	 }
+      }
+   }
+%enddef
+
+EXPORT_TYPE(psVector);
+%include "psVector.h"
+GET_CARRAY(psVector, float,  F32);
+GET_CARRAY(psVector, double, F64);
+   
+EXPORT_TYPE(psObservatory);
+EXPORT_TYPE(psFPA);
+EXPORT_TYPE(psChip);
+EXPORT_TYPE(psCell);
+EXPORT_TYPE(psReadout);
+EXPORT_TYPE(psExposure);
+%include "psAstrometry.h"
+
+%extend psFPA {
+    void set_chip(int i, psChip *chip) {
+        self->chips->data[i] = chip;
+	p_psMemIncrRefCounter(chip, "set_chip", 0);
+    }
+}
+%extend psChip {
+    void set_cell(int i, psCell *cell) {
+        self->cells->data[i] = cell;
+	p_psMemIncrRefCounter(cell, "set_cell", 0);	
+    }
+}
+%extend psCell {
+   char *extname;
+    void set_readout(int i, psReadout *readout) {
+        self->readouts->data[i] = readout;
+	p_psMemIncrRefCounter(readout, "set_readout", 0);	
+    }
+}
+%{
+   char *psCell_extname_get(psCell *cell) { return NULL; }
+   void psCell_extname_set(psCell *cell, char *val) { return; }
+%}
+
+NEWOBJECT(psBinaryOp);
+TYPE_INOUT(void *, out);
+psImage *psBinaryOp(void *out, void *in1, char *op, void *in2);
+CLEAR_TYPE_INOUT(void *, out);
+%include "psScalar.h"
+
+EXPORT_TYPE(psTime);
+NEWOBJECT(psTimeGetTime);
+%include "psTime.h"
+
+EXPORT_TYPE(psFits);
+NEWOBJECT(psFitsReadImage);
+TYPE_INOUT(psImage *, out);
+%include "psFits.h"
+CLEAR_TYPE_INOUT(psImage *, out);
+
+EXPORT_TYPE(psStats);
+EXPORT_TYPE(psHistogram);
+%include "psStats.h"
+%include "psImageStats.h"
Index: /tags/rel-0_0_2/pois/swig/simpleFits.c
===================================================================
--- /tags/rel-0_0_2/pois/swig/simpleFits.c	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/simpleFits.c	(revision 7503)
@@ -0,0 +1,678 @@
+/*
+ * Write a fits image to a file descriptor; useful for talking to DS9
+ *
+ * This version knows about psLib data structures
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "pslib.h"
+#include "poisErrorCodes.h"
+
+#include "simpleFits.h"
+   
+#define FITS_SIZE 2880
+
+typedef struct {
+    char keyword[9];
+    enum {
+	UNKNOWN_CARD = -1,
+	LOGICAL_CARD,
+	STRING_CARD,
+	INTEGER_CARD,
+	DOUBLE_CARD,
+	FLOAT_CARD,
+    } type;
+    union {
+	char *l;
+	char *s;
+	int i;
+	double d;
+	float f;      
+    } val;
+    char *commnt;
+} CARD;
+      
+
+/*
+ * Write a string value
+ */
+static int
+write_card_s(int fd,
+	     int ncard,
+	     char *record,
+	     const char *keyword,
+	     const char *val,
+	     const char *commnt)
+{
+    char *card = &record[80*ncard];
+    char value[20];			/* blank-padded val, if needed */
+
+    if(strlen(val) < 8) {		/* FITS requires at least 8 chars */
+	sprintf(value, "%-8s", val);
+	val = value;
+    }
+
+    if(*keyword == '\0' ||
+       !strcmp(keyword,"COMMENT") || !strcmp(keyword,"END") || !strcmp(keyword,"HISTORY")) {
+	sprintf(card,"%-8.8s%-72s",keyword,val);
+    } else {
+	sprintf(card,"%-8.8s= '%s' %c%-*s",keyword,val,
+		(commnt[0] == '\0' ? ' ' : '/'),
+		(int)(80 - 14 - strlen(val)), commnt);
+    }
+/*
+ * Write record if full
+ */
+    if(++ncard == 36) {
+	if(write(fd, record, FITS_SIZE) != FITS_SIZE) {
+	    psError(POIS_ERR_FITS, true,"Cannot write header record\n");
+	}
+	ncard = 0;
+    }
+   
+    return ncard;
+}   
+
+/*****************************************************************************/
+/*
+ * Write an integer (%d)
+ */
+static int
+write_card_i(int fd,
+	     int ncard,
+	     char *record,
+	     const char *keyword,
+	     const int val,
+	     const char *commnt)
+{
+    char *card = &record[80*ncard];
+
+    sprintf(card,"%-8.8s= %20d %c%-48s",keyword,val,
+	    (commnt[0] == '\0' ? ' ' : '/'),commnt);
+/*
+ * Write record if full
+ */
+    if(++ncard == 36) {
+	if(write(fd, record, FITS_SIZE) != FITS_SIZE) {
+	    psError(POIS_ERR_FITS, true,"Cannot write header record\n");
+	}
+	ncard = 0;
+    }
+   
+    return ncard;
+}   
+
+/*****************************************************************************/
+/*
+ * Write a logical value
+ */
+static int
+write_card_l(int fd,
+	     int ncard,
+	     char *record,
+	     const char *keyword,
+	     const char *val,
+	     const char *commnt)
+{
+    char *card = &record[80*ncard];
+
+    if(strcmp(val,"T") != 0 && strcmp(val,"F") != 0) {
+	psError(POIS_ERR_FITS, true,"Invalid logical %s for keyword %s\n",val,keyword);
+	val = "?";
+    }
+    sprintf(card,"%-8.8s= %20s %c%-48s",keyword,val,
+	    (commnt[0] == '\0' ? ' ' : '/'),commnt);
+/*
+ * Write record if full
+ */
+    if(++ncard == 36) {
+	if(write(fd, record, FITS_SIZE) != FITS_SIZE) {
+	    psError(POIS_ERR_FITS, true,"Cannot write header record\n");
+	}
+	ncard = 0;
+    }
+   
+    return ncard;
+}   
+
+/*****************************************************************************/
+/*
+ * Write a double floating value
+ */
+static int
+write_card_d(int fd,
+	     int ncard,
+	     char *record,
+	     const char *keyword,
+	     const double val,
+	     const char *commnt)
+{
+    char *card = &record[80*ncard];
+
+    sprintf(card,"%-8.8s= %20g %c%-48s",keyword,val,
+	    (commnt[0] == '\0' ? ' ' : '/'),commnt);
+/*
+ * Write record if full
+ */
+    if(++ncard == 36) {
+	if(write(fd, record, FITS_SIZE) != FITS_SIZE) {
+	    psError(POIS_ERR_FITS, true,"Cannot write header record\n");
+	}
+	ncard = 0;
+    }
+   
+    return ncard;
+}   
+
+/*****************************************************************************/
+/*
+ * Write a floating value
+ */
+static int
+write_card_f(int fd,
+	     int ncard,
+	     char *record,
+	     const char *keyword,
+	     const float val,
+	     const char *commnt)
+{
+    char *card = &record[80*ncard];
+
+    sprintf(card,"%-8.8s= %20g %c%-48s",keyword,val,
+	    (commnt[0] == '\0' ? ' ' : '/'),commnt);
+/*
+ * Write record if full
+ */
+    if(++ncard == 36) {
+	if(write(fd, record, FITS_SIZE) != FITS_SIZE) {
+	    psError(POIS_ERR_FITS, true,"Cannot write header record\n");
+	}
+	ncard = 0;
+    }
+   
+    return ncard;
+}   
+
+/*****************************************************************************/
+/*
+ * Write a CARD
+ */
+static int
+write_card(int fd,
+	   int ncard,
+	   char *record,
+	   const CARD *card)
+{
+    switch (card->type) {
+      case UNKNOWN_CARD:		// we should already have complained
+	return ncard;
+      case LOGICAL_CARD:
+	return write_card_l(fd, ncard, record, card->keyword, card->val.l, card->commnt);
+      case STRING_CARD:
+	return write_card_s(fd, ncard, record, card->keyword, card->val.s, card->commnt);
+      case INTEGER_CARD:
+	return write_card_i(fd, ncard, record, card->keyword, card->val.i, card->commnt);
+      case DOUBLE_CARD:
+	return write_card_d(fd, ncard, record, card->keyword, card->val.d, card->commnt);
+      case FLOAT_CARD:
+	return write_card_f(fd, ncard, record, card->keyword, card->val.f, card->commnt);
+    }
+    printf("Impossible type for card %s, %d\n", card->keyword, card->type);
+    abort();
+    return ncard;			/* NOTREACHED */
+}
+
+/*****************************************************************************/
+/*
+ * Byte swap ABABAB -> BABABAB in place
+ */
+static void swap_2(char *arr,		// array to swap
+		   int n)		// number of bytes
+{
+    char *end,
+	t;
+
+    if(n%2 != 0) {
+	psError(POIS_ERR_FITS, true,"Attempt to byte swap odd number of bytes: %d\n",n);
+	n = 2*(int)(n/2);
+    }
+
+    for(end = arr + n;arr < end;arr += 2) {
+	t = arr[0];
+	arr[0] = arr[1];
+	arr[1] = t;
+    }
+}
+
+/*
+ * Byte swap ABCDABCD -> DCBADCBA in place (e.g. sun <--> vax)
+ */
+static void swap_4(char *arr,		// array to swap
+		   int n)		// number of bytes
+{
+    char *end,
+	t;
+
+    if(n%4 != 0) {
+	psError(POIS_ERR_FITS, true,"Attempt to byte swap non-multiple of 4 bytes: %d\n",n);
+	n = 4*(int)(n/4);
+    }
+
+    for(end = arr + n;arr < end;arr += 4) {
+	t = arr[0];
+	arr[0] = arr[3];
+	arr[3] = t;
+	t = arr[1];
+	arr[1] = arr[2];
+	arr[2] = t;
+    }
+}
+
+/*
+ * Byte swap ABCDEFGH -> HGFEDCBA in place (e.g. sun <--> vax)
+ */
+static void swap_8(char *arr,		// array to swap
+		   int n)		// number of bytes
+{
+    char *end,
+	t;
+
+    if(n%8 != 0) {
+	psError(POIS_ERR_FITS, true,"Attempt to byte swap non-multiple of 8 bytes: %d\n",n);
+	n = 8*(int)(n/8);
+    }
+
+    for(end = arr + n;arr < end;arr += 8) {
+	t = arr[0];
+	arr[0] = arr[7];
+	arr[7] = t;
+	t = arr[1];
+	arr[1] = arr[6];
+	arr[6] = t;
+	t = arr[2];
+	arr[2] = arr[5];
+	arr[5] = t;
+	t = arr[3];
+	arr[3] = arr[4];
+	arr[4] = t;
+    }
+}
+
+/*****************************************************************************/
+
+static int
+write_fits_hdr(int fd,
+	       int bitpix,
+	       int naxis,
+	       int *naxes,
+	       CARD **cards,		/* extra header cards */
+	       const psMetadata *metaData, /* metadata to write; or NULL */
+	       int primary)		/* is this the primary HDU? */
+{
+    int i;
+    int ncard;				/* number of cards written */
+    char record[FITS_SIZE + 1];		/* write buffer */
+   
+    ncard = 0;
+    if(primary) {
+	ncard = write_card_l(fd,ncard,record,"SIMPLE","T","");
+    } else {
+	ncard = write_card_s(fd,ncard,record,"XTENSION","IMAGE","");
+    }
+    ncard = write_card_i(fd,ncard,record,"BITPIX",bitpix,"");
+    ncard = write_card_i(fd,ncard,record,"NAXIS",naxis,"");
+    for(i = 0; i < naxis; i++) {
+	char key[] = "NAXIS.";
+	sprintf(key, "NAXIS%d", i + 1);
+	ncard = write_card_i(fd,ncard,record,key,naxes[i],"");
+    }
+    if(primary) {
+	ncard = write_card_l(fd,ncard,record,
+			     "EXTEND","T","There may be extensions");
+    }
+/*
+ * Write extra header cards
+ */
+    if(cards != NULL) {
+	while(*cards != NULL) {
+	    ncard = write_card(fd,ncard,record,*cards);
+	    cards++;
+	}
+    }
+    /*
+     * Is there metadata to write?
+     */
+    if (metaData != NULL) {
+	CARD card;			    // used to write metadata to
+	card.keyword[8] = '\0';		    // we\'ll be using strncpy, so need to guarantee NUL termination
+	psMetadataItem *meta = NULL;	    // used to traverse a set of metadata
+	int new_error = 1;		    // is this a new error?
+
+
+	psListIterator *iter = psListIteratorAlloc(metaData->list, PS_LIST_HEAD, false); 
+	while ((meta = psListGetAndIncrement(iter)) != NULL) {
+	    strncpy(card.keyword, meta->name, 8);
+	    card.commnt = meta->comment;
+	    switch (meta->type) {
+	      case PS_META_F64:
+		card.type = DOUBLE_CARD;
+		card.val.d = meta->data.F64;
+		break;
+	      case PS_META_F32:
+		card.type = FLOAT_CARD;
+		card.val.f = meta->data.F32;
+		break;
+	      case PS_META_S32:
+		card.type = INTEGER_CARD;
+		card.val.i = meta->data.S32;
+		break;
+	      case PS_META_STR:
+		card.type = STRING_CARD;
+		card.val.s = meta->data.V;
+		break;
+	      default:
+		card.type = UNKNOWN_CARD;
+		psError(POIS_ERR_FITS, new_error,
+			"I don't know how to write metadata of type %d", meta->type);
+		new_error = 0;
+		break;
+	    }
+	    if (card.type != UNKNOWN_CARD) {
+		ncard = write_card(fd, ncard, record, &card);
+	    }
+	}
+	psFree(iter);
+    }
+
+    ncard = write_card_s(fd,ncard,record,"END","","");
+    while(ncard != 0) {
+	ncard = write_card_s(fd,ncard,record,"","","");
+    }
+
+    return 0;
+}
+
+/*
+ * Pad out to a FITS record boundary
+ */
+static void pad_to_fits_record(int fd,		// output file descriptor
+			       int npixel,	// number of pixels already written to HDU
+			       int bitpix)	// bitpix for this datatype
+{
+    const int bytes_per_pixel = (bitpix > 0 ? bitpix : -bitpix)/8;
+    int nbyte = npixel*bytes_per_pixel;
+    
+    if(nbyte%FITS_SIZE != 0) {
+	char record[FITS_SIZE + 1];	/* write buffer */
+	
+	nbyte = FITS_SIZE - nbyte%FITS_SIZE;
+	memset(record, ' ', nbyte);
+	if(write(fd, record, nbyte) != nbyte) {
+	    psError(POIS_ERR_FITS, true, "error padding file to multiple of fits block size\n");
+	}	       
+    }
+}
+
+static int write_fits_data(int fd,
+			   int bitpix,
+			   int npix,
+			   char *data,
+			   int pad_record) // Pad data out to a FITS record boundary?
+{
+    char *buff = NULL;			/* I/O buffer */
+    const int bytes_per_pixel = (bitpix > 0 ? bitpix : -bitpix)/8;
+    int nbyte = npix*bytes_per_pixel;
+    int swap_bytes = 0;			/* the default */
+    static int warned = 0;		/* Did we warn about BZERO/BSCALE? */
+
+#if PS_BIGENDIAN == 0			/* we'll need to byte swap FITS */
+    if(bytes_per_pixel > 1) {
+	swap_bytes = 1;
+    }
+#endif
+
+    if(swap_bytes) {
+	if((buff = psAlloc(FITS_SIZE*bytes_per_pixel)) == NULL) {
+	    psError(POIS_ERR_FITS, true,"Can't allocate storage for buff\n");
+	    return -1;
+	}
+    }
+    
+    if(bytes_per_pixel == 2 && !warned) {
+	warned = 1;
+	fprintf(stderr,"Worry about BZERO/BSCALE\n");
+    }
+   
+    while(nbyte > 0) {
+	int nwrite = (nbyte > FITS_SIZE) ? FITS_SIZE : nbyte;
+      
+	if(swap_bytes) {
+	    memcpy(buff, data, nwrite);
+	    if(bytes_per_pixel == 2) {
+		swap_2((char *)buff, nwrite);
+	    } else if(bytes_per_pixel == 4) {
+		swap_4((char *)buff, nwrite);
+	    } else if(bytes_per_pixel == 8) {
+	        swap_8((char *)buff, nwrite);
+	    } else {
+		fprintf(stderr,"You cannot get here\n");
+		abort();
+	    }
+	} else {
+	    buff = data;
+	}
+      
+	if(write(fd, buff, nwrite) != nwrite) {
+	    perror("Error writing image: ");
+	    break;
+	}
+
+	nbyte -= nwrite;
+	data += nwrite;
+    }
+    if (nbyte == 0) {			/* no write errors */
+	if (pad_record) {
+	    pad_to_fits_record(fd, npix, bitpix);
+	}
+    }
+   
+    if(swap_bytes) {
+	psFree(buff);
+    }
+
+    return (nbyte == 0 ? 0 : -1);
+}
+
+int
+rhlWriteFits(int fd,			/* file descriptor to write to */
+	     const psImage *data,	/* The data to write */
+	     const psMetadata *meta,	/* associated metadata */
+	     const char *WCS)		/* name of WCS for pixel coordinates */
+{
+    CARD **cards = NULL;		/* extra header info */
+    int CRVAL1 = 0, CRVAL2 = 0;		/* card indices for keywords */
+    int i;
+    const int naxis = 2;		// == NAXIS
+    int naxes[2];			/* values of NAXIS1 etc */
+    int ncard = 12;			/* number of extra header cards */
+
+    psErrorClear();			// clear error stack
+    /*
+     * Does the data have a WCS? If so, call our pixel-based WCS "A"
+     */
+    if (WCS != NULL && *WCS == '\0') {
+	WCS = NULL;
+    }
+    if (WCS == NULL &&
+	meta != NULL && psMetadataLookup((psMetadata *)meta, "CTYPE1") != NULL) {
+	WCS = "A";
+    } else {
+	psErrorClear();			// set by psMetadataLookup XXX
+	WCS = "";
+    }
+    /*
+     * What sort if image is it?
+     */
+    int bitpix;				// == BITPIX
+    char **rows = NULL;
+    switch (data->type.type) {
+      case PS_TYPE_F64:
+	bitpix = -64;
+	rows = (char **)data->data.F64;
+	break;
+      case PS_TYPE_F32:
+	bitpix = -32;
+	rows = (char **)data->data.F32;
+	break;
+      case PS_TYPE_U16:
+	bitpix = -16;
+	rows = (char **)data->data.U16;
+	break;
+      case PS_TYPE_S8:
+	bitpix = 8;
+	rows = (char **)data->data.S8;
+	break;
+      case PS_TYPE_U8:
+	bitpix = 8;
+	rows = (char **)data->data.U8;
+	break;
+      default:
+	bitpix = 0;			// make gcc happy
+	psAbort(__func__, "Unsupported image data type: %d", data->type.type);
+	break;				// NOTREACHED
+    }
+    assert (rows != NULL);
+    
+    /*
+     * Allocate cards for FITS headers
+     */
+    if (WCS != NULL) {
+       cards = psAlloc((ncard + 1)*sizeof(CARD *));
+       assert(cards != NULL);
+       
+       cards[0] = psAlloc((ncard + 1)*sizeof(CARD));
+       assert(cards[0] != NULL);
+       
+       for(i = 0; i < ncard; i++) {
+	  cards[i] = cards[0] + i;
+       }
+       cards[i] = NULL;
+       /*
+	* Generate cards for WCS, so that pixel (0,0) is correctly labelled
+	*/
+       i = 0;
+       sprintf(cards[i]->keyword, "CRVAL1%s", WCS); CRVAL1 = i;
+       cards[i]->type = INTEGER_CARD;
+       cards[i]->val.i = 0;
+       cards[i]->commnt = "(output) Column pixel of Reference Pixel";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CRVAL2%s", WCS); CRVAL2 = i;
+       cards[i]->type = INTEGER_CARD;
+       cards[i]->val.i = 0;
+       cards[i]->commnt = "(output) Row pixel of Reference Pixel";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CRPIX1%s", WCS); CRVAL1 = i;
+       cards[i]->type = INTEGER_CARD;
+       cards[i]->val.i = 1;
+       cards[i]->commnt = "Column Pixel Coordinate of Reference";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CRPIX2%s", WCS); CRVAL1 = i;
+       cards[i]->type = INTEGER_CARD;
+       cards[i]->val.i = 1;
+       cards[i]->commnt = "Row Pixel Coordinate of Reference";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CTYPE1%s", WCS); CRVAL1 = i;
+       cards[i]->type = STRING_CARD;
+       cards[i]->val.s = "LINEAR";
+       cards[i]->commnt = "Type of projection";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CTYPE1%s", WCS); CRVAL1 = i;
+       cards[i]->type = STRING_CARD;
+       cards[i]->val.s = "LINEAR";
+       cards[i]->commnt = "Type of projection";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CUNIT1%s", WCS); CRVAL1 = i;
+       cards[i]->type = STRING_CARD;
+       cards[i]->val.s = "PIXEL";
+       cards[i]->commnt = "Column unit";
+       
+       i++;
+       sprintf(cards[i]->keyword, "CUNIT2%s", WCS); CRVAL1 = i;
+       cards[i]->type = STRING_CARD;
+       cards[i]->val.s = "PIXEL";
+       cards[i]->commnt = "Row unit";
+      
+       cards[++i] = NULL;
+       assert(i <= ncard);
+    }
+
+    naxes[0] = data->numCols;
+    naxes[1] = data->numRows;
+    
+    write_fits_hdr(fd, bitpix, naxis, naxes, cards, meta, 1);
+    for (int r = 0; r < data->numRows; r++) {
+	if(write_fits_data(fd, bitpix, data->numCols, rows[r], ((r==data->numRows - 1) ? 1 : 0)) < 0) {
+	    psError(POIS_ERR_UNKNOWN, 1, "Error writing data for row %d", r);
+	    break;
+	}
+    }
+
+    if (cards != NULL) {
+       psFree(cards[0]);
+       psFree(cards);
+    }
+
+    psErr *err = psErrorLast();
+    int code = err->code == PS_ERR_NONE ? 0 : -1;
+    psFree(err);
+    
+    return code;
+}   
+
+/************************************************************************************************************/
+
+int
+rhlWriteFitsFile(const char *filename,	/* file to write to (or "| cmd"); NULL => "|xpaset ds9 fits"*/
+		 const psImage *data,	/* The data to write */
+		 const psMetadata *meta, /* associated metadata */
+		 const char *WCS)	/* name of WCS for pixel coordinates */
+{
+    if (filename == NULL) {
+	filename =  "| xpaset ds9 fits"; // i.e. display on DS9
+    }
+
+    int fd;
+
+    if (filename[0] == '|') {		// a command
+	const char *cmd = filename + 1;
+	while (isspace(*cmd)) {
+	    cmd++;
+	}
+
+	fd = fileno(popen(cmd, "w"));
+    } else {
+	fd = creat(filename, 777);
+    }
+
+    if (fd < 0) {
+	psError(POIS_ERR_FITS, 1, "Cannot open \"%s\"", filename);
+	return -1;
+    }
+
+    int ret = rhlWriteFits(fd, data, meta, WCS);
+
+    (void)close(fd);
+
+    return ret;
+}
Index: /tags/rel-0_0_2/pois/swig/simpleFits.h
===================================================================
--- /tags/rel-0_0_2/pois/swig/simpleFits.h	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/simpleFits.h	(revision 7503)
@@ -0,0 +1,15 @@
+#if !defined(SIMPLE_FITS_H)
+#define SIMPLE_FITS_H 1
+
+int
+rhlWriteFits(int fd,			/* file descriptor to write to */
+	     const psImage *data,	/* The data to write */
+	     const psMetadata *meta,	/* associated metadata */
+	     const char *WCS);		/* which WCS to use for pixel coordinates */
+int
+rhlWriteFitsFile(const char *filename,	/* file to write to (or "| cmd"); NULL => "|xpaset ds9 fits" */
+		 const psImage *data,	/* The data to write */
+		 const psMetadata *meta, /* associated metadata */
+		 const char *WCS);	/* name of WCS for pixel coordinates */
+
+#endif
Index: /tags/rel-0_0_2/pois/swig/xpaSwig.i
===================================================================
--- /tags/rel-0_0_2/pois/swig/xpaSwig.i	(revision 7503)
+++ /tags/rel-0_0_2/pois/swig/xpaSwig.i	(revision 7503)
@@ -0,0 +1,148 @@
+// -*- C -*-
+/*
+ * This would be a very simple module, except that I didn't want to
+ * deal with (char **) in SWIG; so I wrote C wrappers to pass a single
+ * (char *) instead.
+ *
+ * I also include some utility routines used to write psImages to
+ * a file descriptor; these are used to display images on ds9 via xpa
+ */
+%module xpaSwig
+
+%include "p_psSwig.i"
+%import "psLibSwig.i"
+
+%rename(XPAGet) XPAGet1;
+%rename(XPASet) XPASet1;
+%rename(XPASetFd) XPASetFd1;
+
+%{
+#include "xpa.h"
+
+static char *
+xmalloc(long n)
+{
+    char *ptr = malloc(n);
+    assert(ptr != NULL);
+
+    return(ptr);
+}
+
+/*
+ * A binding for XPAGet that talks to only one server, but doesn't have to talk (char **) with SWIG
+ */
+char *
+XPAGet1(XPA xpa,
+	char *xtemplate,
+	char *paramlist,
+	char *mode)
+{
+    char *buf = NULL;			/* desired response */
+    int len = 0;			/* length of buf; ignored */
+    char *error = NULL;			/* returned error if any*/
+
+    int n = XPAGet(xpa, xtemplate, paramlist, mode,
+		   &buf, &len, NULL, &error, 1);
+
+    if(n == 0) {
+	return(NULL);
+    }
+    if(error != NULL) {
+	char *errStr = xmalloc(strlen(error) + 1);
+	strcpy(errStr, error);
+	return(errStr);
+    }
+
+    return(buf);
+}
+
+/*****************************************************************************/
+
+char *
+XPASet1(XPA xpa,
+	char *xtemplate,
+	char *paramlist,
+	char *mode,
+	char *buf,			// desired extra data
+	int len)			// length of buf (or -1)
+{
+    if(len < 0) {
+	len = strlen(buf);		// length of buf
+    }
+    char *error = NULL;			// returned error if any
+
+    int n = XPASet(xpa, xtemplate, paramlist, mode,
+		   buf, len, NULL, &error, 1);
+
+    if(n == 0) {
+	return(NULL);
+    }
+    if(error != NULL) {
+	char *errStr = xmalloc(strlen(error) + 1);
+	strcpy(errStr, error);
+	return(errStr);
+    }
+
+    return "";
+}
+
+
+/*****************************************************************************/
+
+char *
+XPASetFd1(XPA xpa,
+	  char *xtemplate,
+	  char *paramlist,
+	  char *mode,
+	  int fd)			/* file descriptor for xpa to read */
+{
+    char *error = NULL;			/* returned error if any*/
+
+    int n = XPASetFd(xpa, xtemplate, paramlist, mode,
+		     fd, NULL, &error, 1);
+
+    if(n == 0) {
+	return(NULL);
+    }
+    if(error != NULL) {
+	char *errStr = xmalloc(strlen(error) + 1);
+	strcpy(errStr, error);
+
+	return(errStr);
+    }
+
+    return NULL;
+}
+%}
+
+%rename(XPA_in) in;			// avoid conflict with python keyword in xpa.h
+
+%import "prsetup.h"
+%import "xpa.h"
+
+%include "exception.i"
+
+%exception {
+    $action
+    if (result == NULL) {
+       SWIG_exception(SWIG_IOError, "XPA returned NULL")
+    }
+}
+
+char *XPAGet1(XPA xpa, char *xtemplate, char *paramlist, char *mode);
+char *XPASet1(XPA xpa, char *xtemplate, char *paramlist, char *mode, char *buf, int len);	
+char *XPASetFd1(XPA xpa, char *xtemplate, char *paramlist, char *mode, int fd);
+
+/************************************************************************************************************/
+/*
+ * Talk to ds9
+ */
+%{
+#   include "pslib.h"
+#   include "simpleFits.h"
+%}
+
+NOTNEGATIVE(rhlWriteFits);
+NOTNEGATIVE(rhlWriteFitsFile);
+
+%include "simpleFits.h"
