Index: /branches/eam_branch_20080324/ippconfig/Makefile.am
===================================================================
--- /branches/eam_branch_20080324/ippconfig/Makefile.am	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/Makefile.am	(revision 17278)
@@ -0,0 +1,54 @@
+SUBDIRS = \
+	recipes \
+	isp \
+	gpc1 \
+	cfh12k \
+	ctio_mosaic2 \
+	megacam \
+	simple \
+	simmosaic \
+	simtest \
+	tc3 \
+	skycell \
+	sdss \
+	esowfi \
+	lbc_red
+
+install_files = \
+	site.config \
+	system.config \
+	GSCregions.tbl \
+	dvo.photcodes
+
+built_sources = \
+	ipprc.config.in \
+	dvo.site.in
+
+built_files = \
+	ipprc.config \
+	dvo.site
+
+installdir = $(datadir)/ippconfig
+
+install_DATA = $(install_files) $(built_files)
+
+install-data-hook:
+	chmod 0755 $(installdir) 
+
+EXTRA_DIST = $(install_files) $(built_sources)
+
+BUILT_SOURCES = $(built_files)
+
+#ACLOCAL_AMFLAGS = -I m4
+
+# From the autoconf manual, section 4.7.2 "Installation Directory Variables"
+edit = sed \
+	-e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \
+	-e 's|@prefix[@]|$(prefix)|g'
+
+ipprc.config dvo.site: Makefile
+	rm -f $@
+	$(edit) '$(srcdir)/$@.in' > $@
+
+ipprc.config: $(srcdir)/ipprc.config.in
+dvo.site: $(srcdir)/dvo.site.in
Index: /branches/eam_branch_20080324/ippconfig/cfh12k/filerules-mef.mdc
===================================================================
--- /branches/eam_branch_20080324/ippconfig/cfh12k/filerules-mef.mdc	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/cfh12k/filerules-mef.mdc	(revision 17278)
@@ -0,0 +1,191 @@
+### File rules for PHU=FPA, EXT=CHIP
+
+### Redirections
+PPIMAGE.OUTPUT        STR PPIMAGE.OUTPUT.MEF
+PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.MEF
+PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.MEF
+
+PPIMAGE.CHIP          STR PPIMAGE.CHIP.MEF
+PPIMAGE.CHIP.MASK     STR PPIMAGE.CHIP.MK.MEF
+PPIMAGE.CHIP.WEIGHT   STR PPIMAGE.CHIP.WT.MEF
+
+PPIMAGE.OUTPUT.FPA1   STR PPIMAGE.OUTPUT.FPA1.MEF
+PPIMAGE.OUTPUT.FPA2   STR PPIMAGE.OUTPUT.FPA2.MEF
+PPIMAGE.BIN1          STR PPIMAGE.BIN1.MEF
+PPIMAGE.BIN2          STR PPIMAGE.BIN2.MEF
+
+PPSTAMP.CHIP          STR PPSTAMP.CHIP.MEF
+
+PSASTRO.INPUT         STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT        STR PSASTRO.OUT.CMF.MEF
+PSASTRO.OUTPUT.MEF    STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT         STR PSPHOT.OUT.CMF.MEF
+
+DVOCORR.OUTPUT        STR DVOCORR.MEF.OUTPUT
+DVOFLAT.OUTPUT        STR DVOFLAT.MEF.OUTPUT
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE 
+
+## files used by ppImage
+PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPIMAGE.MASK       INPUT @DETDB        CHIP       MASK
+PPIMAGE.BIAS       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.DARK       INPUT @DETDB        CHIP       DARK
+PPIMAGE.FLAT       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE     
+PPIMAGE.SHUTTER    INPUT @DETDB        CHIP       IMAGE     
+
+## Files used by ppMerge
+PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE     
+DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER     
+DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE         
+DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE         
+
+## files used by psphot 
+PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE     
+PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE     
+PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD    INPUT @FILES        CHIP       PSF       
+PSPHOT.INPUT.CMF   INPUT @FILES        CHIP       CMF       
+
+## files used by psastro 
+PSASTRO.INPUT.CMP  INPUT @FILES        CHIP       CMP       
+PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF       
+
+## files used by pswarp
+PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE     
+PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+PSWARP.MASK        INPUT @FILES        CHIP       MASK
+PSWARP.SKYCELL     INPUT @FILES        CHIP       IMAGE     
+PSWARP.ASTROM      INPUT @FILES        CHIP       CMF       
+
+PPSUB.INPUT        INPUT @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK   INPUT @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT INPUT @FILES        FPA        WEIGHT
+PPSUB.REF          INPUT @FILES        FPA        IMAGE
+PPSUB.REF.MASK     INPUT @FILES        FPA        MASK
+PPSUB.REF.WEIGHT   INPUT @FILES        FPA        WEIGHT
+PPSUB.SOURCES      INPUT @FILES        FPA        CMF
+
+PPSTACK.INPUT      INPUT @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK INPUT @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA        WEIGHT
+PPSTACK.SOURCES    INPUT @FILES        FPA        CMF
+
+PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+PPARITH.INPUT.IMAGE INPUT @FILES       CHIP       IMAGE
+PPARITH.INPUT.MASK  INPUT @FILES       CHIP       MASK
+
+### output file definitions
+TYPE                     OUTPUT FILENAME.RULE                    FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT.MEF       OUTPUT {OUTPUT}.b0.fits                 IMAGE     COMP_IMG  CHIP       TRUE      MEF
+PPIMAGE.OUT.MK.MEF       OUTPUT {OUTPUT}.mk.fits                 MASK      COMP_MASK CHIP       TRUE      MEF
+PPIMAGE.OUT.WT.MEF       OUTPUT {OUTPUT}.wt.fits                 WEIGHT    COMP_WT   CHIP       TRUE      MEF
+PPIMAGE.OUTPUT.SPL       OUTPUT {OUTPUT}.{CHIP.NAME}.b0.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.MK.SPL       OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits     MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.WT.SPL       OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits     WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.CHIP.MEF         OUTPUT {OUTPUT}.ch.fits                 IMAGE     COMP_IMG  CHIP       TRUE      MEF
+PPIMAGE.CHIP.MK.MEF      OUTPUT {OUTPUT}.ch.mk.fits              MASK      COMP_MASK CHIP       TRUE      MEF
+PPIMAGE.CHIP.WT.MEF      OUTPUT {OUTPUT}.ch.wt.fits              WEIGHT    COMP_WT   CHIP       TRUE      MEF
+PPIMAGE.CHIP.SPL         OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.MK.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits  MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.WT.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits  WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.OUTPUT.FPA1.MEF  OUTPUT {OUTPUT}.b1.fits                 IMAGE     COMP_IMG  FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA2.MEF  OUTPUT {OUTPUT}.b2.fits                 IMAGE     COMP_IMG  FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA1.SPL  OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits     IMAGE     NONE      FPA        TRUE      SPLIT
+PPIMAGE.OUTPUT.FPA2.SPL  OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits     IMAGE     NONE      FPA        TRUE      SPLIT
+
+PPIMAGE.STATS            OUTPUT {OUTPUT}.stats                   STATS     NONE      FPA        TRUE      MEF
+
+PPIMAGE.BIN1.MEF         OUTPUT {OUTPUT}.b1c.fits                IMAGE     COMP_IMG  CHIP       TRUE      MEF
+PPIMAGE.BIN2.MEF         OUTPUT {OUTPUT}.b2c.fits                IMAGE     COMP_IMG  CHIP       TRUE      MEF
+PPIMAGE.BIN1.SPL         OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits     IMAGE     COMP_IMG  CHIP       TRUE      SPLIT
+PPIMAGE.BIN2.SPL         OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits     IMAGE     COMP_IMG  CHIP       TRUE      SPLIT
+
+PPIMAGE.JPEG1            OUTPUT {OUTPUT}.b1.jpg                  JPEG      NONE      FPA        TRUE      NONE
+PPIMAGE.JPEG2            OUTPUT {OUTPUT}.b2.jpg                  JPEG      NONE      FPA        TRUE      NONE
+
+PPMERGE.OUTPUT.MASK      OUTPUT {OUTPUT}.fits                    MASK      NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.BIAS      OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.DARK      OUTPUT {OUTPUT}.fits                    DARK      NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER   OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FLAT      OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FRINGE    OUTPUT {OUTPUT}.fits                    FRINGE    NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SIGMA     OUTPUT {OUTPUT}.sigma.fits              IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT     OUTPUT {OUTPUT}.count.fits              IMAGE     NONE      CHIP       TRUE      NONE
+
+DVOCORR.MEF.OUTPUT       OUTPUT {OUTPUT}.fc.fits              	 IMAGE     NONE      CHIP       TRUE      MEF
+DVOCORR.SPL.OUTPUT       OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits  	 IMAGE     NONE      CHIP       TRUE      SPLIT
+DVOFLAT.MEF.OUTPUT       OUTPUT {OUTPUT}.co.fits              	 IMAGE     NONE      CHIP       TRUE      MEF
+DVOFLAT.SPL.OUTPUT       OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits  	 IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PSPHOT.RESID             OUTPUT {OUTPUT}.res.fits             	 IMAGE     NONE      CHIP       FALSE     MEF
+PSPHOT.BACKGND           OUTPUT {OUTPUT}.bck.fits             	 IMAGE     NONE      CHIP       FALSE     MEF
+PSPHOT.BACKSUB           OUTPUT {OUTPUT}.sub.fits             	 IMAGE     NONE      CHIP       FALSE     MEF
+PSPHOT.BACKMDL           OUTPUT {OUTPUT}.mdl.fits             	 IMAGE     NONE      CHIP       FALSE     MEF
+
+PSPHOT.OUTPUT.RAW        OUTPUT {OUTPUT}.{CHIP.NAME}          	 RAW       NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX         OUTPUT {OUTPUT}.{CHIP.NAME}.sx          SX        NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.OBJ        OUTPUT {OUTPUT}.{CHIP.NAME}.obj         OBJ       NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CMP        OUTPUT {OUTPUT}.{CHIP.NAME}.cmp      	 CMP       NONE      CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.SPL       OUTPUT {OUTPUT}.{CHIP.NAME}.cmf      	 CMF       NONE      CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.MEF       OUTPUT {OUTPUT}.cmf                  	 CMF       NONE      CHIP       TRUE      MEF
+
+PSPHOT.PSF.SAVE          OUTPUT {OUTPUT}.psf                  	 PSF       NONE      CHIP       TRUE      MEF
+
+SOURCE.PLOT.MOMENTS      OUTPUT {OUTPUT}.mnt.png              	 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL     OUTPUT {OUTPUT}.psf.png              	 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.APRESID      OUTPUT {OUTPUT}.dap.png              	 KAPA      NONE      FPA        TRUE      NONE
+
+PSASTRO.OUTPUT.CMP       OUTPUT {OUTPUT}.{CHIP.NAME}.smp      	 CMP       NONE      CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.smf      	 CMF       NONE      CHIP       TRUE      SPLIT
+PSASTRO.OUT.CMF.MEF      OUTPUT {OUTPUT}.smf                  	 CMF       NONE      FPA        TRUE      MEF
+PSASTRO.OUT.MODEL        OUTPUT {OUTPUT}.asm              ASTROM.MODEL     NONE      FPA        TRUE      NONE
+PSASTRO.OUT.REFSTARS     OUTPUT {OUTPUT}.aref.fits        ASTROM.REFSTARS  NONE      FPA        TRUE      NONE
+
+PSWARP.OUTPUT            OUTPUT {OUTPUT}.fits                 	 IMAGE     COMP_IMG  FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK       OUTPUT {OUTPUT}.mk.fits              	 MASK      COMP_MASK FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT     OUTPUT {OUTPUT}.wt.fits              	 WEIGHT    COMP_WT   FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES    OUTPUT {OUTPUT}.cmf                     CMF       NONE      FPA        TRUE      NONE
+PSWARP.BIN1              OUTPUT {OUTPUT}.b1.fits              	 IMAGE     COMP_IMG  FPA        TRUE      NONE
+PSWARP.BIN2              OUTPUT {OUTPUT}.b2.fits              	 IMAGE     COMP_IMG  FPA        TRUE      NONE
+
+SKYCELL.STATS            OUTPUT {OUTPUT}.stats                	 STATS     NONE      FPA        TRUE      NONE
+SKYCELL.TEMPLATE         OUTPUT {OUTPUT}.skycell              	 SKYCELL   NONE      FPA        TRUE      NONE
+
+PPSUB.OUTPUT             OUTPUT {OUTPUT}.fits                 	 IMAGE     COMP_IMG  FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK        OUTPUT {OUTPUT}.mk.fits              	 MASK      COMP_MASK FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT      OUTPUT {OUTPUT}.wt.fits              	 WEIGHT    COMP_WT   FPA        TRUE      NONE
+
+PPSTACK.OUTPUT           OUTPUT {OUTPUT}.fits                 	 IMAGE     COMP_IMG  FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK      OUTPUT {OUTPUT}.mk.fits              	 MASK      COMP_MASK FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT    OUTPUT {OUTPUT}.wt.fits              	 WEIGHT    COMP_WT   FPA        TRUE      NONE
+
+PPSTAMP.OUTPUT           OUTPUT {OUTPUT}.fits                 	 IMAGE     NONE      FPA        TRUE      NONE
+PPSTAMP.CHIP.MEF         OUTPUT {OUTPUT}.ch.fits              	 IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT.MEF         OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      MEF
+PPSIM.OUTPUT.SPL         OUTPUT {OUTPUT}.{CHIP.NAME}.fits        IMAGE     NONE      CHIP       TRUE      SPLIT
+PPSIM.SOURCES            OUTPUT {OUTPUT}.cmf                     CMF       NONE      FPA        TRUE      MEF
+
+PPARITH.OUTPUT.IMAGE     OUTPUT {OUTPUT}.fits                 	 IMAGE     COMP_IMG  CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK      OUTPUT {OUTPUT}.fits                 	 MASK      COMP_MASK CHIP       TRUE      NONE
+
+LOG.IMFILE               OUTPUT {OUTPUT}.{CHIP.NAME}.log      	 TEXT      NONE      CHIP       TRUE      NONE
+LOG.EXP                  OUTPUT {OUTPUT}.log                  	 TEXT      NONE      FPA        TRUE      NONE
+
+TRACE.IMFILE             OUTPUT {OUTPUT}.{CHIP.NAME}.trace    	 TEXT      NONE      CHIP       TRUE      NONE
+TRACE.EXP                OUTPUT {OUTPUT}.trace                	 TEXT      NONE      FPA        TRUE      NONE
Index: /branches/eam_branch_20080324/ippconfig/configure.ac
===================================================================
--- /branches/eam_branch_20080324/ippconfig/configure.ac	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/configure.ac	(revision 17278)
@@ -0,0 +1,28 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ippConfig], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([ipprc.config.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_MAINTAINER_MODE
+
+AC_PROG_INSTALL
+
+AC_CONFIG_FILES([
+  Makefile
+  recipes/Makefile
+  isp/Makefile
+  gpc1/Makefile
+  ctio_mosaic2/Makefile
+  cfh12k/Makefile
+  megacam/Makefile
+  simple/Makefile
+  simmosaic/Makefile
+  simtest/Makefile
+  tc3/Makefile
+  skycell/Makefile
+  sdss/Makefile
+  esowfi/Makefile
+  lbc_red/Makefile
+])
+AC_OUTPUT
Index: /branches/eam_branch_20080324/ippconfig/ctio_mosaic2/camera.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/ctio_mosaic2/camera.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/ctio_mosaic2/camera.config	(revision 17278)
@@ -0,0 +1,415 @@
+# Camera configuration file for the CTIO MOSAIC2 : describes the camera
+
+# File formats that we know about
+FORMATS		METADATA
+    MEF		STR	ctio_mosaic2/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA	METADATA
+	ccd00	STR	LeftAmp RightAmp
+	ccd01	STR	LeftAmp RightAmp
+	ccd02	STR	LeftAmp RightAmp
+	ccd03	STR	LeftAmp RightAmp
+	ccd04	STR	LeftAmp RightAmp
+	ccd05	STR	LeftAmp RightAmp
+	ccd06	STR	LeftAmp RightAmp
+	ccd07	STR	LeftAmp RightAmp
+END
+
+# menu of possible filter names:  internal  STR  external name
+FILTER.ID       METADATA
+   VR MULTI
+   VR STR VR.Supermacho.c6027 
+   VR STR VR Supermacho c6027 
+   VR STR VR SuperMacho c6027 
+   R  STR R Harris c6004      
+   I  STR I c6028             
+   U  STR U c6001
+   B  STR B Harris c6002
+   V  STR V Harris c6026
+   # Minor filters 
+   g  MULTI
+   g  STR g SDSS c6017
+   g  STR SDSS g c6017
+   r  MULTI
+   r  STR SDSS r c6018
+   r  STR r SDSS c6018
+   i  MULTI
+   i  STR i SDSS c6019
+   i  STR SDSS i c6019
+   # Unused filters (only used in zeros, and therefore irrelevent)
+   NULL MULTI
+   NULL STR z SDSS c6020
+   NULL STR SDSS z c6020
+   NULL STR ha8 H-alpha+8nm c6011
+   NULL STR M Washington c6007
+   NULL STR u SDSS c6021
+   NULL STR VRr Supermacho c6027 
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	ctio_mosaic2		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+	# Recipes for ppImage
+        PPIMAGE         STR     ctio_mosaic2/ppImage.config          # Default: all (normal) options on
+        PSPHOT          STR     ctio_mosaic2/psphot.config           # Default: all (normal) options on
+	PPMERGE		STR	ctio_mosaic2/ppMerge.config
+END
+
+# Reduction classes
+REDUCTION	METADATA
+	# Detrend processing
+	DETREND		METADATA
+		BIAS_PROCESS	STR	PPIMAGE_O
+		BIAS_RESID	STR	PPIMAGE_B
+		BIAS_VERIFY	STR	PPIMAGE_OB
+		BIAS_STACK	STR	PPMERGE_BIAS
+		DARK_PROCESS	STR	PPIMAGE_OB
+		DARK_RESID	STR	PPIMAGE_D
+		DARK_VERIFY	STR	PPIMAGE_OBD
+		DARK_STACK	STR	PPMERGE_DARK
+		SHUTTER_PROCESS	STR	PPIMAGE_OBD
+		SHUTTER_RESID	STR	PPIMAGE_S
+		SHUTTER_VERIFY	STR	PPIMAGE_OBDS
+		SHUTTER_STACK	STR	PPMERGE_SHUTTER
+		FLAT_PROCESS	STR	PPIMAGE_OBDS
+		FLAT_RESID	STR	PPIMAGE_F
+		FLAT_VERIFY	STR	PPIMAGE_OBDSF
+		FLAT_STACK	STR	PPMERGE_FLAT
+		FRINGE_PROCESS	STR	PPIMAGE_OBDSF
+		FRINGE_RESID	STR	PPIMAGE_R
+		FRINGE_VERIFY	STR	PPIMAGE_OBDSFR
+		FRINGE_STACK	STR	PPMERGE_FRINGE
+
+		# Generation of pixel masks from darks and flats
+		DARKMASK_PROCESS	STR	PPIMAGE_OBD
+		DARKMASK_RESID		STR	PPIMAGE_N
+		DARKMASK_VERIFY		STR	PPIMAGE_OBD
+		DARKMASK_STACK		STR	PPMERGE_DARKMASK
+		FLATMASK_PROCESS	STR	PPIMAGE_OBDSF
+		FLATMASK_RESID		STR	PPIMAGE_N
+		FLATMASK_VERIFY		STR	PPIMAGE_OBDSF
+		FLATMASK_STACK		STR	PPMERGE_FLATMASK
+		JPEG_BIN1_IMAGE_DARKMASK STR	PPIMAGE_J1_IMAGE_M
+		JPEG_BIN2_IMAGE_DARKMASK STR	PPIMAGE_J2_IMAGE_M
+		JPEG_BIN1_IMAGE_FLATMASK STR	PPIMAGE_J1_IMAGE_M
+		JPEG_BIN2_IMAGE_FLATMASK STR	PPIMAGE_J2_IMAGE_M
+		JPEG_BIN1_RESID_DARKMASK STR	PPIMAGE_J1_RESID_M
+		JPEG_BIN2_RESID_DARKMASK STR	PPIMAGE_J2_RESID_M
+		JPEG_BIN1_RESID_FLATMASK STR	PPIMAGE_J1_RESID_M
+		JPEG_BIN2_RESID_FLATMASK STR	PPIMAGE_J2_RESID_M
+
+ 		JPEG_BIN1_IMAGE_BIAS     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_DARK     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_SHUTTER  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FLAT     STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_DOMEFLAT STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_SKYFLAT  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FRINGE   STR  PPIMAGE_J1_IMAGE_R
+ 		JPEG_BIN2_IMAGE_BIAS     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_DARK     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_SHUTTER  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FLAT     STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_DOMEFLAT STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_SKYFLAT  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FRINGE   STR  PPIMAGE_J2_IMAGE_R
+
+ 		JPEG_BIN1_RESID_BIAS     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_DARK     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_SHUTTER  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FLAT     STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_DOMEFLAT STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_SKYFLAT  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FRINGE   STR  PPIMAGE_J1_RESID_R
+ 		JPEG_BIN2_RESID_BIAS     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_DARK     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_SHUTTER  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FLAT     STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_DOMEFLAT STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_SKYFLAT  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FRINGE   STR  PPIMAGE_J2_RESID_R
+	END
+	# Processing raw data
+	DEFAULT		METADATA
+		CHIP		STR	PPIMAGE_OBDSFRA
+	END
+END
+
+FITS    METADATA
+# BITPIX is the bits per pixel for writing the output data
+# COMP = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ] are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE [0..16] is the number of "noise bits" to preserve when quantising floating point data; 16 for no loss
+# HSCALE is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+
+# BITPIX(S32) is the bits per pixel for writing the output data
+# COMPRESSION(STR) = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ](S32) are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE(S32) [0..16] is the number of "noise bits" to preserve when quantising floating point data
+# HSCALE(S32) is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH(S32) is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+# SCALING(STR) = NONE|RANGE|STDEV_POSITIVE|STDEV_NEGATIVE|STDEV_BOTH|MANUAL is the scaling scheme
+# BSCALE(F32) is the manual scaling to apply (when SCALING = MANUAL)
+# BZERO(F32) is the manual zero-point to apply (when SCALING = MANUAL)
+# STDEV.BITS(S32) is the number of bits to map to a standard deviation (when SCALING = STDEV_*)
+# STDEV.NUM(F32) is the number of standard deviations to the edge (when SCALING = STDEV_NEGATIVE|STDEV_POSITIVE)
+# FLOAT(STR) is the name of a custom floating-point type
+
+	DET_IMAGE	METADATA
+		BITPIX		S32	-32
+	END
+	DET_MASK	METADATA
+		BITPIX		S32	8
+	END
+	DET_WEIGHT	METADATA
+		BITPIX		S32	-32
+	END
+
+	SKY_IMAGE	METADATA
+		BITPIX		S32	-32
+	END
+	SKY_MASK	METADATA
+		BITPIX		S32	8
+	END
+	SKY_WEIGHT	METADATA
+		BITPIX		S32	-32
+	END
+
+	COMP_POS	METADATA
+		BITPIX		S32	16
+		SCALING		STR	STDEV_POSITIVE
+		STDEV.BITS	S32	4
+		STDEV.NUM	F32	10
+		COMPRESSION	STR	RICE
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+	COMP_MASK	METADATA
+		BITPIX		S32	8
+		COMPRESSION	STR	PLIO
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+	COMP_SUB	METADATA
+		BITPIX		S32	16
+		SCALING		STR	STDEV_BOTH
+		STDEV.BITS	S32	4
+		STDEV.NUM	F32	5
+		COMPRESSION	STR	RICE
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+
+END
+
+FILERULES METADATA
+   ### Redirections
+   PSASTRO.INPUT      	 STR PSASTRO.INPUT.CMF
+   PSASTRO.OUTPUT     	 STR PSASTRO.OUT.CMF.MEF
+   PSASTRO.OUTPUT.MEF  	 STR PSASTRO.OUT.CMF.MEF
+   PSPHOT.OUTPUT      	 STR PSPHOT.OUT.CMF.MEF
+   PPIMAGE.OUTPUT     	 STR PPIMAGE.OUTPUT.MEF
+   PPIMAGE.BIN1       	 STR PPIMAGE.BIN1.MEF
+   PPIMAGE.BIN2       	 STR PPIMAGE.BIN2.MEF
+   DVOCORR.OUTPUT     	 STR DVOCORR.MEF.OUTPUT
+   DVOFLAT.OUTPUT     	 STR DVOFLAT.MEF.OUTPUT
+   PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.MEF
+   PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.MEF
+
+   ### input file definitions
+   ### use @DETDB entries to get the detrend images from the database
+   ### replace @DETDB with @FILES if you want to require it from the 
+   ### command line, or with an explicit name to require a specific file
+   TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE 
+
+   ## files used by ppImage
+   PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE
+   PPIMAGE.MASK       INPUT @DETDB        CHIP       IMAGE
+   PPIMAGE.BIAS       INPUT @DETDB        CHIP       IMAGE
+   PPIMAGE.DARK       INPUT @DETDB        CHIP       DARK
+   PPIMAGE.FLAT       INPUT @DETDB        CHIP       IMAGE
+   PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE     
+   PPIMAGE.SHUTTER    INPUT @DETDB        CHIP       IMAGE     
+
+   ## Files used by ppMerge
+   PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+   PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+   PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+   ## files used to build and apply the flat-field correction images
+   DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE     
+   DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER     
+   DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE 	
+   DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE 	
+
+   ## files used by psphot 
+   PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE     
+   PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE     
+   PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+   PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+   PSPHOT.PSF.LOAD    INPUT @FILES        CHIP       PSF       
+   PSPHOT.INPUT.CMF   INPUT @FILES        CHIP       CMF       
+
+   ## files used by psastro 
+   PSASTRO.INPUT.CMP  INPUT @FILES        CHIP       CMP       
+   PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF       
+
+   ## files used by pswarp
+   PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE     
+   PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+   PSWARP.MASK        INPUT @FILES        CHIP       MASK
+   PSWARP.SKYCELL     INPUT @FILES        CHIP       IMAGE     
+   PSWARP.ASTROM      INPUT @FILES        CHIP       CMF       
+
+   PPSUB.INPUT        INPUT @FILES        FPA        IMAGE
+   PPSUB.INPUT.MASK   INPUT @FILES        FPA        MASK
+   PPSUB.INPUT.WEIGHT INPUT @FILES        FPA        WEIGHT
+   PPSUB.REF          INPUT @FILES        FPA        IMAGE
+   PPSUB.REF.MASK     INPUT @FILES        FPA        MASK
+   PPSUB.REF.WEIGHT   INPUT @FILES        FPA        WEIGHT
+   PPSUB.SOURCES      INPUT @FILES        FPA        CMF
+
+   PPSTACK.INPUT      INPUT @FILES        FPA        IMAGE
+   PPSTACK.INPUT.MASK INPUT @FILES        FPA        MASK
+   PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA        WEIGHT
+   PPSTACK.SOURCES    INPUT @FILES        FPA        CMF
+
+   PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+   PPARITH.INPUT.IMAGE INPUT @FILES       CHIP       IMAGE
+   PPARITH.INPUT.MASK  INPUT @FILES       CHIP       MASK
+
+   ### output file definitions
+   TYPE                  OUTPUT FILENAME.RULE                 FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+   PPIMAGE.STATS	 OUTPUT {OUTPUT}.stats		      STATS     NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.SPL  	 OUTPUT {OUTPUT}.{CHIP.NAME}.b0.fits  IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.OUTPUT.MEF  	 OUTPUT {OUTPUT}.b0.fits              IMAGE     COMP_SUB  CHIP       TRUE      MEF
+   #PPIMAGE.OUTPUT.DETMASK OUTPUT {OUTPUT}.{CHIP.NAME}.fits   IMAGE     COMP_MASK CHIP       TRUE      NONE
+   PPIMAGE.OUTPUT.DETMASK OUTPUT {OUTPUT}.detmask.fits	      IMAGE     COMP_MASK CHIP       TRUE      MEF
+
+   PPIMAGE.OUT.MK.SPL  	 OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits  MASK      COMP_MASK CHIP       TRUE      SPLIT
+   PPIMAGE.OUT.WT.SPL  	 OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits  WEIGHT    COMP_POS  CHIP       TRUE      SPLIT
+   PPIMAGE.OUT.MK.MEF  	 OUTPUT {OUTPUT}.mk.fits              MASK      COMP_MASK CHIP       TRUE      MEF
+   PPIMAGE.OUT.WT.MEF  	 OUTPUT {OUTPUT}.wt.fits              WEIGHT    COMP_POS  CHIP       TRUE      MEF
+
+   PPIMAGE.CHIP          OUTPUT {OUTPUT}.fits                 IMAGE     COMP_POS  CHIP       TRUE      MEF
+   PPIMAGE.CHIP.MASK     OUTPUT {OUTPUT}.mk.fits              MASK      COMP_MASK CHIP       TRUE      MEF
+   PPIMAGE.CHIP.WEIGHT   OUTPUT {OUTPUT}.wt.fits              WEIGHT    COMP_POS  CHIP       TRUE      MEF
+
+   PPIMAGE.OUTPUT.FPA1 	 OUTPUT {OUTPUT}.b1.fits              IMAGE     NONE      FPA        TRUE      MEF
+   PPIMAGE.OUTPUT.FPA2 	 OUTPUT {OUTPUT}.b2.fits              IMAGE     NONE      FPA        TRUE      MEF
+
+   PPIMAGE.BIN1.SPL    	 OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits  IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.BIN2.SPL    	 OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits  IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.BIN1.MEF    	 OUTPUT {OUTPUT}.b1c.fits             IMAGE     NONE      CHIP       TRUE      MEF
+   PPIMAGE.BIN2.MEF    	 OUTPUT {OUTPUT}.b2c.fits             IMAGE     NONE      CHIP       TRUE      MEF
+
+   PPIMAGE.JPEG1       	 OUTPUT {OUTPUT}.b1.jpg               JPEG      NONE      FPA        TRUE      NONE
+   PPIMAGE.JPEG2       	 OUTPUT {OUTPUT}.b2.jpg               JPEG      NONE      FPA        TRUE      NONE
+
+   PPMERGE.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                 MASK      NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.BIAS   OUTPUT {OUTPUT}.fits                 IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.DARK   OUTPUT {OUTPUT}.fits                 DARK      NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.fits                IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.FLAT   OUTPUT {OUTPUT}.fits                 IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.FRINGE OUTPUT {OUTPUT}.fits                 FRINGE    NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.SIGMA  OUTPUT {OUTPUT}.sigma.fits           IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.COUNT  OUTPUT {OUTPUT}.count.fits           IMAGE     NONE      CHIP       TRUE      NONE
+
+   DVOCORR.MEF.OUTPUT  	 OUTPUT {OUTPUT}.fc.fits              IMAGE     NONE      CHIP       TRUE      MEF
+   DVOCORR.SPL.OUTPUT  	 OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits  IMAGE     NONE      CHIP       TRUE      SPLIT
+   DVOFLAT.MEF.OUTPUT  	 OUTPUT {OUTPUT}.co.fits              IMAGE     NONE      CHIP       TRUE      MEF
+   DVOFLAT.SPL.OUTPUT  	 OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits  IMAGE     NONE      CHIP       TRUE      SPLIT
+
+   PSPHOT.RESID        	 OUTPUT {OUTPUT}.res.fits             IMAGE     NONE      CHIP       TRUE      MEF
+   PSPHOT.BACKGND      	 OUTPUT {OUTPUT}.bck.fits             IMAGE     NONE      CHIP       TRUE      MEF
+   PSPHOT.BACKSUB      	 OUTPUT {OUTPUT}.sub.fits             IMAGE     NONE      CHIP       TRUE      MEF
+   PSPHOT.BACKMDL      	 OUTPUT {OUTPUT}.mdl.fits             IMAGE     NONE      CHIP       TRUE      MEF
+
+   PSPHOT.OUTPUT.RAW   	 OUTPUT {OUTPUT}.{CHIP.NAME}          RAW       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUTPUT.SX    	 OUTPUT {OUTPUT}.sx                   SX        NONE      CHIP       TRUE      NONE
+   PSPHOT.OUTPUT.OBJ   	 OUTPUT {OUTPUT}.obj                  OBJ       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUTPUT.CMP   	 OUTPUT {OUTPUT}.{CHIP.NAME}.cmp      CMP       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUT.CMF.SPL  	 OUTPUT {OUTPUT}.{CHIP.NAME}.cmf      CMF       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUT.CMF.MEF  	 OUTPUT {OUTPUT}.cmf                  CMF       NONE      FPA        TRUE      NONE
+
+   PSPHOT.PSF.SAVE       OUTPUT {OUTPUT}.psf                  PSF       NONE      CHIP       TRUE      MEF
+
+   SOURCE.PLOT.MOMENTS   OUTPUT {OUTPUT}.mnt.png              KAPA      NONE      FPA        TRUE      NONE
+   SOURCE.PLOT.PSFMODEL  OUTPUT {OUTPUT}.psf.png              KAPA      NONE      FPA        TRUE      NONE
+   SOURCE.PLOT.APRESID   OUTPUT {OUTPUT}.dap.png              KAPA      NONE      FPA        TRUE      NONE
+
+   PSASTRO.OUTPUT.CMP    OUTPUT {OUTPUT}.{CHIP.NAME}.smp      CMP       NONE      CHIP       TRUE      NONE
+   PSASTRO.OUT.CMF.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.smf      CMF       NONE      CHIP       TRUE      NONE
+   PSASTRO.OUT.CMF.MEF   OUTPUT {OUTPUT}.smf                  CMF       NONE      FPA        TRUE      NONE
+
+   PSWARP.OUTPUT       	 OUTPUT {OUTPUT}.fits      	      IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.MASK    OUTPUT {OUTPUT}.mk.fits              MASK      NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits              WEIGHT    NONE      FPA        TRUE      NONE
+   PSWARP.BIN1         	 OUTPUT {OUTPUT}.b1.fits   	      IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.BIN2         	 OUTPUT {OUTPUT}.b2.fits   	      IMAGE     NONE      FPA        TRUE      NONE
+
+   SKYCELL.STATS         OUTPUT {OUTPUT}.stats                STATS     NONE      FPA        TRUE      NONE
+   SKYCELL.TEMPLATE      OUTPUT {OUTPUT}.skycell              SKYCELL   NONE      FPA        TRUE      NONE
+
+   PPSUB.OUTPUT          OUTPUT {OUTPUT}.fits                 IMAGE     NONE      FPA        TRUE      NONE
+   PPSUB.OUTPUT.MASK     OUTPUT {OUTPUT}.mk.fits              MASK      NONE      FPA        TRUE      NONE
+   PPSUB.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits              WEIGHT    NONE      FPA        TRUE      NONE
+
+   PPSTACK.OUTPUT        OUTPUT {OUTPUT}.fits                 IMAGE     NONE      FPA        TRUE      NONE
+   PPSTACK.OUTPUT.MASK   OUTPUT {OUTPUT}.mk.fits              MASK      NONE      FPA        TRUE      NONE
+   PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.wt.fits              WEIGHT    NONE      FPA        TRUE      NONE
+
+   PPSTAMP.OUTPUT        OUTPUT {OUTPUT}.fits                 IMAGE     NONE      FPA        TRUE      NONE
+   PPSTAMP.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits              IMAGE     NONE      CHIP       FALSE     MEF
+
+   PPSIM.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+
+   PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits                 IMAGE     COMP_POS  CHIP       TRUE      NONE
+   PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                 MASK      COMP_MASK CHIP       TRUE      NONE
+
+   LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log      TEXT      NONE      CHIP      TRUE       NONE
+   LOG.EXP               OUTPUT {OUTPUT}.log                  TEXT      NONE      FPA       TRUE       NONE
+   TRACE.IMFILE          OUTPUT {OUTPUT}.{CHIP.NAME}.trace    TEXT      NONE      CHIP       TRUE      NONE
+END
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR {CHIP.NAME}.hdr
+  CMF.DATA STR {CHIP.NAME}.psf # use .PSF and .EXT?
+
+  PSF.HEAD  STR {CHIP.NAME}.hdr
+  PSF.TABLE STR {CHIP.NAME}.psf_model
+  PSF.RESID STR {CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branch_20080324/ippconfig/ctio_mosaic2/psphot.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/ctio_mosaic2/psphot.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/ctio_mosaic2/psphot.config	(revision 17278)
@@ -0,0 +1,42 @@
+	
+# turn these on to see specific outputs
+SAVE.RESID	BOOL 	TRUE
+
+IMSTATS_NPIX        S32  1000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+# if you want to list multiple entries, uncomment 'MULTI' here
+# PSF_MODEL           MULTI
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+# PSF_MODEL           STR  PS_MODEL_GAUSS
+# PSF_MODEL           STR  PS_MODEL_PGAUSS
+# PSF_MODEL           STR  PS_MODEL_TGAUSS  ## not well tested, not very successful
+
+MOMENTS_SN_MIN      F32  20.0           # min S/N to measure moments
+EXT_MIN_SN           F32  20.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  20.0
+AP_MIN_SN            F32  25.0
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
+
+## gene's overrides for testing:
+PEAKS_NSIGMA_LIMIT_2 F32  10.0 	   	 # peak significance threshold
+
+EXT_MODEL           STR  PS_MODEL_QGAUSS
+
+TEST_FIT            BOOL FALSE
+TEST_FIT_X          F32  781
+TEST_FIT_Y          F32  830
+
+TEST_FIT_MODE       STR  CONV
+#TEST_FIT_MODE       STR PSF
+#TEST_FIT_MODE       STR  EXT
+
+#TEST_FIT_MODEL      STR  PS_MODEL_SERSIC
+TEST_FIT_MODEL      STR  PS_MODEL_QGAUSS
+TEST_MOMENTS_RADIUS F32 9.0
+OUTPUT.FORMAT       STR PS1_DEV_1
Index: /branches/eam_branch_20080324/ippconfig/esowfi/filerules-mef.mdc
===================================================================
--- /branches/eam_branch_20080324/ippconfig/esowfi/filerules-mef.mdc	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/esowfi/filerules-mef.mdc	(revision 17278)
@@ -0,0 +1,142 @@
+### Redirections
+PSASTRO.INPUT      STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT     STR PSASTRO.OUT.CMF.SPL
+PSASTRO.OUTPUT.MEF STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT      STR PSPHOT.OUT.CMF.SPL
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the
+### command line, or with an explicit name to require a specific file
+TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT      INPUT @FILES        CHIP        IMAGE
+PPIMAGE.MASK       INPUT @DETDB        CHIP        IMAGE
+PPIMAGE.BIAS       INPUT @DETDB        CHIP        IMAGE
+PPIMAGE.DARK       INPUT @DETDB        CHIP        DARK
+PPIMAGE.FLAT       INPUT @DETDB        CHIP        IMAGE
+PPIMAGE.FRINGE     INPUT @DETDB        CHIP        FRINGE
+PPIMAGE.SHUTTER    INPUT @DETDB        CHIP        IMAGE
+
+## Files used by ppMerge
+PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+## files used to build and apply the flat field correction images
+DVOCORR.INPUT      INPUT @FILES        CHIP        IMAGE
+DVOCORR.REFHEAD    INPUT @FILES        CHIP        HEADER
+DVOFLAT.INPUT      INPUT @FILES        CHIP        IMAGE
+DVOFLAT.CORR       INPUT @DETDB        CHIP        IMAGE
+
+## files used by psphot
+PSPHOT.LOAD        INPUT @FILES        CHIP        IMAGE
+PSPHOT.INPUT       INPUT @FILES        CHIP        IMAGE
+PSPHOT.MASK        INPUT @FILES        CHIP        MASK
+PSPHOT.WEIGHT      INPUT @FILES        CHIP        WEIGHT
+PSPHOT.PSF.LOAD    INPUT @FILES        CHIP        PSF
+PSPHOT.INPUT.CMF   INPUT @FILES        CHIP        CMF
+
+## files used by psastro
+PSASTRO.INPUT.CMF  INPUT @FILES        CHIP        CMF
+
+## files used by pswarp
+PSWARP.INPUT       INPUT @FILES        CHIP        IMAGE
+PSWARP.SKYCELL     INPUT @FILES        CHIP        IMAGE
+PSWARP.WEIGHT      INPUT @FILES        CHIP        WEIGHT
+PSWARP.MASK        INPUT @FILES        CHIP        MASK
+PSWARP.ASTROM      INPUT @FILES        CHIP        CMF
+
+PPSUB.INPUT        INPUT    none.fits  CHIP        IMAGE
+PPSUB.INPUT.MASK   INPUT    none.fits  CHIP        MASK
+PPSUB.INPUT.WEIGHT INPUT    none.fits  CHIP        WEIGHT
+PPSUB.REF          INPUT    none.fits  CHIP        IMAGE
+PPSUB.REF.MASK     INPUT    none.fits  CHIP        MASK
+PPSUB.REF.WEIGHT   INPUT    none.fits  CHIP        WEIGHT
+
+PPSTACK.INPUT      INPUT    none.fits  CHIP        IMAGE
+PPSTACK.INPUT.MASK INPUT    none.fits  FPA         MASK
+
+PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+### output file definitions
+TYPE                    OUTPUT   FILENAME.RULE                   FILE.TYPE  DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT          OUTPUT   {OUTPUT}.{CHIP.NAME}.fits          IMAGE     CHIP       TRUE      SPLIT
+PPIMAGE.OUTPUT.MASK     OUTPUT   {OUTPUT}.{CHIP.NAME}.mask.fits     MASK      CHIP       TRUE      SPLIT
+PPIMAGE.OUTPUT.WEIGHT   OUTPUT   {OUTPUT}.{CHIP.NAME}.wt.fits       WEIGHT    CHIP       TRUE      SPLIT
+PPIMAGE.CHIP            OUTPUT   {OUTPUT}.{CHIP.NAME}.ch.fits       IMAGE     CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.MASK       OUTPUT   {OUTPUT}.{CHIP.NAME}.ch.mask.fits  MASK      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.WEIGHT     OUTPUT   {OUTPUT}.{CHIP.NAME}.ch.wt.fits    WEIGHT    CHIP       TRUE      SPLIT
+PPIMAGE.OUTPUT.FPA1     OUTPUT   {OUTPUT}.fpa1.fits                 IMAGE     FPA        TRUE      TOGETHER
+PPIMAGE.OUTPUT.FPA2     OUTPUT   {OUTPUT}.fpa2.fits                 IMAGE     FPA        TRUE      TOGETHER
+PPIMAGE.STATS           OUTPUT   {OUTPUT}.stats                     STATS     FPA        TRUE      TOGETHER
+
+PPIMAGE.JPEG1       OUTPUT   {OUTPUT}.b1.jpg                JPEG       FPA        TRUE      TOGETHER
+PPIMAGE.JPEG2       OUTPUT   {OUTPUT}.b2.jpg                JPEG       FPA        TRUE      TOGETHER
+PPIMAGE.BIN1        OUTPUT   {OUTPUT}.{CHIP.NAME}.b1.fits   IMAGE      CHIP       TRUE      SPLIT
+PPIMAGE.BIN2        OUTPUT   {OUTPUT}.{CHIP.NAME}.b2.fits   IMAGE      CHIP       TRUE      SPLIT
+
+PPMERGE.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                   MASK      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.BIAS   OUTPUT {OUTPUT}.fits                   IMAGE     CHIP       TRUE      NONE
+PPMERGE.OUTPUT.DARK   OUTPUT {OUTPUT}.fits                   DARK      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.fits                  IMAGE     CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FLAT   OUTPUT {OUTPUT}.fits                   IMAGE     CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FRINGE OUTPUT {OUTPUT}.fits                   FRINGE    CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SIGMA  OUTPUT {OUTPUT}.sigma.fits             IMAGE     CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT  OUTPUT {OUTPUT}.count.fits             IMAGE     CHIP       TRUE      NONE
+
+DVOCORR.OUTPUT      OUTPUT   {OUTPUT}.{CHIP.NAME}.fc.fits   IMAGE      CHIP       TRUE      SPLIT
+DVOFLAT.OUTPUT      OUTPUT   {OUTPUT}.{CHIP.NAME}.co.fits   IMAGE      CHIP       TRUE      SPLIT
+
+PSPHOT.RESID        OUTPUT   {OUTPUT}.{CHIP.NAME}.res.fits  IMAGE      CHIP       TRUE      SPLIT
+PSPHOT.BACKGND      OUTPUT   {OUTPUT}.{CHIP.NAME}.bck.fits  IMAGE      CHIP       TRUE      SPLIT
+PSPHOT.BACKSUB      OUTPUT   {OUTPUT}.{CHIP.NAME}.sub.fits  IMAGE      CHIP       TRUE      SPLIT
+PSPHOT.BACKMDL      OUTPUT   {OUTPUT}.{CHIP.NAME}.mdl.fits  IMAGE      CHIP       TRUE      SPLIT
+PSPHOT.BACKMDL.STDEV OUTPUT   {OUTPUT}.{CHIP.NAME}.mdd.fits IMAGE      CHIP       TRUE      SPLIT
+
+PSPHOT.OUTPUT.RAW   OUTPUT   {OUTPUT}.{CHIP.NAME}           RAW        CHIP       TRUE      SPLIT
+PSPHOT.OUTPUT.SX    OUTPUT   {OUTPUT}.{CHIP.NAME}.sx        SX         CHIP       TRUE      SPLIT
+PSPHOT.OUTPUT.OBJ   OUTPUT   {OUTPUT}.{CHIP.NAME}.obj       OBJ        CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.SPL  OUTPUT   {OUTPUT}.{CHIP.NAME}.cmf       CMF        CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.MEF  OUTPUT   {OUTPUT}.cmf                   CMF        FPA        TRUE      TOGETHER
+
+PSPHOT.PSF.SAVE     OUTPUT   {OUTPUT}.{CHIP.NAME}.psf       PSF        CHIP       TRUE      SPLIT
+
+SOURCE.PLOT.MOMENTS  OUTPUT  {OUTPUT}.{CHIP.NAME}.mnt.png   KAPA       CHIP       TRUE      SPLIT
+SOURCE.PLOT.PSFMODEL OUTPUT  {OUTPUT}.{CHIP.NAME}.psf.png   KAPA       CHIP       TRUE      SPLIT
+SOURCE.PLOT.APRESID  OUTPUT  {OUTPUT}.{CHIP.NAME}.dap.png   KAPA       CHIP       TRUE      SPLIT
+
+PSASTRO.OUTPUT.CMP   OUTPUT   {OUTPUT}.{CHIP.NAME}.smp      CMP        CHIP       TRUE      SPLIT
+PSASTRO.OUT.CMF.SPL  OUTPUT   {OUTPUT}.{CHIP.NAME}.smf      CMF        CHIP       TRUE      SPLIT
+PSASTRO.OUT.CMF.MEF  OUTPUT   {OUTPUT}.smf                  CMF        FPA        TRUE      TOGETHER
+
+PSWARP.OUTPUT       OUTPUT   {OUTPUT}.fits                  IMAGE      FPA        TRUE      TOGETHER
+PSWARP.OUTPUT.MASK  OUTPUT   {OUTPUT}.mask.fits             MASK       FPA        TRUE      TOGETHER
+PSWARP.OUTPUT.WEIGHT OUTPUT  {OUTPUT}.wt.fits               WEIGHT     FPA        TRUE      TOGETHER
+PSWARP.BIN1         OUTPUT   {OUTPUT}.b1.fits               IMAGE      FPA        TRUE      TOGETHER
+PSWARP.BIN2         OUTPUT   {OUTPUT}.b2.fits               IMAGE      FPA        TRUE      TOGETHER
+
+SKYCELL.STATS         OUTPUT {OUTPUT}.stats                 STATS      FPA        TRUE      NONE
+SKYCELL.TEMPLATE      OUTPUT {OUTPUT}.skycell               SKYCELL    FPA        TRUE      NONE
+
+PPSUB.OUTPUT        OUTPUT   {OUTPUT}.fits                  IMAGE      FPA        TRUE      TOGETHER
+PPSUB.OUTPUT.MASK   OUTPUT   {OUTPUT}.mask.fits             MASK       FPA        TRUE      TOGETHER
+PPSUB.OUTPUT.WEIGHT OUTPUT   {OUTPUT}.wt.fits               WEIGHT     FPA        TRUE      TOGETHER
+
+PPSTACK.OUTPUT      OUTPUT   {OUTPUT}.fits                  IMAGE      FPA        TRUE      TOGETHER
+PPSTACK.OUTPUT.MASK OUTPUT   {OUTPUT}.mask.fits             MASK       FPA        TRUE      TOGETHER
+
+PPSTAMP.OUTPUT        OUTPUT {OUTPUT}.fits                  IMAGE     NONE      FPA        TRUE      NONE
+PPSTAMP.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits               IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT        OUTPUT   {OUTPUT}.{CHIP.NAME}.fits      IMAGE      CHIP       TRUE      SPLIT
+
+PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits                  IMAGE     NONE      CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                  MASK      NONE      CHIP       TRUE      NONE
+
+LOG.IMFILE          OUTPUT {OUTPUT}.{CHIP.NAME}.log         TEXT       CHIP       TRUE      SPLIT
+LOG.EXP             OUTPUT {OUTPUT}.log                     TEXT       FPA        TRUE      TOGETHER
Index: /branches/eam_branch_20080324/ippconfig/esowfi/ppImage.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/esowfi/ppImage.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/esowfi/ppImage.config	(revision 17278)
@@ -0,0 +1,222 @@
+### ppImage recipe configuration file for simulated test camera
+
+# List of tasks to perform
+
+BASE.FITS       BOOL    FALSE           # Save base detrended image?
+CHIP.FITS       BOOL    TRUE            # Save chip-mosaicked image?
+BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE         BOOL    FALSE           # Reduce overscan to a single value?
+OVERSCAN.FIT            STR     POLYNOMIAL      # NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER          S32     7               # Order of polynomial fit
+OVERSCAN.STAT           STR     MEAN            # MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END   
+END
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
Index: /branches/eam_branch_20080324/ippconfig/gpc1/filerules.mdc
===================================================================
--- /branches/eam_branch_20080324/ippconfig/gpc1/filerules.mdc	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/gpc1/filerules.mdc	(revision 17278)
@@ -0,0 +1,150 @@
+PSASTRO.INPUT      STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT     STR PSASTRO.OUT.CMF.SPL
+PSASTRO.OUTPUT.MEF STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT      STR PSPHOT.OUT.CMF.SPL
+
+### input file definitions
+TYPE                    INPUT    FILENAME.RULE                 DATA.LEVEL FILE.TYPE
+PPIMAGE.INPUT           INPUT    none.fits                     CHIP       IMAGE
+INPUT.MASK              INPUT    none.fits                     CHIP       MASK
+INPUT.WEIGHT            INPUT    none.fits                     CHIP       WEIGHT
+INPUT.PSF               INPUT    none.fits                     CHIP       PSF
+INPUT.SRC               INPUT    none.fits                     CHIP       CMF
+PPIMAGE.BIAS            INPUT    @DETDB                        CHIP       IMAGE
+PPIMAGE.DARK            INPUT    @DETDB                        CHIP       DARK
+PPIMAGE.SHUTTER         INPUT    @DETDB                        CHIP       IMAGE
+PPIMAGE.FLAT            INPUT    @DETDB                        CHIP       IMAGE
+PPIMAGE.MASK            INPUT    @DETDB                        CHIP       MASK
+
+## Files used by ppMerge
+PPMERGE.INPUT           INPUT    @FILES                        CHIP       IMAGE
+PPMERGE.INPUT.MASK      INPUT    @FILES                        CHIP       MASK
+PPMERGE.INPUT.WEIGHT    INPUT    @FILES                        CHIP       WEIGHT
+
+## files used by psphot
+PSPHOT.LOAD             INPUT    @FILES                        CHIP       IMAGE
+PSPHOT.INPUT            INPUT    @FILES                        CHIP       IMAGE
+PSPHOT.MASK             INPUT    @FILES                        CHIP       MASK     
+PSPHOT.WEIGHT           INPUT    @FILES                        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD         INPUT    @FILES                        CHIP       PSF       
+PSPHOT.INPUT.CMF        INPUT    @FILES                        CHIP       CMF       
+
+PSWARP.INPUT            INPUT    none.fits                     CHIP       IMAGE
+PSWARP.WEIGHT           INPUT    none.fits                     CHIP       WEIGHT
+PSWARP.MASK             INPUT    none.fits                     CHIP       MASK
+PSWARP.SKYCELL          INPUT    none.fits                     FPA        IMAGE
+PSWARP.ASTROM           INPUT    none.fits                     CHIP       CMF
+
+PSASTRO.WCS             INPUT    none.fits                     CHIP       CMF
+PSASTRO.MODEL           INPUT    @DETDB                        FPA        ASTROM
+
+PSASTRO.INPUT.CMP       INPUT    none.fits                     CHIP       CMP
+PSASTRO.INPUT.CMF       INPUT    none.fits                     CHIP       CMF
+
+PPSUB.INPUT             INPUT    none.fits                     FPA        IMAGE
+PPSUB.INPUT.MASK        INPUT    none.fits                     FPA        MASK
+PPSUB.INPUT.WEIGHT      INPUT    none.fits                     FPA        WEIGHT
+PPSUB.REF               INPUT    none.fits                     FPA        IMAGE
+PPSUB.REF.MASK          INPUT    none.fits                     FPA        MASK
+PPSUB.REF.WEIGHT        INPUT    none.fits                     FPA        WEIGHT
+
+PPSTACK.INPUT           INPUT    @FILES                        FPA        IMAGE
+PPSTACK.INPUT.MASK      INPUT    @FILES                        FPA        MASK
+PPSTACK.INPUT.WEIGHT    INPUT    @FILES                        FPA        WEIGHT
+PPSTACK.SOURCES         INPUT    @FILES                        FPA        CMF
+
+PPSTAMP.INPUT           INPUT    none.fits                     CHIP       IMAGE
+
+PPARITH.INPUT.IMAGE     INPUT    none.fits                     CHIP       IMAGE
+PPARITH.INPUT.MASK      INPUT    none.fits                     CHIP       MASK
+
+### output file definitions
+TYPE                   OUTPUT FILENAME.RULE                     FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT         OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     COMP_POS   CHIP      TRUE      NONE
+PPIMAGE.OUTPUT.MASK    OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits      MASK      COMP_MASK  CHIP      TRUE      NONE
+PPIMAGE.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits      WEIGHT    COMP_POS   CHIP      TRUE      NONE
+PPIMAGE.OUTPUT.DETMASK OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     COMP_MASK  CHIP      TRUE      NONE
+
+PPIMAGE.CHIP.RAW       OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     COMP_POS   CHIP      TRUE      NONE
+# PPIMAGE.CHIP.MK      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     COMP_MASK  CHIP      TRUE      NONE
+PPIMAGE.CHIP           OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     COMP_POS   CHIP      TRUE      NONE
+PPIMAGE.CHIP.MASK      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits   MASK      COMP_MASK  CHIP      TRUE      NONE
+PPIMAGE.CHIP.WEIGHT    OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits   WEIGHT    COMP_POS   CHIP      TRUE      NONE
+
+PPIMAGE.OUTPUT.FPA1    OUTPUT {OUTPUT}.b1.fits                  IMAGE     NONE       FPA       TRUE      NONE
+PPIMAGE.OUTPUT.FPA2    OUTPUT {OUTPUT}.b2.fits                  IMAGE     NONE       FPA       TRUE      NONE
+
+PPIMAGE.JPEG1          OUTPUT {OUTPUT}.b1.jpg                   JPEG      NONE       FPA       TRUE      NONE
+PPIMAGE.JPEG2          OUTPUT {OUTPUT}.b2.jpg                   JPEG      NONE       FPA       TRUE      NONE
+PPIMAGE.BIN1           OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits      IMAGE     NONE       CHIP      TRUE      NONE
+PPIMAGE.BIN2           OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits      IMAGE     NONE       CHIP      TRUE      NONE
+
+PPIMAGE.STATS          OUTPUT {OUTPUT}.{CHIP.NAME}.stats        STATS     NONE       CHIP      TRUE      NONE
+
+PPMERGE.OUTPUT.MASK    OUTPUT {OUTPUT}.{CHIP.NAME}.fits         MASK      NONE       CHIP      TRUE      NONE
+PPMERGE.OUTPUT.BIAS    OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP      TRUE      NONE
+PPMERGE.OUTPUT.DARK    OUTPUT {OUTPUT}.{CHIP.NAME}.fits         DARK      NONE       CHIP      TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP      TRUE      NONE
+PPMERGE.OUTPUT.FLAT    OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP      TRUE      NONE
+PPMERGE.OUTPUT.FRINGE  OUTPUT {OUTPUT}.{CHIP.NAME}.fits         FRINGE    NONE       CHIP      TRUE      NONE
+PPMERGE.OUTPUT.SIGMA   OUTPUT {OUTPUT}.sigma.fits               IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT   OUTPUT {OUTPUT}.count.fits               IMAGE     NONE      CHIP       TRUE      NONE
+
+DVOCORR.OUTPUT         OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits      IMAGE     NONE       CHIP      TRUE      NONE
+DVOFLAT.OUTPUT         OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits      IMAGE     NONE       CHIP      TRUE      NONE
+
+PSPHOT.RESID           OUTPUT {OUTPUT}.{CHIP.NAME}.res.fits     IMAGE     COMP_SUB   CHIP      TRUE      NONE
+PSPHOT.BACKGND         OUTPUT {OUTPUT}.{CHIP.NAME}.bck.fits     IMAGE     COMP_POS   CHIP      TRUE      NONE
+PSPHOT.BACKSUB         OUTPUT {OUTPUT}.{CHIP.NAME}.sub.fits     IMAGE     COMP_SUB   CHIP      TRUE      NONE
+PSPHOT.BACKMDL         OUTPUT {OUTPUT}.{CHIP.NAME}.mdl.fits     IMAGE     COMP_POS   CHIP      TRUE      NONE
+
+PSPHOT.OUTPUT.RAW      OUTPUT {OUTPUT}.{CHIP.NAME}              RAW       NONE       CHIP      TRUE      NONE
+PSPHOT.OUTPUT.SX       OUTPUT {OUTPUT}.{CHIP.NAME}.sx           SX        NONE       CHIP      TRUE      NONE
+PSPHOT.OUTPUT.OBJ      OUTPUT {OUTPUT}.{CHIP.NAME}.obj          OBJ       NONE       CHIP      TRUE      NONE
+PSPHOT.OUTPUT.CMP      OUTPUT {OUTPUT}.{CHIP.NAME}.cmp          CMP       NONE       CHIP      TRUE      NONE
+PSPHOT.OUT.CMF.SPL     OUTPUT {OUTPUT}.{CHIP.NAME}.cmf          CMF       NONE       CHIP      TRUE      NONE
+PSPHOT.OUT.CMF.MEF     OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA       TRUE      MEF
+
+PSPHOT.PSF.SAVE        OUTPUT {OUTPUT}.psf                      PSF       NONE       CHIP      TRUE      MEF
+
+SOURCE.PLOT.MOMENTS    OUTPUT {OUTPUT}.{CHIP.NAME}.mnt.png      KAPA      NONE       CHIP      TRUE      NONE
+SOURCE.PLOT.PSFMODEL   OUTPUT {OUTPUT}.{CHIP.NAME}.psf.png      KAPA      NONE       CHIP      TRUE      NONE
+SOURCE.PLOT.APRESID    OUTPUT {OUTPUT}.{CHIP.NAME}.dap.png      KAPA      NONE       CHIP      TRUE      NONE
+
+PSASTRO.OUTPUT.CMP     OUTPUT {OUTPUT}.{CHIP.NAME}.smp          CMP       NONE       CHIP      TRUE      NONE
+PSASTRO.OUT.CMF.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.smf          CMF       NONE       CHIP      TRUE      NONE
+PSASTRO.OUT.CMF.MEF    OUTPUT {OUTPUT}.smf                      CMF       NONE       FPA       TRUE      MEF
+PSASTRO.OUT.MODEL      OUTPUT {OUTPUT}.asm               ASTROM.MODEL     NONE       FPA       TRUE      NONE
+PSASTRO.OUT.REFSTARS   OUTPUT {OUTPUT}.aref.fits         ASTROM.REFSTARS  NONE       FPA       TRUE      NONE
+
+PSWARP.OUTPUT          OUTPUT {OUTPUT}.fits                     IMAGE     COMP_POS   FPA       TRUE      NONE
+PSWARP.OUTPUT.MASK     OUTPUT {OUTPUT}.mask.fits                MASK      COMP_MASK  FPA       TRUE      NONE
+PSWARP.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_POS   FPA       TRUE      NONE
+PSWARP.OUTPUT.SOURCES  OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA       TRUE      NONE
+PSWARP.BIN1            OUTPUT {OUTPUT}.b1.fits                  IMAGE     NONE       FPA       TRUE      NONE
+PSWARP.BIN2            OUTPUT {OUTPUT}.b2.fits                  IMAGE     NONE       FPA       TRUE      NONE
+
+SKYCELL.STATS          OUTPUT {OUTPUT}.stats                    STATS     NONE       FPA       TRUE      NONE
+SKYCELL.TEMPLATE       OUTPUT {OUTPUT}.skycell                  SKYCELL   NONE       FPA       TRUE      NONE
+
+PPSUB.OUTPUT           OUTPUT {OUTPUT}.fits                     IMAGE     COMP_SUB   FPA       TRUE      NONE
+PPSUB.OUTPUT.MASK      OUTPUT {OUTPUT}.mask.fits                MASK      COMP_MASK  FPA       TRUE      NONE
+PPSUB.OUTPUT.WEIGHT    OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_POS   FPA       TRUE      NONE
+
+PPSTACK.OUTPUT         OUTPUT {OUTPUT}.fits                     IMAGE     COMP_POS   FPA       TRUE      NONE
+PPSTACK.OUTPUT.MASK    OUTPUT {OUTPUT}.mask.fits                MASK      COMP_MASK  FPA       TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_POS   FPA       TRUE      NONE
+
+PPSTAMP.OUTPUT         OUTPUT {OUTPUT}.fits                     IMAGE     NONE       FPA       TRUE      NONE
+PPSTAMP.CHIP           OUTPUT {OUTPUT}.ch.fits                  IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT           OUTPUT {OUTPUT}.fits                     IMAGE     NONE       FPA       TRUE      MEF
+
+PPARITH.OUTPUT.IMAGE   OUTPUT {OUTPUT}.fits                     IMAGE     COMP_POS  CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK    OUTPUT {OUTPUT}.fits                     MASK      COMP_MASK CHIP       TRUE      NONE
+
+LOG.IMFILE             OUTPUT {OUTPUT}.{CHIP.NAME}.log          TEXT      NONE       CHIP      TRUE      NONE
+LOG.EXP                OUTPUT {OUTPUT}.log                      TEXT      NONE       FPA       TRUE      NONE
+
+TRACE.IMFILE           OUTPUT {OUTPUT}.{CHIP.NAME}.trace        TEXT      NONE       CHIP      TRUE      NONE
+TRACE.EXP              OUTPUT {OUTPUT}.trace                    TEXT      NONE       FPA       TRUE      NONE
Index: /branches/eam_branch_20080324/ippconfig/gpc1/ppImage.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/gpc1/ppImage.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/gpc1/ppImage.config	(revision 17278)
@@ -0,0 +1,223 @@
+
+# binned output image options
+BIN1.XBIN               S32      16
+BIN1.YBIN               S32      16
+BIN2.XBIN               S32     150
+BIN2.YBIN               S32     150
+
+OVERSCAN.SINGLE         BOOL    FALSE            # Reduce overscan to a single value?
+OVERSCAN.STAT           STR     MEDIAN
+OVERSCAN.BOXCAR         S32     3
+
+PHOTCODE.RULE           STR     {DETECTOR}.{FILTER.ID}.{CHIP.NAME}
+
+OLDDARK			BOOL	TRUE
+
+## XXX use these local variations until we are building real detrend images
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+    EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+  ASTROM METADATA
+  END	
+END
+
+# Overscan, bias
+PPIMAGE_OB         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD        METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_DET_ONLY   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  FPA1.FITS        BOOL    TRUE            # Save 1st binned fpa image? 
+  FPA2.FITS        BOOL    TRUE            # Save 2nd binned fpa image? 
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Photometry and astrometry only (for pre-reduced data)
+PPIMAGE_PA         METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
Index: /branches/eam_branch_20080324/ippconfig/isp/camera.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/isp/camera.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/isp/camera.config	(revision 17278)
@@ -0,0 +1,330 @@
+# Camera configuration file for the Pan-STARRS Imaging Sky Probe
+
+# File formats that we know about
+FORMATS         METADATA
+        ISP     STR     isp/format.config
+        CMP     STR     isp/cmp.config
+        CMF     STR     isp/cmf.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip            STR     Cell
+END
+
+# valid filter names and corresponding IDs
+FILTER.ID       METADATA
+        g       STR     g
+        r       STR     r
+        i       STR     i
+        z       STR     z
+        y       STR     y
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	isp		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        PSPHOT          STR     isp/psphot.config               # psphot details
+        PSASTRO         STR     isp/psastro.config              # psastro details
+        PPIMAGE         STR     isp/ppImage.config              # Recipes for ppImage
+        PPMERGE         STR     isp/ppMerge.config              # Recipes for ppMerge
+	REJECTIONS	STR     isp/rejections.config
+END
+
+# Reduction classes
+REDUCTION	METADATA
+	# Detrend processing
+	DETREND		METADATA
+		BIAS_PROCESS	   STR	PPIMAGE_O
+		BIAS_RESID	   STR	PPIMAGE_B
+		BIAS_VERIFY	   STR	PPIMAGE_OB
+		BIAS_STACK	   STR	PPMERGE_BIAS
+		DARK_PROCESS	   STR	PPIMAGE_OB
+		DARK_RESID	   STR	PPIMAGE_D
+		DARK_VERIFY	   STR	PPIMAGE_OBD
+		DARK_STACK	   STR	PPMERGE_DARK
+		SHUTTER_PROCESS	   STR	PPIMAGE_OBD
+		SHUTTER_RESID	   STR	PPIMAGE_S
+		SHUTTER_VERIFY	   STR	PPIMAGE_OBDS
+		SHUTTER_STACK	   STR	PPMERGE_SHUTTER
+		FLAT_PROCESS	   STR	PPIMAGE_OBDS
+		FLAT_RESID	   STR	PPIMAGE_F
+		FLAT_VERIFY	   STR	PPIMAGE_OBDSF
+		FLAT_STACK	   STR	PPMERGE_FLAT
+		FRINGE_PROCESS	   STR	PPIMAGE_OBDSF
+		FRINGE_RESID	   STR	PPIMAGE_R
+		FRINGE_VERIFY	   STR	PPIMAGE_OBDSFR
+		FRINGE_STACK	   STR	PPMERGE_FRINGE
+
+ 		JPEG_BIN1_IMAGE_BIAS     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_DARK     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_SHUTTER  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FLAT     STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_DOMEFLAT STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_SKYFLAT  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FRINGE   STR  PPIMAGE_J1_IMAGE_R
+ 		JPEG_BIN2_IMAGE_BIAS     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_DARK     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_SHUTTER  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FLAT     STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_DOMEFLAT STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_SKYFLAT  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FRINGE   STR  PPIMAGE_J2_IMAGE_R
+
+ 		JPEG_BIN1_RESID_BIAS     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_DARK     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_SHUTTER  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FLAT     STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_DOMEFLAT STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_SKYFLAT  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FRINGE   STR  PPIMAGE_J1_RESID_R
+ 		JPEG_BIN2_RESID_BIAS     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_DARK     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_SHUTTER  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FLAT     STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_DOMEFLAT STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_SKYFLAT  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FRINGE   STR  PPIMAGE_J2_RESID_R
+	END
+	# Processing raw data
+	DEFAULT		METADATA
+		CHIP		STR	PPIMAGE_OBDSFRA
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+
+	# Detrend Processing only for raw data
+	DETREND_ONLY		METADATA
+		CHIP		STR	PPIMAGE_DET_ONLY
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+END
+
+FITS    METADATA
+# BITPIX is the bits per pixel for writing the output data
+# COMP = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ] are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE [0..16] is the number of "noise bits" to preserve when quantising floating point data; 16 for no loss
+# HSCALE is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+
+# BITPIX(S32) is the bits per pixel for writing the output data
+# COMPRESSION(STR) = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ](S32) are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE(S32) [0..16] is the number of "noise bits" to preserve when quantising floating point data
+# HSCALE(S32) is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH(S32) is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+# SCALING(STR) = NONE|RANGE|STDEV_POSITIVE|STDEV_NEGATIVE|STDEV_BOTH|MANUAL is the scaling scheme
+# BSCALE(F32) is the manual scaling to apply (when SCALING = MANUAL)
+# BZERO(F32) is the manual zero-point to apply (when SCALING = MANUAL)
+# STDEV.BITS(S32) is the number of bits to map to a standard deviation (when SCALING = STDEV_*)
+# STDEV.NUM(F32) is the number of standard deviations to the edge (when SCALING = STDEV_NEGATIVE|STDEV_POSITIVE)
+# FLOAT(STR) is the name of a custom floating-point type
+
+	DET_IMAGE	METADATA
+		BITPIX		S32	-32
+	END
+	DET_MASK	METADATA
+		BITPIX		S32	8
+	END
+	DET_WEIGHT	METADATA
+		BITPIX		S32	-32
+	END
+
+	SKY_IMAGE	METADATA
+		BITPIX		S32	-32
+	END
+	SKY_MASK	METADATA
+		BITPIX		S32	8
+	END
+	SKY_WEIGHT	METADATA
+		BITPIX		S32	-32
+	END
+
+	COMPRESSED_POSITIVE	METADATA
+		BITPIX		S32	16
+		SCALING		STR	STDEV_POSITIVE
+		STDEV.BITS	S32	4
+		STDEV.NUM	F32	10
+		COMPRESSION	STR	RICE
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+	COMPRESSED_MASK		METADATA
+		COMPRESSION	STR	PLIO
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+	COMPRESSED_SUBTRACTION	METADATA
+		BITPIX		S32	16
+		SCALING		STR	STDEV_BOTH
+		STDEV.BITS	S32	4
+		STDEV.NUM	F32	5
+		COMPRESSION	STR	RICE
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+
+END
+
+FILERULES METADATA
+   PSASTRO.INPUT       STR PSASTRO.INPUT.CMF
+   PSASTRO.OUTPUT      STR PSASTRO.OUTPUT.CMF
+   PSASTRO.OUTPUT.MEF  STR PSASTRO.OUTPUT.CMF
+   PSPHOT.OUTPUT       STR PSPHOT.OUTPUT.CMF
+
+   ### input file definitions
+   ### use @DETDB entries to get the detrend images from the database
+   ### replace @DETDB with @FILES if you want to require it from the 
+   ### command line, or with an explicit name to require a specific file
+   TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE 
+
+   ## files used by ppImage
+   PPIMAGE.INPUT      INPUT @FILES        FPA        IMAGE     
+   PPIMAGE.MASK       INPUT mask.fits     FPA        IMAGE     
+   PPIMAGE.BIAS       INPUT @DETDB        FPA        IMAGE     
+   PPIMAGE.DARK       INPUT @DETDB        FPA        DARK
+   PPIMAGE.FLAT       INPUT @DETDB        FPA        IMAGE     
+   PPIMAGE.FRINGE     INPUT @DETDB        FPA        FRINGE
+   PPIMAGE.SHUTTER    INPUT @DETDB        FPA        IMAGE     
+
+   ## Files used by ppMerge
+   PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+   PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+   PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+   ## files used to build and apply the flat-field correction images
+   DVOCORR.INPUT      INPUT @FILES        FPA        IMAGE
+   DVOCORR.REFHEAD    INPUT @FILES        FPA        HEADER
+   DVOFLAT.INPUT      INPUT @FILES        FPA        IMAGE
+   DVOFLAT.CORR       INPUT @DETDB        FPA        IMAGE
+
+   ## files used by psphot 
+   PSPHOT.LOAD        INPUT @FILES        FPA        IMAGE
+   PSPHOT.INPUT       INPUT @FILES        FPA        IMAGE     
+   PSPHOT.MASK        INPUT @FILES        FPA        MASK     
+   PSPHOT.WEIGHT      INPUT @FILES        FPA        WEIGHT     
+   PSPHOT.PSF.LOAD    INPUT @FILES        FPA        PSF       
+
+   ## files used by psastro 
+   PSASTRO.INPUT.CMP  INPUT @FILES        FPA        CMP       
+   PSASTRO.INPUT.CMF  INPUT @FILES        FPA        CMF       
+
+   ## files used by pswarp
+   PSWARP.INPUT       INPUT @FILES        FPA        IMAGE
+   PSWARP.WEIGHT      INPUT @FILES        FPA        WEIGHT
+   PSWARP.MASK        INPUT @FILES        FPA        MASK
+   PSWARP.SKYCELL     INPUT @FILES        FPA        IMAGE
+   PSWARP.ASTROM      INPUT @FILES        FPA        CMF
+
+   PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+   PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+   ### output file definitions
+   TYPE                  OUTPUT  FILENAME.RULE        FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+   PPIMAGE.OUTPUT      	 OUTPUT  {OUTPUT}.isp.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.MASK 	 OUTPUT  {OUTPUT}.mask.fits   MASK      NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.WEIGHT OUTPUT  {OUTPUT}.wt.fits     WEIGHT    NONE      FPA        TRUE      NONE
+   PPIMAGE.CHIP 	 OUTPUT  {OUTPUT}.chip.fits   IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.CHIP.MASK 	 OUTPUT  {OUTPUT}.chip.mask.fits MASK   NONE      FPA        TRUE      NONE
+   PPIMAGE.CHIP.WEIGHT 	 OUTPUT  {OUTPUT}.chip.wt.fits WEIGHT   NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.FPA1 	 OUTPUT  {OUTPUT}.b1.fits     IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.FPA2 	 OUTPUT  {OUTPUT}.b2.fits     IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.STATS 	 OUTPUT  {OUTPUT}.stats       STATS     NONE      FPA        TRUE      NONE
+
+   PPIMAGE.BIN1        	 OUTPUT  {OUTPUT}.b1c.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.BIN2        	 OUTPUT  {OUTPUT}.b2c.fits    IMAGE     NONE      FPA        TRUE      NONE
+
+   PPIMAGE.JPEG1       	 OUTPUT  {OUTPUT}.b1.jpg      JPEG      NONE      FPA        TRUE      NONE
+   PPIMAGE.JPEG2       	 OUTPUT  {OUTPUT}.b2.jpg      JPEG      NONE      FPA        TRUE      NONE
+
+   PPMERGE.OUTPUT.MASK   OUTPUT {OUTPUT}.fits         MASK      NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.BIAS   OUTPUT {OUTPUT}.fits         IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.DARK   OUTPUT {OUTPUT}.fits         DARK      NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.fits        IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.FLAT   OUTPUT {OUTPUT}.fits         IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.FRINGE OUTPUT {OUTPUT}.fits         FRINGE    NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.SIGMA  OUTPUT {OUTPUT}.sigma.fits   IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.COUNT  OUTPUT {OUTPUT}.count.fits   IMAGE     NONE      CHIP       TRUE      NONE
+
+   DVOCORR.OUTPUT    	 OUTPUT  {OUTPUT}.fc.fits     IMAGE     NONE      FPA        TRUE      NONE
+   DVOFLAT.OUTPUT    	 OUTPUT  {OUTPUT}.co.fits     IMAGE     NONE      FPA        TRUE      NONE
+
+   PSPHOT.RESID        	 OUTPUT  {OUTPUT}.res.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PSPHOT.BACKGND      	 OUTPUT  {OUTPUT}.bck.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PSPHOT.BACKSUB      	 OUTPUT  {OUTPUT}.sub.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PSPHOT.BACKMDL      	 OUTPUT  {OUTPUT}.mdl.fits    IMAGE     NONE      FPA        TRUE      NONE
+
+   PSPHOT.OUTPUT.RAW   	 OUTPUT  {OUTPUT}             RAW       NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.SX    	 OUTPUT  {OUTPUT}.sx          SX        NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.OBJ   	 OUTPUT  {OUTPUT}.obj         OBJ       NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.CMP   	 OUTPUT  {OUTPUT}.cmp         CMP       NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.CMF   	 OUTPUT  {OUTPUT}.cmf         CMF       NONE      FPA        TRUE      NONE
+
+   PSPHOT.PSF.SAVE     	 OUTPUT  {OUTPUT}.psf         PSF       NONE      FPA        TRUE      NONE
+
+   SOURCE.PLOT.MOMENTS   OUTPUT  {OUTPUT}.mnt.png     KAPA      NONE      FPA        TRUE      NONE
+   SOURCE.PLOT.PSFMODEL  OUTPUT  {OUTPUT}.psf.png     KAPA      NONE      FPA        TRUE      NONE
+   SOURCE.PLOT.APRESID   OUTPUT  {OUTPUT}.dap.png     KAPA      NONE      FPA        TRUE      NONE
+
+   PSASTRO.OUTPUT.CMP    OUTPUT  {OUTPUT}.smp         CMP       NONE      FPA        TRUE      NONE
+   PSASTRO.OUTPUT.CMF    OUTPUT  {OUTPUT}.smf         CMF       NONE      FPA        TRUE      NONE
+
+   PSWARP.OUTPUT         OUTPUT {OUTPUT}.fits         IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.MASK    OUTPUT {OUTPUT}.mask.fits    MASK      NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits      WEIGHT    NONE      FPA        TRUE      NONE
+   PSWARP.BIN1           OUTPUT {OUTPUT}.b1.fits      IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.BIN2           OUTPUT {OUTPUT}.b2.fits      IMAGE     NONE      FPA        TRUE      NONE
+
+   SKYCELL.STATS         OUTPUT {OUTPUT}.stats        STATS     NONE      FPA        TRUE      NONE
+   SKYCELL.TEMPLATE      OUTPUT {OUTPUT}.skycell      SKYCELL   NONE      FPA        TRUE      NONE
+
+   PPSIM.OUTPUT          OUTPUT {OUTPUT}.fits         IMAGE     NONE      FPA        TRUE      ISP
+   PPSIM.SOURCES   	 OUTPUT {OUTPUT}.cmf          CMF       NONE      FPA        TRUE      NONE
+
+   PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits         IMAGE     NONE      CHIP       TRUE      NONE
+   PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits         MASK      NONE      CHIP       TRUE      NONE
+
+   LOG.IMFILE            OUTPUT {OUTPUT}.log          TEXT      NONE      FPA        TRUE      NONE
+   LOG.EXP               OUTPUT {OUTPUT}.log          TEXT      NONE      FPA        TRUE      NONE
+END
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR cmf.hdr
+  CMF.DATA STR cmf.psf # use .PSF and .EXT?
+
+  PSF.HEAD  STR	hdr
+  PSF.TABLE STR psf_model
+  PSF.RESID STR psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/.cvsignore
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/.cvsignore	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/.cvsignore	(revision 17278)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/Makefile.am
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/Makefile.am	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/Makefile.am	(revision 17278)
@@ -0,0 +1,23 @@
+
+installdir = $(datadir)/ippconfig/lbc_red
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	rejections.config \
+	filerules.mdc
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/camera.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/camera.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/camera.config	(revision 17278)
@@ -0,0 +1,182 @@
+# Camera configuration file for LBCred: describes the camera
+
+# File formats that we know about
+FORMATS         METADATA
+        MEF 	STR     lbc_red/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+	CHIP1	STR	Cell
+	CHIP2	STR	Cell
+	CHIP3	STR	Cell
+	CHIP4	STR	Cell
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+   Y	  STR	Y-FAN
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID         METADATA
+        FPA     STR     fpa
+        CHIP    STR     {CHIP.NAME}
+        CELL    STR     {CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	isp		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  BinoBias STR BIAS
+  bias     STR BIAS
+  zero     STR BIAS
+  dark     STR DARK
+  flat     STR SKYFLAT
+  SkyFlat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        # Other recipes
+        PSPHOT          STR     lbc_red/psphot.config           # psphot details
+        PSASTRO         STR     lbc_red/psastro.config          # psastro details
+        PPIMAGE         STR     lbc_red/ppImage.config          # ppImage recipe
+	REJECTIONS      STR     lbc_red/rejections.config       # rejection recipe
+END
+
+REDUCTION	METADATA
+	DETREND		METADATA
+	# doesn't the following entry mean there should be a ppimage_o recipe in ppImage.config???
+		BIAS_PROCESS	STR	PPIMAGE_O
+		BIAS_RESID	STR	PPIMAGE_B
+		BIAS_VERIFY	STR	PPIMAGE_OB
+                BIAS_STACK      STR     PPMERGE_BIAS
+		DARK_PROCESS	STR	PPIMAGE_OB
+		DARK_RESID	STR	PPIMAGE_D
+		DARK_VERIFY	STR	PPIMAGE_OBD
+                DARK_STACK      STR     PPMERGE_DARK
+		SHUTTER_PROCESS	STR	PPIMAGE_OBD
+		SHUTTER_RESID	STR	PPIMAGE_S
+		SHUTTER_VERIFY	STR	PPIMAGE_OBDS
+                SHUTTER_STACK   STR     PPMERGE_SHUTTER
+		FLAT_PROCESS	STR	PPIMAGE_OBDS
+		FLAT_RESID	STR	PPIMAGE_F
+		FLAT_VERIFY	STR	PPIMAGE_OBDSF
+                FLAT_STACK      STR     PPMERGE_FLAT
+		FRINGE_PROCESS	STR	PPIMAGE_OBDSF
+		FRINGE_RESID	STR	PPIMAGE_R
+		FRINGE_VERIFY	STR	PPIMAGE_OBDSFR
+                FRINGE_STACK    STR     PPMERGE_FRINGE
+
+		# Generation of pixel masks from darks and flats
+		DARKMASK_PROCESS	STR	PPIMAGE_OBD
+		DARKMASK_RESID		STR	PPIMAGE_N
+		DARKMASK_VERIFY		STR	PPIMAGE_OBD
+		DARKMASK_STACK		STR	PPMERGE_MASK
+		FLATMASK_PROCESS	STR	PPIMAGE_OBDSF
+		FLATMASK_RESID		STR	PPIMAGE_N
+		FLATMASK_VERIFY		STR	PPIMAGE_OBDSF
+		FLATMASK_STACK		STR	PPMERGE_MASK
+		JPEG_BIN1_IMAGE_DARKMASK STR	PPIMAGE_J1_IMAGE_M
+		JPEG_BIN2_IMAGE_DARKMASK STR	PPIMAGE_J2_IMAGE_M
+		JPEG_BIN1_IMAGE_FLATMASK STR	PPIMAGE_J1_IMAGE_M
+		JPEG_BIN2_IMAGE_FLATMASK STR	PPIMAGE_J2_IMAGE_M
+		JPEG_BIN1_RESID_DARKMASK STR	PPIMAGE_J1_RESID_M
+		JPEG_BIN2_RESID_DARKMASK STR	PPIMAGE_J2_RESID_M
+		JPEG_BIN1_RESID_FLATMASK STR	PPIMAGE_J1_RESID_M
+		JPEG_BIN2_RESID_FLATMASK STR	PPIMAGE_J2_RESID_M
+
+ 		JPEG_BIN1_IMAGE_BIAS     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_DARK     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_SHUTTER  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FLAT     STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_DOMEFLAT STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_SKYFLAT  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FRINGE   STR  PPIMAGE_J1_IMAGE_R
+ 		JPEG_BIN2_IMAGE_BIAS     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_DARK     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_SHUTTER  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FLAT     STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_DOMEFLAT STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_SKYFLAT  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FRINGE   STR  PPIMAGE_J2_IMAGE_R
+
+ 		JPEG_BIN1_RESID_BIAS     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_DARK     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_SHUTTER  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FLAT     STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_DOMEFLAT STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_SKYFLAT  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FRINGE   STR  PPIMAGE_J1_RESID_R
+ 		JPEG_BIN2_RESID_BIAS     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_DARK     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_SHUTTER  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FLAT     STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_DOMEFLAT STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_SKYFLAT  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FRINGE   STR  PPIMAGE_J2_RESID_R
+	END
+
+	# basic science analysis
+	DEFAULT		METADATA
+		CHIP		STR	PPIMAGE_OBDSFRA
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+
+        # photometry & astrometry for pre-detrended images
+	PROCESSED	METADATA
+		CHIP		STR	PPIMAGE_PA
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+
+	END
+
+	# Detrend Processing only for raw data
+	DETREND_ONLY		METADATA
+		CHIP		STR	PPIMAGE_DET_ONLY
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+END
+
+FITS    METADATA
+# BITPIX is the bits per pixel for writing the output data
+# COMP = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ] are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE [0..16] is the number of "noise bits" to preserve when quantising floating point data; 16 for no loss
+# HSCALE is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+        TYPE            FITS    FLOAT	BITPIX  COMP     TILE.X  TILE.Y  TILE.Z  NOISE   HSCALE  HSMOOTH
+        DET_IMAGE       FITS    NONE	-32     RICE     0       1       1       16      0       0
+        DET_MASK        FITS    NONE	8       PLIO     0       1       1       0       0       0
+        DET_WEIGHT      FITS    NONE	-32     RICE     0       1       1       16      0       0
+        SKY_IMAGE       FITS    NONE	-32     RICE     0       1       1       16      0       0
+        SKY_MASK        FITS    NONE	8       PLIO     0       1       1       0       0       0
+        SKY_WEIGHT      FITS    NONE	-32     RICE     0       1       1       16      0       0
+END
+
+FILERULES	STR	lbc_red/filerules.mdc	# File rules
+
+EXTNAME.RULES METADATA
+  CMF.HEAD   STR {CHIP.NAME}.hdr
+  CMF.DATA   STR {CHIP.NAME}.psf # use .PSF and .EXT?
+
+  PSF.HEAD   STR {CHIP.NAME}.hdr
+  PSF.TABLE  STR {CHIP.NAME}.psf_model
+  PSF.RESID  STR {CHIP.NAME}.psf_resid
+
+  REF.ASTROM STR {CHIP.NAME}.ref_astrom
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/dvo.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/dvo.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/dvo.config	(revision 17278)
@@ -0,0 +1,50 @@
+
+# location of DVO database tables
+CATDIR			/data/ipp001.0/jester/stripe82/coadd/catdir
+
+# keywords used by DVO to interpret the headers
+
+# used by parse_time to find time-related keywords
+MJD-KEYWORD		NONE
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		YYYY-MM-DD
+UT-KEYWORD		TAIHMS
+JD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		-7.0
+CAL_INSTMAG_MIN		-9.5
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# allowed astrometry error (arcseconds)
+ADDSTAR_MAX_CERROR	5.0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		5.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+CATMODE			MEF
+CATFORMAT		PANSTARRS_DEV_0
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/dvo.layout
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/dvo.layout	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/dvo.layout	(revision 17278)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/filerules.mdc
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/filerules.mdc	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/filerules.mdc	(revision 17278)
@@ -0,0 +1,165 @@
+### File rules for PHU=FPA, EXT=CHIP
+
+### Redirections
+PPIMAGE.OUTPUT        STR PPIMAGE.OUTPUT.MEF
+PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.MEF
+PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.MEF
+PPIMAGE.CHIP          STR PPIMAGE.CHIP.MEF
+PPIMAGE.CHIP.MASK     STR PPIMAGE.CHIP.MK.MEF
+PPIMAGE.CHIP.WEIGHT   STR PPIMAGE.CHIP.WT.MEF
+PPIMAGE.OUTPUT.FPA1   STR PPIMAGE.OUTPUT.FPA1.MEF
+PPIMAGE.OUTPUT.FPA2   STR PPIMAGE.OUTPUT.FPA2.MEF
+PPIMAGE.BIN1          STR PPIMAGE.BIN1.MEF
+PPIMAGE.BIN2          STR PPIMAGE.BIN2.MEF
+
+PPSTAMP.CHIP          STR PPSTAMP.CHIP.MEF
+
+PSASTRO.INPUT         STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT        STR PSASTRO.OUT.CMF.MEF
+PSASTRO.OUTPUT.MEF    STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT         STR PSPHOT.OUT.CMF.MEF
+DVOCORR.OUTPUT        STR DVOCORR.MEF.OUTPUT
+DVOFLAT.OUTPUT        STR DVOFLAT.MEF.OUTPUT
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPIMAGE.MASK       INPUT @DETDB        CHIP       MASK
+PPIMAGE.BIAS       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.DARK       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FLAT       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE
+PPIMAGE.SHUTTER    INPUT @DETDB        CHIP       IMAGE     
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER
+DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE
+
+## files used by psphot 
+PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE
+PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE
+PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD    INPUT @FILES        CHIP       PSF       
+
+## files used by psastro 
+PSASTRO.INPUT.CMP  INPUT @FILES        CHIP       CMP
+PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF
+
+## files used by pswarp
+PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE
+PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+PSWARP.MASK        INPUT @FILES        CHIP       MASK
+PSWARP.SKYCELL     INPUT @FILES        CHIP       IMAGE
+PSWARP.ASTROM      INPUT @FILES        CHIP       CMF
+
+PPSUB.INPUT        INPUT @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK   INPUT @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT INPUT @FILES        FPA        WEIGHT
+PPSUB.REF          INPUT @FILES        FPA        IMAGE
+PPSUB.REF.MASK     INPUT @FILES        FPA        MASK
+PPSUB.REF.WEIGHT   INPUT @FILES        FPA        WEIGHT
+PPSUB.SOURCES      INPUT @FILES        FPA        CMF
+
+PPSTACK.INPUT      INPUT @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK INPUT @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA        WEIGHT
+PPSTACK.SOURCES    INPUT @FILES        FPA        CMF
+
+PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+### output file definitions
+TYPE                  OUTPUT FILENAME.RULE                    FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT.MEF    OUTPUT {OUTPUT}.b0.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.OUT.MK.MEF    OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      CHIP       TRUE      MEF
+PPIMAGE.OUT.WT.MEF    OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      CHIP       TRUE      MEF
+PPIMAGE.OUTPUT.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.b0.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.MK.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits     MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.WT.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits     WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.MK.MEF   OUTPUT {OUTPUT}.ch.mk.fits              MASK      NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.WT.MEF   OUTPUT {OUTPUT}.ch.wt.fits              WEIGHT    NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.MK.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits  MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.WT.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits  WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.OUTPUT.FPA1.MEF OUTPUT {OUTPUT}.b1.fits               IMAGE     NONE      FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA2.MEF OUTPUT {OUTPUT}.b2.fits               IMAGE     NONE      FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA1.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits   IMAGE     NONE      FPA        TRUE      SPLIT
+PPIMAGE.OUTPUT.FPA2.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits   IMAGE     NONE      FPA        TRUE      SPLIT
+
+PPIMAGE.STATS         OUTPUT {OUTPUT}.stats                   STATS     NONE      FPA        TRUE      MEF
+
+PPIMAGE.BIN1.MEF      OUTPUT {OUTPUT}.b1c.fits                IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.BIN2.MEF      OUTPUT {OUTPUT}.b2c.fits                IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.BIN1.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.BIN2.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.JPEG1         OUTPUT {OUTPUT}.b1.jpg                  JPEG      NONE      FPA        TRUE      NONE
+PPIMAGE.JPEG2         OUTPUT {OUTPUT}.b2.jpg                  JPEG      NONE      FPA        TRUE      NONE
+
+DVOCORR.MEF.OUTPUT    OUTPUT {OUTPUT}.fc.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+DVOCORR.SPL.OUTPUT    OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+DVOFLAT.MEF.OUTPUT    OUTPUT {OUTPUT}.co.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+DVOFLAT.SPL.OUTPUT    OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PSPHOT.RESID          OUTPUT {OUTPUT}.res.fits                IMAGE     NONE      CHIP       FALSE     NONE
+PSPHOT.BACKGND        OUTPUT {OUTPUT}.bck.fits                IMAGE     NONE      CHIP       FALSE     NONE
+PSPHOT.BACKSUB        OUTPUT {OUTPUT}.sub.fits                IMAGE     NONE      CHIP       FALSE     NONE
+PSPHOT.BACKMDL        OUTPUT {OUTPUT}.mdl.fits                IMAGE     NONE      CHIP       FALSE     NONE
+
+PSPHOT.OUTPUT.RAW     OUTPUT {OUTPUT}.{CHIP.NAME}             RAW       NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX      OUTPUT {OUTPUT}.sx                      SX        NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.OBJ     OUTPUT {OUTPUT}.obj                     OBJ       NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.CMP     OUTPUT {OUTPUT}.{CHIP.NAME}.cmp         CMP       NONE      CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.cmf         CMF       NONE      CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.MEF    OUTPUT {OUTPUT}.cmf                     CMF       NONE      CHIP       TRUE      NONE
+
+PSPHOT.PSF.SAVE       OUTPUT {OUTPUT}.psf                     PSF       NONE      CHIP       TRUE      NONE
+
+SOURCE.PLOT.MOMENTS   OUTPUT {OUTPUT}.mnt.png                 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL  OUTPUT {OUTPUT}.psf.png                 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.APRESID   OUTPUT {OUTPUT}.dap.png                 KAPA      NONE      FPA        TRUE      NONE
+
+PSASTRO.OUTPUT.CMP    OUTPUT {OUTPUT}.{CHIP.NAME}.smp         CMP       NONE      CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.smf         CMF       NONE      CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.MEF   OUTPUT {OUTPUT}.smf                     CMF       NONE      FPA        TRUE      NONE
+PSASTRO.OUT.ASTROM    OUTPUT {OUTPUT}.ast                     ASTROM    NONE      FPA        TRUE      NONE
+
+PSWARP.OUTPUT         OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK    OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES OUTPUT {OUTPUT}.cmf                     CMF       NONE      FPA        TRUE      NONE
+PSWARP.BIN1           OUTPUT {OUTPUT}.b1.fits                 IMAGE     NONE      FPA        TRUE      NONE
+PSWARP.BIN2           OUTPUT {OUTPUT}.b2.fits                 IMAGE     NONE      FPA        TRUE      NONE
+
+PPSUB.OUTPUT          OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK     OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTACK.OUTPUT        OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK   OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTAMP.OUTPUT        OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSTAMP.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits                 IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.fits        IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                    MASK      NONE      CHIP       TRUE      NONE
+
+LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log         TEXT      NONE      CHIP       TRUE      NONE
+LOG.EXP               OUTPUT {OUTPUT}.log                     TEXT      NONE      FPA        TRUE      NONE
+
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/format.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/format.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/format.config	(revision 17278)
@@ -0,0 +1,154 @@
+# LBCred format.config, copied from MegaCam format_mef.config
+# Like Megacam, LBC has an FPA in a multi-extension fits file, with one chip per extension
+
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	LBT-DX
+	INSTRUME	STR	LBC-RED
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.NAME	STR	OBS_ID	# A PHU keyword for unique identifier
+	CHIP.NAME	STR	EXTNAME	# An extension keyword for unique identifie
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chipName:chipType
+	LBCCHIP1	STR	CHIP1:Chip
+	LBCCHIP2	STR	CHIP2:Chip
+	LBCCHIP3	STR	CHIP3:Chip
+	LBCCHIP4	STR	CHIP4:Chip
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	Chip	STR	Cell:Cell
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts to use
+	Cell	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	TRIMSEC
+	# Not sure about these...
+		CELL.X0			S32	0
+		CELL.Y0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAIN
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     OBSRA
+        FPA.DEC         STR     OBSDEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.OBSTYPE	STR	IMAGETYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.FOCUS	STR	Z4 # ???
+	FPA.TIME	STR	MJD_OBS
+#	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	CCDTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     EXPTIME
+        CELL.READNOISE  STR     RDNOISE
+	CELL.TIME	STR	MJD_OBS
+#	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     LBCBIN
+        CELL.YBIN       STR     LBCBIN
+	CELL.SATURATION	STR	SATURATE
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	LBT-DX
+	FPA.INSTRUMENT		STR	LBC-RED		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	LBC-RED
+	CELL.TIMESYS		STR	UTC
+	FPA.TIMESYS		STR	UTC
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4608
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4608
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	65536.0
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	# Do the x0, y0 positions need to account for the gaps??? 
+	# How do I specify rotation?
+	# 
+	# LBC layout is
+	#
+	#   444444444
+	#   444444444
+	#   444444444
+	#
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	# 
+	CHIP.X0		METADATA
+		CHIP1	S32	4096
+		CHIP2	S32	2048
+		CHIP3	S32	0
+	# This is probably wrong!
+		CHIP4	S32	1024
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		CHIP1	S32	0
+		CHIP2	S32	0
+		CHIP3	S32	0
+		CHIP4	S32	4608
+	END
+	CHIP.XPARITY	S32	1
+	CHIP.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
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/ppImage.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/ppImage.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/ppImage.config	(revision 17278)
@@ -0,0 +1,173 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN               S32     18
+BIN1.YBIN               S32     18
+BIN2.XBIN               S32     144
+BIN2.YBIN               S32     144
+
+# LBCred  needs fringe subtraction (?)
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+FRINGE.FILTERS	STR y
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END 
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/ppMerge.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/ppMerge.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/ppMerge.config	(revision 17278)
@@ -0,0 +1,44 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	MASKVAL		S32	0xff		# Mask value for input data
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_MASK	METADATA
+END
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/psastro.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/psastro.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/psastro.config	(revision 17278)
@@ -0,0 +1,67 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32    1000.
+PSASTRO.GRID.SCALE     F32      50.
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+PSASTRO.MATCH.RADIUS   F32     12.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -14.5  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32    12.0
+PSASTRO.MOSAIC.RADIUS.N1    F32    0.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N2    F32    0.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32    6.0 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32      2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32      4   # number of y-dir cells per chip
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    2.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+END
+
+# XXX Test for taurus
+# PSASTRO.GRID.MIN.SIGMA F32  3.0
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/psphot.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/psphot.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/psphot.config	(revision 17278)
@@ -0,0 +1,42 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS     	BOOL    TRUE
+
+BACKGROUND.XBIN	    S32  128            # size of background superpixels
+BACKGROUND.YBIN	    S32  128            # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA      F32  2.0             # statistic used to measure background
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  50.0
+
+# OUTPUT.FORMAT       STR SMPDATA
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MAX_CHI          F32  50.0		 # reject objects worse that this
+
+#PSF.TREND.MODE STR MAP
+#PSF.TREND.NX   S32 1
+#PSF.TREND.NY   S32 1
+
+DIAGNOSTIC.PLOTS		METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32 -1
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32 12
+END
+
Index: /branches/eam_branch_20080324/ippconfig/lbc_red/rejections.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/lbc_red/rejections.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/lbc_red/rejections.config	(revision 17278)
@@ -0,0 +1,38 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
Index: /branches/eam_branch_20080324/ippconfig/megacam/filerules-mef.mdc
===================================================================
--- /branches/eam_branch_20080324/ippconfig/megacam/filerules-mef.mdc	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/megacam/filerules-mef.mdc	(revision 17278)
@@ -0,0 +1,195 @@
+### File rules for PHU=FPA, EXT=CHIP
+
+### Redirections
+PPIMAGE.OUTPUT        STR PPIMAGE.OUTPUT.MEF
+PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.MEF
+PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.MEF
+
+PPIMAGE.CHIP          STR PPIMAGE.CHIP.MEF
+PPIMAGE.CHIP.MASK     STR PPIMAGE.CHIP.MK.MEF
+PPIMAGE.CHIP.WEIGHT   STR PPIMAGE.CHIP.WT.MEF
+
+PPIMAGE.OUTPUT.FPA1   STR PPIMAGE.OUTPUT.FPA1.MEF
+PPIMAGE.OUTPUT.FPA2   STR PPIMAGE.OUTPUT.FPA2.MEF
+PPIMAGE.BIN1          STR PPIMAGE.BIN1.MEF
+PPIMAGE.BIN2          STR PPIMAGE.BIN2.MEF
+
+PPSTAMP.CHIP          STR PPSTAMP.CHIP.MEF
+
+PSASTRO.INPUT         STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT        STR PSASTRO.OUT.CMF.MEF
+PSASTRO.OUTPUT.MEF    STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT         STR PSPHOT.OUT.CMF.MEF
+
+DVOCORR.OUTPUT        STR DVOCORR.MEF.OUTPUT
+DVOFLAT.OUTPUT        STR DVOFLAT.MEF.OUTPUT
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPIMAGE.MASK       INPUT @DETDB        CHIP       MASK
+PPIMAGE.BIAS       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.DARK       INPUT @DETDB        CHIP       DARK
+PPIMAGE.FLAT       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE
+PPIMAGE.SHUTTER    INPUT @DETDB        CHIP       IMAGE     
+
+## Files used by ppMerge
+PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER
+DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE
+
+## files used by psphot 
+PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE
+PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE
+PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD    INPUT @FILES        CHIP       PSF       
+PSPHOT.INPUT.CMF   INPUT @FILES        CHIP       CMF       
+
+## files used by psastro 
+PSASTRO.WCS        INPUT none.fits     CHIP       CMF
+PSASTRO.MODEL      INPUT @DETDB        FPA        ASTROM
+PSASTRO.INPUT.CMP  INPUT @FILES        CHIP       CMP
+PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF
+
+## files used by pswarp
+PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE
+PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+PSWARP.MASK        INPUT @FILES        CHIP       MASK
+PSWARP.SKYCELL     INPUT @FILES        CHIP       IMAGE
+PSWARP.ASTROM      INPUT @FILES        CHIP       CMF
+
+PPSUB.INPUT        INPUT @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK   INPUT @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT INPUT @FILES        FPA        WEIGHT
+PPSUB.REF          INPUT @FILES        FPA        IMAGE
+PPSUB.REF.MASK     INPUT @FILES        FPA        MASK
+PPSUB.REF.WEIGHT   INPUT @FILES        FPA        WEIGHT
+PPSUB.SOURCES      INPUT @FILES        FPA        CMF
+
+PPSTACK.INPUT      INPUT @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK INPUT @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA        WEIGHT
+PPSTACK.INPUT.PSF  INPUT @FILES        CHIP       PSF
+PPSTACK.SOURCES    INPUT @FILES        FPA        CMF
+
+PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+PPARITH.INPUT.IMAGE INPUT @FILES       CHIP       IMAGE
+PPARITH.INPUT.MASK  INPUT @FILES       CHIP       MASK
+
+### output file definitions
+TYPE                  OUTPUT FILENAME.RULE                    FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT.MEF    OUTPUT {OUTPUT}.b0.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.OUT.MK.MEF    OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      CHIP       TRUE      MEF
+PPIMAGE.OUT.WT.MEF    OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      CHIP       TRUE      MEF
+PPIMAGE.OUTPUT.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.b0.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.MK.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits     MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.WT.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits     WEIGHT    NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUTPUT.DETMASK OUTPUT {OUTPUT}.fits                   IMAGE     MASK      CHIP       TRUE      MEF
+
+PPIMAGE.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.MK.MEF   OUTPUT {OUTPUT}.ch.mk.fits              MASK      NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.WT.MEF   OUTPUT {OUTPUT}.ch.wt.fits              WEIGHT    NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.MK.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits  MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.WT.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits  WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.OUTPUT.FPA1.MEF OUTPUT {OUTPUT}.b1.fits               IMAGE     NONE      FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA2.MEF OUTPUT {OUTPUT}.b2.fits               IMAGE     NONE      FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA1.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits   IMAGE     NONE      FPA        TRUE      SPLIT
+PPIMAGE.OUTPUT.FPA2.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits   IMAGE     NONE      FPA        TRUE      SPLIT
+
+PPIMAGE.STATS         OUTPUT {OUTPUT}.stats                   STATS     NONE      FPA        TRUE      MEF
+
+PPIMAGE.BIN1.MEF      OUTPUT {OUTPUT}.b1c.fits                IMAGE     NONE  CHIP       TRUE      MEF
+PPIMAGE.BIN2.MEF      OUTPUT {OUTPUT}.b2c.fits                IMAGE     NONE  CHIP       TRUE      MEF
+PPIMAGE.BIN1.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits     IMAGE     NONE  CHIP       TRUE      SPLIT
+PPIMAGE.BIN2.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits     IMAGE     NONE  CHIP       TRUE      SPLIT
+
+PPIMAGE.JPEG1         OUTPUT {OUTPUT}.b1.jpg                  JPEG      NONE      FPA        TRUE      NONE
+PPIMAGE.JPEG2         OUTPUT {OUTPUT}.b2.jpg                  JPEG      NONE      FPA        TRUE      NONE
+
+PPMERGE.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                    MASK      NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.BIAS   OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.DARK   OUTPUT {OUTPUT}.fits                    DARK      NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.fits                   IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FLAT   OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FRINGE OUTPUT {OUTPUT}.fits                    FRINGE    NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SIGMA  OUTPUT {OUTPUT}.sigma.fits              IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT  OUTPUT {OUTPUT}.count.fits              IMAGE     NONE      CHIP       TRUE      NONE
+
+DVOCORR.MEF.OUTPUT    OUTPUT {OUTPUT}.fc.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+DVOCORR.SPL.OUTPUT    OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+DVOFLAT.MEF.OUTPUT    OUTPUT {OUTPUT}.co.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+DVOFLAT.SPL.OUTPUT    OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PSPHOT.RESID          OUTPUT {OUTPUT}.res.fits                IMAGE     NONE      CHIP       FALSE     MEF
+PSPHOT.BACKGND        OUTPUT {OUTPUT}.bck.fits                IMAGE     NONE      CHIP       FALSE     MEF
+PSPHOT.BACKSUB        OUTPUT {OUTPUT}.sub.fits                IMAGE     NONE      CHIP       FALSE     MEF
+PSPHOT.BACKMDL        OUTPUT {OUTPUT}.mdl.fits                IMAGE     NONE      CHIP       FALSE     MEF
+
+PSPHOT.OUTPUT.RAW     OUTPUT {OUTPUT}.{CHIP.NAME}             RAW       NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX      OUTPUT {OUTPUT}.{CHIP.NAME}.sx          SX        NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.OBJ     OUTPUT {OUTPUT}.{CHIP.NAME}.obj         OBJ       NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CMP     OUTPUT {OUTPUT}.{CHIP.NAME}.cmp         CMP       NONE      CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.cmf         CMF       NONE      CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.MEF    OUTPUT {OUTPUT}.cmf                     CMF       NONE      CHIP       TRUE      MEF
+
+PSPHOT.PSF.SAVE       OUTPUT {OUTPUT}.psf                     PSF       NONE      CHIP       TRUE      MEF
+
+SOURCE.PLOT.MOMENTS   OUTPUT {OUTPUT}.mnt.png                 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL  OUTPUT {OUTPUT}.psf.png                 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.APRESID   OUTPUT {OUTPUT}.dap.png                 KAPA      NONE      FPA        TRUE      NONE
+
+PSASTRO.OUTPUT.CMP    OUTPUT {OUTPUT}.{CHIP.NAME}.smp         CMP       NONE      CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.smf         CMF       NONE      CHIP       TRUE      SPLIT
+PSASTRO.OUT.CMF.MEF   OUTPUT {OUTPUT}.smf                     CMF       NONE      FPA        TRUE      MEF
+PSASTRO.OUT.MODEL     OUTPUT {OUTPUT}.asm              ASTROM.MODEL     NONE      FPA        TRUE      NONE
+PSASTRO.OUT.REFSTARS  OUTPUT {OUTPUT}.aref.fits        ASTROM.REFSTARS  NONE      FPA        TRUE      NONE
+
+PSWARP.OUTPUT         OUTPUT {OUTPUT}.fits                    IMAGE     NONE     FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK    OUTPUT {OUTPUT}.mk.fits                 MASK      NONE     FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE     FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES OUTPUT {OUTPUT}.cmf                     CMF       NONE     FPA        TRUE      NONE
+PSWARP.BIN1           OUTPUT {OUTPUT}.b1.fits                 IMAGE     NONE     FPA        TRUE      NONE
+PSWARP.BIN2           OUTPUT {OUTPUT}.b2.fits                 IMAGE     NONE     FPA        TRUE      NONE
+
+SKYCELL.STATS         OUTPUT {OUTPUT}.stats                   STATS     NONE      FPA        TRUE      NONE
+SKYCELL.TEMPLATE      OUTPUT {OUTPUT}.skycell                 SKYCELL   NONE      FPA        TRUE      NONE
+
+PPSUB.OUTPUT          OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK     OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTACK.OUTPUT        OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK   OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTAMP.OUTPUT        OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSTAMP.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits                 IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT.MEF      OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      MEF
+PPSIM.OUTPUT.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.fits        IMAGE     NONE      CHIP       TRUE      SPLIT
+PPSIM.SOURCES         OUTPUT {OUTPUT}.cmf                     CMF       NONE      FPA        TRUE      MEF
+
+PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits                    IMAGE     NONE  CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                    MASK      MASK CHIP       TRUE      NONE
+
+LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log         TEXT      NONE      CHIP       TRUE      NONE
+LOG.EXP               OUTPUT {OUTPUT}.log                     TEXT      NONE      FPA        TRUE      NONE
+
+TRACE.IMFILE          OUTPUT {OUTPUT}.{CHIP.NAME}.trace       TEXT      NONE      CHIP       TRUE      NONE
+TRACE.EXP             OUTPUT {OUTPUT}.trace                   TEXT      NONE      FPA        TRUE      NONE
Index: /branches/eam_branch_20080324/ippconfig/megacam/ppImage.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/megacam/ppImage.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/megacam/ppImage.config	(revision 17278)
@@ -0,0 +1,172 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN               S32     18
+BIN1.YBIN               S32     18
+BIN2.XBIN               S32     144
+BIN2.YBIN               S32     144
+
+# megacam needs fringe subtraction
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END 
Index: /branches/eam_branch_20080324/ippconfig/megacam/ppMerge.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/megacam/ppMerge.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/megacam/ppMerge.config	(revision 17278)
@@ -0,0 +1,40 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+END
+
Index: /branches/eam_branch_20080324/ippconfig/recipes/ppMerge.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/recipes/ppMerge.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/recipes/ppMerge.config	(revision 17278)
@@ -0,0 +1,95 @@
+# Recipe configuration for ppMerge
+
+ROWS            S32     128		# Number of rows to read at once
+ELECTRONS       F32     100.0           # Minimum number of electrons for useful signal
+SAMPLE          S32     100000          # Sampling factor for measuring the background
+REJ		F32	3.0		# Rejection threshold (sigma)
+ITER		S32	0		# Number of rejection iterations
+FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+FRACLOW		F32	0.0		# Fraction of low pixels to reject immediately
+NKEEP		S32	5		# Minimum number of pixels in stack to keep
+WEIGHTS		BOOL	FALSE		# Use image weights in combination?
+FRINGE.NUM	S32	10000		# Number of fringe regions
+FRINGE.SIZE	S32	5		# Half-size of fringe regions
+FRINGE.XSMOOTH	S32	5		# Number of smoothing regions in x
+FRINGE.YSMOOTH	S32	11		# Number of smoothing regions in y
+SHUTTER.SIZE	S32	128		# Size for shutter measurement regions
+MASK.SUSPECT	F32	5.0		# Threshold for suspect pixels (sigma)
+MASK.BAD	F32	0.2		# Threshold for bad pixels
+MASK.MODE	STR	FRACTION	# Mode for identifying bad pixels in the suspect map
+MASK.CHIPSTATS	BOOL	TRUE		# Measure stats for masking by chip (otherwise by readout)?
+MASK.GROW	S32	0		# Grow bad pixels by this radius
+MASK.GROWVAL	STR	SUSPECT		# Give grown mask pixels this value
+MASKVAL		STR	SAT,BAD		# Mask value for input data
+COMBINE		STR	CLIPPED		# Statistic to use for combination
+MEAN		STR	ROBUST_MEDIAN	# Statistic to use to measure the mean
+STDEV		STR	ROBUST_STDEV	# Statistic to use to measure the stdev
+
+STATS.BY.CHIP   BOOL    TRUE            # measure stats for masking by chip (or by readout)
+MASK.GROW.NPIX  S32     3               # measure stats for masking by chip (or by readout)
+
+# Ordinates for fitting dark current
+DARK.ORDINATES	METADATA
+	CELL.DARKTIME	S32	1	# Traditional dark current term
+END
+DARK.NORM	STR	NONE		# Dark normalisation concept
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	2		# Number of rejection iterations
+	FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.0		# Fraction of low pixels to reject immediately
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+	COMBINE		STR	CLIPPED		# Statistic to use for combination: 
+END
+
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	2		# Number of rejection iterations
+	FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.0		# Fraction of low pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_DARKMASK METADATA
+	ITER		S32	2		# Number of iterations
+	MASK.BAD	F32	0.2		# Threshold for bad pixels (sigma)
+	MASK.MODE	STR	FRACTION	# Mode for identifying bad pixels in the suspect map
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_FLATMASK METADATA
+	ITER		S32	2		# Number of iterations
+	MASK.BAD	F32	0.2		# Threshold for bad pixels (sigma)
+	MASK.MODE	STR	FRACTION	# Mode for identifying bad pixels in the suspect map
+END
+
+# Shutter generation --- already included in default, above
+PPMERGE_SHUTTER	METADATA
+	REJ		F32	2.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+END
Index: /branches/eam_branch_20080324/ippconfig/recipes/ppStack.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/recipes/ppStack.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/recipes/ppStack.config	(revision 17278)
@@ -0,0 +1,26 @@
+# Recipe configuration for ppStack (image combination)
+
+ITER		S32	1		# Number of rejection iterations
+COMBINE.REJ	F32	4.0		# Rejection threshold in combination (sigma)
+MASK.BAD	STR	BLANK,SAT,BAD	# Mask value of bad pixels
+MASK.BLANK	STR	BLANK		# Mask value to give blank pixels
+THRESHOLD.MASK	F32	0.8		# Threshold for mask deconvolution (0..1)
+IMAGE.REJ	F32	0.2		# Rejected pixel fraction threshold for rejecting entire image
+ROWS		S32	64		# Number of rows to read at once
+VARIANCE	BOOL	TRUE		# Use variance in rejection?
+SAFE		BOOL	TRUE		# Play safe when combining small number of values?
+
+RENORM		BOOL	TRUE		# Renormalise variance maps?
+RENORM.MEAN	STR	ROBUST_MEDIAN	# Statistic to use for mean in renormalisation
+RENORM.STDEV	STR	ROBUST_STDEV	# Statistic to use for stdev in renormalisation
+RENORM.WIDTH	S32	300		# Size of renormalisation boxes (pixels)
+
+PSF.INSTANCES	S32	5		# Number of instances for PSF generation
+PSF.RADIUS	F32	20.0		# Radius for PSF generation
+PSF.ORDER	S32	3		# Order of spatial variation for PSF generation
+PSF.MODEL	STR	PS_MODEL_RGAUSS	# Model for PSF generation
+
+TEMP.IMAGE	STR	conv.im.fits	# Suffix for convolved images
+TEMP.MASK	STR	conv.mk.fits	# Suffix for convolved masks
+TEMP.WEIGHT	STR	conv.wt.fits	# Suffix for convolved weight maps
+TEMP.DELETE	BOOL	FALSE		# Delete temporary files on completion?
Index: /branches/eam_branch_20080324/ippconfig/simmosaic/camera.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/simmosaic/camera.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/simmosaic/camera.config	(revision 17278)
@@ -0,0 +1,388 @@
+# Camera configuration file for simulated mosaic
+
+# File formats that we know about
+FORMATS         METADATA
+	TOGETHER STR	simmosaic/format_together.config
+	SPLIT	STR	simmosaic/format_split.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip00		STR	Cell00 Cell01 Cell10 Cell11
+        Chip01		STR	Cell00 Cell01 Cell10 Cell11
+        Chip10		STR	Cell00 Cell01 Cell10 Cell11
+        Chip11		STR	Cell00 Cell01 Cell10 Cell11
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+	NONE	STR	NONE
+	B	STR	B
+	V	STR	V
+	R	STR	R
+	I	STR	I
+	g	STR	g
+	r	STR	r
+	i	STR	i
+	z	STR	z
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+	CHIP	STR	{CHIP.NAME}
+	CELL	STR	{CHIP.NAME}.{CELL.NAME}
+	fpa	STR	fpa
+	chip	STR	{CHIP.NAME}
+	cell	STR	{CHIP.NAME}.{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	simmosaic		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+        PPIMAGE         STR     simmosaic/ppImage.config	# Default: all (normal) options on
+	PSPHOT		STR	simmosaic/psphot.config		# psphot details
+	PSASTRO		STR	simmosaic/psastro.config	# psastro details
+	REJECTIONS	STR	simmosaic/rejections.config	# Rejection limits
+END
+
+# Reduction classes
+REDUCTION	METADATA
+	# Detrend processing
+	DETREND		METADATA
+		BIAS_PROCESS	STR	PPIMAGE_O
+		BIAS_RESID	STR	PPIMAGE_B
+		BIAS_VERIFY	STR	PPIMAGE_OB
+		BIAS_STACK	STR	PPMERGE_BIAS
+		DARK_PROCESS	STR	PPIMAGE_OB
+		DARK_RESID	STR	PPIMAGE_D
+		DARK_VERIFY	STR	PPIMAGE_OBD
+		DARK_STACK	STR	PPMERGE_DARK
+		SHUTTER_PROCESS	STR	PPIMAGE_OBD
+		SHUTTER_RESID	STR	PPIMAGE_S
+		SHUTTER_VERIFY	STR	PPIMAGE_OBDS
+		SHUTTER_STACK	STR	PPMERGE_SHUTTER
+		FLAT_PROCESS	STR	PPIMAGE_OBDS
+		FLAT_RESID	STR	PPIMAGE_F
+		FLAT_VERIFY	STR	PPIMAGE_OBDSF
+		FLAT_STACK	STR	PPMERGE_FLAT
+		FRINGE_PROCESS	STR	PPIMAGE_OBDSF
+		FRINGE_RESID	STR	PPIMAGE_R
+		FRINGE_VERIFY	STR	PPIMAGE_OBDSFR
+		FRINGE_STACK	STR	PPMERGE_FRINGE
+
+		# Generation of pixel masks from darks and flats
+		DARKMASK_PROCESS	STR	PPIMAGE_OBD
+		DARKMASK_RESID		STR	PPIMAGE_N
+		DARKMASK_VERIFY		STR	PPIMAGE_OBD
+		DARKMASK_STACK		STR	PPMERGE_DARKMASK
+		FLATMASK_PROCESS	STR	PPIMAGE_OBDSF
+		FLATMASK_RESID		STR	PPIMAGE_N
+		FLATMASK_VERIFY		STR	PPIMAGE_OBDSF
+		FLATMASK_STACK		STR	PPMERGE_FLATMASK
+		JPEG_BIN1_IMAGE_DARKMASK STR	PPIMAGE_J1_IMAGE_M
+		JPEG_BIN2_IMAGE_DARKMASK STR	PPIMAGE_J2_IMAGE_M
+		JPEG_BIN1_IMAGE_FLATMASK STR	PPIMAGE_J1_IMAGE_M
+		JPEG_BIN2_IMAGE_FLATMASK STR	PPIMAGE_J2_IMAGE_M
+		JPEG_BIN1_RESID_DARKMASK STR	PPIMAGE_J1_RESID_M
+		JPEG_BIN2_RESID_DARKMASK STR	PPIMAGE_J2_RESID_M
+		JPEG_BIN1_RESID_FLATMASK STR	PPIMAGE_J1_RESID_M
+		JPEG_BIN2_RESID_FLATMASK STR	PPIMAGE_J2_RESID_M
+
+ 		JPEG_BIN1_IMAGE_BIAS     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_DARK     STR  PPIMAGE_J1_IMAGE_B
+		JPEG_BIN1_IMAGE_SHUTTER  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FLAT     STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_DOMEFLAT STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_SKYFLAT  STR  PPIMAGE_J1_IMAGE_F
+		JPEG_BIN1_IMAGE_FRINGE   STR  PPIMAGE_J1_IMAGE_R
+ 		JPEG_BIN2_IMAGE_BIAS     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_DARK     STR  PPIMAGE_J2_IMAGE_B
+		JPEG_BIN2_IMAGE_SHUTTER  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FLAT     STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_DOMEFLAT STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_SKYFLAT  STR  PPIMAGE_J2_IMAGE_F
+		JPEG_BIN2_IMAGE_FRINGE   STR  PPIMAGE_J2_IMAGE_R
+
+ 		JPEG_BIN1_RESID_BIAS     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_DARK     STR  PPIMAGE_J1_RESID_B
+		JPEG_BIN1_RESID_SHUTTER  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FLAT     STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_DOMEFLAT STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_SKYFLAT  STR  PPIMAGE_J1_RESID_F
+		JPEG_BIN1_RESID_FRINGE   STR  PPIMAGE_J1_RESID_R
+ 		JPEG_BIN2_RESID_BIAS     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_DARK     STR  PPIMAGE_J2_RESID_B
+		JPEG_BIN2_RESID_SHUTTER  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FLAT     STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_DOMEFLAT STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_SKYFLAT  STR  PPIMAGE_J2_RESID_F
+		JPEG_BIN2_RESID_FRINGE   STR  PPIMAGE_J2_RESID_R
+	END
+	# Processing raw data
+	DEFAULT		METADATA
+		CHIP		STR	PPIMAGE_OBDSFRA
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+END
+
+FITS    METADATA
+# BITPIX is the bits per pixel for writing the output data
+# COMP = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ] are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE [0..16] is the number of "noise bits" to preserve when quantising floating point data; 16 for no loss
+# HSCALE is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+
+# BITPIX(S32) is the bits per pixel for writing the output data
+# COMPRESSION(STR) = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ](S32) are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE(S32) [0..16] is the number of "noise bits" to preserve when quantising floating point data
+# HSCALE(S32) is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH(S32) is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+# SCALING(STR) = NONE|RANGE|STDEV_POSITIVE|STDEV_NEGATIVE|STDEV_BOTH|MANUAL is the scaling scheme
+# BSCALE(F32) is the manual scaling to apply (when SCALING = MANUAL)
+# BZERO(F32) is the manual zero-point to apply (when SCALING = MANUAL)
+# STDEV.BITS(S32) is the number of bits to map to a standard deviation (when SCALING = STDEV_*)
+# STDEV.NUM(F32) is the number of standard deviations to the edge (when SCALING = STDEV_NEGATIVE|STDEV_POSITIVE)
+# FLOAT(STR) is the name of a custom floating-point type
+
+	DET_IMAGE	METADATA
+		BITPIX		S32	-32
+	END
+	DET_MASK	METADATA
+		BITPIX		S32	8
+	END
+	DET_WEIGHT	METADATA
+		BITPIX		S32	-32
+	END
+
+	SKY_IMAGE	METADATA
+		BITPIX		S32	-32
+	END
+	SKY_MASK	METADATA
+		BITPIX		S32	8
+	END
+	SKY_WEIGHT	METADATA
+		BITPIX		S32	-32
+	END
+
+	COMPRESSED_POSITIVE	METADATA
+		BITPIX		S32	16
+		SCALING		STR	STDEV_POSITIVE
+		STDEV.BITS	S32	4
+		STDEV.NUM	F32	10
+		COMPRESSION	STR	RICE
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+	COMPRESSED_MASK		METADATA
+		COMPRESSION	STR	PLIO
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+	COMPRESSED_SUBTRACTION	METADATA
+		BITPIX		S32	16
+		SCALING		STR	STDEV_BOTH
+		STDEV.BITS	S32	4
+		STDEV.NUM	F32	5
+		COMPRESSION	STR	RICE
+		TILE.X		S32	0
+		TILE.Y		S32	1
+		TILE.Z		S32	1
+		NOISE		S32	8
+	END
+
+END
+
+FILERULES METADATA
+   ### Redirections
+   PSASTRO.INPUT      STR PSASTRO.INPUT.CMF
+   PSASTRO.OUTPUT     STR PSASTRO.OUT.CMF.SPL
+   PSASTRO.OUTPUT.MEF STR PSASTRO.OUT.CMF.MEF
+   PSPHOT.OUTPUT      STR PSPHOT.OUT.CMF.SPL
+
+   ### input file definitions
+   ### use @DETDB entries to get the detrend images from the database
+   ### replace @DETDB with @FILES if you want to require it from the 
+   ### command line, or with an explicit name to require a specific file
+   TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE 
+
+   ## files used by ppImage
+   PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE     
+   PPIMAGE.MASK       INPUT @DETDB        CELL       IMAGE     
+   PPIMAGE.BIAS       INPUT @DETDB        CELL       IMAGE     
+   PPIMAGE.DARK       INPUT @DETDB        CELL       DARK     
+   PPIMAGE.FLAT       INPUT @DETDB        CELL       IMAGE     
+   PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE     
+   PPIMAGE.SHUTTER    INPUT @DETDB        CELL       IMAGE     
+
+   ## Files used by ppMerge
+   PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+   PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+   PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+   ## files used to build and apply the flat-field correction images
+   DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE     
+   DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER     
+   DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE 	
+   DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE 	
+
+   ## files used by psphot 
+   PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE     
+   PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE     
+   PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+   PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+   PSPHOT.PSF.LOAD    INPUT @FILES        CHIP	     PSF       
+   PSPHOT.INPUT.CMF   INPUT @FILES        CHIP	     CMF       
+
+   ## files used by psastro 
+   PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF       
+
+   ## files used by pswarp
+   PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE     
+   PSWARP.SKYCELL     INPUT @FILES        FPA        IMAGE     
+   PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+   PSWARP.MASK        INPUT @FILES        CHIP       MASK
+   PSWARP.ASTROM      INPUT @FILES        CHIP       CMF       
+
+   PPSUB.INPUT        INPUT    none.fits  FPA	     IMAGE
+   PPSUB.INPUT.MASK   INPUT    none.fits  FPA	     MASK
+   PPSUB.INPUT.WEIGHT INPUT    none.fits  FPA	     WEIGHT
+   PPSUB.REF          INPUT    none.fits  FPA	     IMAGE
+   PPSUB.REF.MASK     INPUT    none.fits  FPA	     MASK
+   PPSUB.REF.WEIGHT   INPUT    none.fits  FPA	     WEIGHT
+
+   PPSTACK.INPUT      INPUT    none.fits  FPA	     IMAGE
+   PPSTACK.INPUT.MASK INPUT    none.fits  FPA	     MASK
+   PPSTACK.INPUT.WEIGHT INPUT  none.fits  FPA	     WEIGHT
+
+   PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+   PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+   PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+
+   ### output file definitions
+   TYPE                OUTPUT   FILENAME.RULE      	       FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+   PPIMAGE.OUTPUT      OUTPUT   {OUTPUT}.{CHIP.NAME}.fits      IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.OUTPUT.MASK OUTPUT   {OUTPUT}.{CHIP.NAME}.mask.fits MASK      NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.OUTPUT.WEIGHT OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits   WEIGHT    NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.CHIP        OUTPUT 	{OUTPUT}.{CHIP.NAME}.ch.fits   IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.CHIP.MASK   OUTPUT 	{OUTPUT}.{CHIP.NAME}.ch.mask.fits MASK   NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.CHIP.WEIGHT OUTPUT 	{OUTPUT}.{CHIP.NAME}.ch.wt.fits WEIGHT   NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.OUTPUT.FPA1 OUTPUT 	{OUTPUT}.fpa1.fits             IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.FPA2 OUTPUT 	{OUTPUT}.fpa2.fits             IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.STATS       OUTPUT   {OUTPUT}.stats                 STATS     NONE      FPA        TRUE      NONE
+
+   PPIMAGE.JPEG1       OUTPUT   {OUTPUT}.b1.jpg    	       JPEG      NONE      FPA        TRUE      NONE
+   PPIMAGE.JPEG2       OUTPUT   {OUTPUT}.b2.jpg    	       JPEG      NONE      FPA        TRUE      NONE
+   PPIMAGE.BIN1        OUTPUT   {OUTPUT}.{CHIP.NAME}.b1.fits   IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPIMAGE.BIN2        OUTPUT   {OUTPUT}.{CHIP.NAME}.b2.fits   IMAGE     NONE      CHIP       TRUE      SPLIT
+
+   PPMERGE.OUTPUT.MASK   OUTPUT {OUTPUT}.{CHIP.NAME}.fits      MASK      NONE      CHIP       TRUE      SPLIT
+   PPMERGE.OUTPUT.BIAS   OUTPUT {OUTPUT}.{CHIP.NAME}.fits      IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPMERGE.OUTPUT.DARK   OUTPUT {OUTPUT}.{CHIP.NAME}.fits      DARK      NONE      CHIP       TRUE      SPLIT
+   PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.{CHIP.NAME}.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPMERGE.OUTPUT.FLAT   OUTPUT {OUTPUT}.{CHIP.NAME}.fits      IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPMERGE.OUTPUT.FRINGE OUTPUT {OUTPUT}.{CHIP.NAME}.fits      FRINGE    NONE      CHIP       TRUE      SPLIT
+   PPMERGE.OUTPUT.SIGMA  OUTPUT {OUTPUT}.sigma.fits            IMAGE     NONE      CHIP       TRUE      NONE
+   PPMERGE.OUTPUT.COUNT  OUTPUT {OUTPUT}.count.fits            IMAGE     NONE      CHIP       TRUE      NONE
+
+   DVOCORR.OUTPUT      OUTPUT 	{OUTPUT}.{CHIP.NAME}.fc.fits   IMAGE     NONE      CHIP       TRUE      NONE
+   DVOFLAT.OUTPUT      OUTPUT 	{OUTPUT}.{CHIP.NAME}.co.fits   IMAGE     NONE      CHIP       TRUE      NONE
+
+   PSPHOT.RESID        OUTPUT   {OUTPUT}.{CHIP.NAME}.res.fits  IMAGE     NONE      CHIP       TRUE      NONE
+   PSPHOT.BACKGND      OUTPUT   {OUTPUT}.{CHIP.NAME}.bck.fits  IMAGE     NONE      CHIP       TRUE      NONE
+   PSPHOT.BACKSUB      OUTPUT   {OUTPUT}.{CHIP.NAME}.sub.fits  IMAGE     NONE      CHIP       TRUE      NONE
+   PSPHOT.BACKMDL      OUTPUT   {OUTPUT}.{CHIP.NAME}.mdl.fits  IMAGE     NONE      CHIP       TRUE      NONE
+   PSPHOT.BACKMDL.STDEV OUTPUT   {OUTPUT}.{CHIP.NAME}.mdd.fits IMAGE     NONE      CHIP       TRUE      NONE
+
+   PSPHOT.OUTPUT.RAW   OUTPUT   {OUTPUT}.{CHIP.NAME}           RAW       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUTPUT.SX    OUTPUT   {OUTPUT}.{CHIP.NAME}.sx        SX        NONE      CHIP       TRUE      NONE
+   PSPHOT.OUTPUT.OBJ   OUTPUT   {OUTPUT}.{CHIP.NAME}.obj       OBJ       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUT.CMF.SPL  OUTPUT   {OUTPUT}.{CHIP.NAME}.cmf       CMF       NONE      CHIP       TRUE      NONE
+   PSPHOT.OUT.CMF.MEF  OUTPUT   {OUTPUT}.cmf                   CMF       NONE      FPA        TRUE      NONE
+
+   PSPHOT.PSF.SAVE     OUTPUT   {OUTPUT}.{CHIP.NAME}.psf       PSF       NONE      CHIP       TRUE      NONE
+
+   SOURCE.PLOT.MOMENTS  OUTPUT  {OUTPUT}.{CHIP.NAME}.mnt.png   KAPA      NONE      CHIP       TRUE      NONE
+   SOURCE.PLOT.PSFMODEL OUTPUT  {OUTPUT}.{CHIP.NAME}.psf.png   KAPA      NONE      CHIP       TRUE      NONE
+   SOURCE.PLOT.APRESID  OUTPUT  {OUTPUT}.{CHIP.NAME}.dap.png   KAPA      NONE      CHIP       TRUE      NONE
+
+   PSASTRO.OUTPUT.CMP   OUTPUT   {OUTPUT}.{CHIP.NAME}.smp      CMP       NONE      CHIP       TRUE      SPLIT
+   PSASTRO.OUT.CMF.SPL  OUTPUT   {OUTPUT}.{CHIP.NAME}.smf      CMF       NONE      CHIP       TRUE      SPLIT
+   PSASTRO.OUT.CMF.MEF  OUTPUT   {OUTPUT}.smf		       CMF       NONE      FPA        TRUE      TOGETHER
+
+   PSWARP.OUTPUT       OUTPUT   {OUTPUT}.fits      	       IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.MASK  OUTPUT   {OUTPUT}.mask.fits             MASK      NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.WEIGHT OUTPUT  {OUTPUT}.wt.fits               WEIGHT    NONE      FPA        TRUE      NONE
+   PSWARP.BIN1         OUTPUT   {OUTPUT}.b1.fits   	       IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.BIN2         OUTPUT   {OUTPUT}.b2.fits   	       IMAGE     NONE      FPA        TRUE      NONE
+
+   SKYCELL.STATS       OUTPUT   {OUTPUT}.stats                 STATS     NONE      FPA        TRUE      NONE
+   SKYCELL.TEMPLATE    OUTPUT   {OUTPUT}.skycell               SKYCELL   NONE      FPA        TRUE      NONE
+
+   PPSUB.OUTPUT        OUTPUT   {OUTPUT}.fits                  IMAGE     NONE      FPA        TRUE      NONE
+   PPSUB.OUTPUT.MASK   OUTPUT   {OUTPUT}.mask.fits             MASK      NONE      FPA        TRUE      NONE
+   PPSUB.OUTPUT.WEIGHT OUTPUT   {OUTPUT}.wt.fits               WEIGHT    NONE      FPA        TRUE      NONE
+
+   PPSTACK.OUTPUT      OUTPUT   {OUTPUT}.fits                  IMAGE     NONE      FPA        TRUE      NONE
+   PPSTACK.OUTPUT.MASK OUTPUT   {OUTPUT}.mask.fits             MASK      NONE      FPA        TRUE      NONE
+   PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.weight.fits           WEIGHT    NONE      FPA        TRUE      NONE
+
+   PPSTAMP.OUTPUT        OUTPUT {OUTPUT}.fits                  IMAGE     NONE      FPA        TRUE      NONE
+   PPSTAMP.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits               IMAGE     NONE      CHIP       FALSE     MEF
+
+   PPSIM.OUTPUT        OUTPUT   {OUTPUT}.{CHIP.NAME}.fits      IMAGE     NONE      CHIP       TRUE      SPLIT
+   PPSIM.SOURCES       OUTPUT   {OUTPUT}.cmf                   CMF       NONE      FPA        TRUE      NONE
+
+   PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits                  IMAGE     NONE      CHIP       TRUE      NONE
+   PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                  MASK     NONE      CHIP       TRUE      NONE
+
+   LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log       TEXT      NONE      CHIP       TRUE      NONE
+   LOG.EXP               OUTPUT {OUTPUT}.log                   TEXT      NONE      FPA        TRUE      NONE
+END
+
+# FPA file defines properties of a possible input|output object
+# user can set the filename (I|O), filename rules (O), or abstract source (@FILES, @DETDB) (I) 
+# user can set the extension name, if used
+# user can set the file type (IMAGE, JPEG, RAW, SX, OBJ, CMP, CMF) : but these are not variable in most cases!
+# user can set the file depth: only valid for output files
+# user can set the data depth: must be >= file depth
+# user can set the file format: only valid for newly created FPAs
+# user can set the colormap, scaling method, scaling range (JPEG only)
+# user can set the extension name for the data and header segments (CMF only)
+
+
+EXTNAME.RULES	METADATA
+	CMF.HEAD	STR	{CHIP.NAME}.hdr
+	CMF.DATA	STR	{CHIP.NAME}.psf
+	PSF.HEAD	STR	{CHIP.NAME}.hdr
+	PSF.TABLE	STR	{CHIP.NAME}.psf_model
+	PSF.RESID	STR	{CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branch_20080324/ippconfig/simmosaic/ppImage.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/simmosaic/ppImage.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/simmosaic/ppImage.config	(revision 17278)
@@ -0,0 +1,222 @@
+### ppImage recipe configuration file for simulated test camera
+
+# List of tasks to perform
+
+BASE.FITS       BOOL    FALSE           # Save base detrended image?
+CHIP.FITS       BOOL    TRUE            # Save chip-mosaicked image?
+BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE         BOOL    FALSE           # Reduce overscan to a single value?
+OVERSCAN.FIT            STR     POLYNOMIAL      # NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER          S32     7               # Order of polynomial fit
+OVERSCAN.STAT           STR     MEAN            # MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END   
+END
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
Index: /branches/eam_branch_20080324/ippconfig/simtest/Makefile.am
===================================================================
--- /branches/eam_branch_20080324/ippconfig/simtest/Makefile.am	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/simtest/Makefile.am	(revision 17278)
@@ -0,0 +1,22 @@
+
+installdir = $(datadir)/ippconfig/simtest
+
+install_files = \
+	camera.config \
+	format.config \
+	ppImage.config \
+	psastro.config \
+	psphot.config \
+	rejections.config \
+	dvo.config \
+	ppSim.config \
+	filerules.mdc
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branch_20080324/ippconfig/simtest/filerules.mdc
===================================================================
--- /branches/eam_branch_20080324/ippconfig/simtest/filerules.mdc	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/simtest/filerules.mdc	(revision 17278)
@@ -0,0 +1,137 @@
+### File rules for PHU=FPA, EXT=NONE
+
+
+PSASTRO.INPUT      STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT     STR PSASTRO.OUTPUT.CMF
+PSASTRO.OUTPUT.MEF STR PSASTRO.OUTPUT.CMF
+PSPHOT.OUTPUT      STR PSPHOT.OUTPUT.CMF
+
+### input file definitions
+TYPE               INPUT    FILENAME.RULE                 DATA.LEVEL FILE.TYPE 
+PPIMAGE.INPUT      INPUT    none.fits                     FPA        IMAGE     
+INPUT.MASK         INPUT    none.fits                     FPA        MASK      
+INPUT.WEIGHT       INPUT    none.fits                     FPA        WEIGHT    
+INPUT.PSF          INPUT    none.fits                     READOUT    PSF       
+INPUT.SRC          INPUT    none.fits                     READOUT    CMF       
+PPIMAGE.BIAS       INPUT    @DETDB                        CHIP       IMAGE     
+PPIMAGE.DARK       INPUT    @DETDB                        CHIP       DARK     
+PPIMAGE.SHUTTER    INPUT    @DETDB                        CHIP       IMAGE     
+PPIMAGE.FLAT       INPUT    @DETDB                        CHIP       IMAGE     
+PPIMAGE.MASK       INPUT    @DETDB                        CHIP       IMAGE     
+
+## Files used by ppMerge
+PPMERGE.INPUT      INPUT    @FILES                        CHIP       IMAGE
+PPMERGE.INPUT.MASK INPUT    @FILES                        CHIP       MASK
+PPMERGE.INPUT.WEIGHT INPUT  @FILES                        CHIP       WEIGHT
+
+## files used by psphot 
+PSPHOT.LOAD        INPUT    @FILES                        CHIP       IMAGE
+PSPHOT.INPUT       INPUT    none.fits                     CHIP       IMAGE     
+
+PSWARP.INPUT       INPUT    none.fits                     FPA        IMAGE     
+PSWARP.WEIGHT      INPUT    none.fits                     FPA        WEIGHT
+PSWARP.MASK        INPUT    none.fits                     FPA        MASK
+PSWARP.SKYCELL     INPUT    none.fits                     FPA        IMAGE     
+PSWARP.ASTROM      INPUT    none.fits                     FPA        CMF
+
+PSASTRO.INPUT.CMP  INPUT    none.fits                     CHIP       CMP       
+PSASTRO.INPUT.CMF  INPUT    none.fits                     CHIP       CMF       
+PSPHOT.PSF.LOAD    INPUT    none.psf                      CHIP       PSF       
+PSPHOT.INPUT.CMF   INPUT    none.fits                     CHIP       CMF       
+
+PPSUB.INPUT        INPUT    none.fits                     FPA        IMAGE
+PPSUB.INPUT.MASK   INPUT    none.fits                     FPA        MASK
+PPSUB.INPUT.WEIGHT INPUT    none.fits                     FPA        WEIGHT
+PPSUB.REF          INPUT    none.fits                     FPA        IMAGE
+PPSUB.REF.MASK     INPUT    none.fits                     FPA        MASK
+PPSUB.REF.WEIGHT   INPUT    none.fits                     FPA        WEIGHT
+
+PPSTACK.INPUT      INPUT    none.fits                     FPA        IMAGE
+PPSTACK.INPUT.MASK INPUT    none.fits                     FPA        MASK
+PPSTACK.INPUT.WEIGHT INPUT  none.fits                     FPA        WEIGHT
+
+PPSTAMP.INPUT      INPUT    none.fits                     FPA	     IMAGE
+
+PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+### output file definitions
+TYPE                OUTPUT   FILENAME.RULE           FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT      OUTPUT   {OUTPUT}.fits           IMAGE     NONE      FPA        TRUE      NONE
+PPIMAGE.OUTPUT.DETMASK OUTPUT {OUTPUT}.fits          IMAGE     NONE      FPA        TRUE      NONE
+PPIMAGE.OUTPUT.MASK OUTPUT   {OUTPUT}.mask.fits      MASK      NONE      FPA        TRUE      NONE
+PPIMAGE.OUTPUT.WEIGHT OUTPUT {OUTPUT}.wt.fits        WEIGHT    NONE      FPA        TRUE      NONE
+PPIMAGE.CHIP        OUTPUT   {OUTPUT}.ch.fits        IMAGE     NONE      FPA        TRUE      NONE
+PPIMAGE.CHIP.MASK   OUTPUT   {OUTPUT}.ch.mask.fits   MASK      NONE      FPA        TRUE      NONE
+PPIMAGE.CHIP.WEIGHT OUTPUT   {OUTPUT}.ch.wt.fits     WEIGHT    NONE      FPA        TRUE      NONE
+PPIMAGE.OUTPUT.FPA1 OUTPUT   {OUTPUT}.b1.fits        IMAGE     NONE      FPA        TRUE      NONE
+PPIMAGE.OUTPUT.FPA2 OUTPUT   {OUTPUT}.b2.fits        IMAGE     NONE      FPA        TRUE      NONE
+PPIMAGE.STATS       OUTPUT   {OUTPUT}.stats          STATS     NONE      FPA        TRUE      NONE
+
+PPIMAGE.JPEG1       OUTPUT   {OUTPUT}.b1.jpg         JPEG      NONE      FPA        TRUE      NONE
+PPIMAGE.JPEG2       OUTPUT   {OUTPUT}.b2.jpg         JPEG      NONE      FPA        TRUE      NONE
+PPIMAGE.BIN1        OUTPUT   {OUTPUT}.b1.fits        IMAGE     NONE      FPA        TRUE      NONE
+PPIMAGE.BIN2        OUTPUT   {OUTPUT}.b2.fits        IMAGE     NONE      FPA        TRUE      NONE
+
+PPMERGE.OUTPUT.MASK   OUTPUT {OUTPUT}.fits           MASK      NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.BIAS   OUTPUT {OUTPUT}.fits           IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.DARK   OUTPUT {OUTPUT}.fits           DARK      NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER OUTPUT {OUTPUT}.fits          IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FLAT   OUTPUT {OUTPUT}.fits           IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FRINGE OUTPUT {OUTPUT}.fits           FRINGE    NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SIGMA  OUTPUT {OUTPUT}.sigma.fits     IMAGE     NONE      CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT  OUTPUT {OUTPUT}.count.fits     IMAGE     NONE      CHIP       TRUE      NONE
+
+DVOCORR.OUTPUT      OUTPUT   {OUTPUT}.fc.fits        IMAGE     NONE      FPA        TRUE      NONE
+DVOFLAT.OUTPUT      OUTPUT   {OUTPUT}.co.fits        IMAGE     NONE      FPA        TRUE      NONE
+
+PSPHOT.RESID        OUTPUT   {OUTPUT}.res.fits       IMAGE     NONE      FPA        TRUE      NONE
+PSPHOT.BACKGND      OUTPUT   {OUTPUT}.bck.fits       IMAGE     NONE      FPA        TRUE      NONE
+PSPHOT.BACKSUB      OUTPUT   {OUTPUT}.sub.fits       IMAGE     NONE      FPA        TRUE      NONE
+PSPHOT.BACKMDL      OUTPUT   {OUTPUT}.mdl.fits       IMAGE     NONE      FPA        TRUE      NONE
+
+PSPHOT.OUTPUT.RAW   OUTPUT   {OUTPUT}                RAW       NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.SX    OUTPUT   {OUTPUT}.sx             SX        NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.OBJ   OUTPUT   {OUTPUT}.obj            OBJ       NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.CMP   OUTPUT   {OUTPUT}.cmp            CMP       NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.CMF   OUTPUT   {OUTPUT}.cmf            CMF       NONE      FPA        TRUE      NONE
+PSPHOT.PSF.SAVE     OUTPUT   {OUTPUT}.psf            PSF       NONE      FPA        TRUE      NONE
+
+SOURCE.PLOT.MOMENTS  OUTPUT  {OUTPUT}.mnt.png        KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL OUTPUT  {OUTPUT}.psf.png        KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.APRESID  OUTPUT  {OUTPUT}.dap.png        KAPA      NONE      FPA        TRUE      NONE
+
+PSASTRO.OUTPUT.CMP  OUTPUT   {OUTPUT}.smp            CMP       NONE      FPA        TRUE      NONE
+PSASTRO.OUTPUT.CMF  OUTPUT   {OUTPUT}.smf            CMF       NONE      FPA        TRUE      NONE
+
+PSWARP.OUTPUT       OUTPUT   {OUTPUT}.fits           IMAGE     NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK  OUTPUT   {OUTPUT}.mask.fits      MASK      NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT OUTPUT  {OUTPUT}.wt.fits        WEIGHT    NONE      FPA        TRUE      NONE
+PSWARP.BIN1         OUTPUT   {OUTPUT}.b1.fits        IMAGE     NONE      FPA        TRUE      NONE
+PSWARP.BIN2         OUTPUT   {OUTPUT}.b2.fits        IMAGE     NONE      FPA        TRUE      NONE
+
+SKYCELL.STATS       OUTPUT   {OUTPUT}.stats          STATS     NONE      FPA        TRUE      NONE
+SKYCELL.TEMPLATE    OUTPUT   {OUTPUT}.skycell        SKYCELL   NONE      FPA        TRUE      NONE
+
+PPSUB.OUTPUT        OUTPUT   {OUTPUT}.fits           IMAGE     NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK   OUTPUT   {OUTPUT}.mask.fits      MASK      NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT OUTPUT   {OUTPUT}.wt.fits        WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTACK.OUTPUT      OUTPUT   {OUTPUT}.fits           IMAGE     NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK OUTPUT   {OUTPUT}.mask.fits      MASK      NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.weight.fits    WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTAMP.OUTPUT      OUTPUT   {OUTPUT}.fits           IMAGE     NONE      FPA        TRUE      NONE
+PPSTAMP.CHIP        OUTPUT   {OUTPUT}.ch.fits        IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT        OUTPUT   {OUTPUT}.fits           IMAGE     NONE      FPA        TRUE      SIMTEST
+PPSIM.SOURCES       OUTPUT   {OUTPUT}.cmf            CMF       NONE      FPA        TRUE      NONE
+
+PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits           IMAGE     NONE      CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits           MASK      NONE      CHIP       TRUE      NONE
+
+LOG.IMFILE            OUTPUT {OUTPUT}.imfile.log     TEXT      NONE      FPA        TRUE      NONE
+LOG.EXP               OUTPUT {OUTPUT}.exp.log        TEXT      NONE      FPA        TRUE      NONE
+
+TRACE.IMFILE          OUTPUT {OUTPUT}.imfile.trace   TEXT      NONE      FPA        TRUE      NONE
+TRACE.EXP             OUTPUT {OUTPUT}.exp.trace      TEXT      NONE      FPA        TRUE      NONE
Index: /branches/eam_branch_20080324/ippconfig/simtest/ppImage.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/simtest/ppImage.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/simtest/ppImage.config	(revision 17278)
@@ -0,0 +1,225 @@
+### ppImage recipe configuration file for simulated test camera
+
+# List of tasks to perform
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+BIN1.FITS	BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS	BOOL    TRUE		# Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	7		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END	
+END
+
+# Photometry code
+PHOTCODE.RULE           STR     {DETECTOR}.{FILTER.ID}
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+  CHIP.MASK.FITS  BOOL    TRUE            # Save mask
+END
Index: /branches/eam_branch_20080324/ippconfig/system.config
===================================================================
--- /branches/eam_branch_20080324/ippconfig/system.config	(revision 17278)
+++ /branches/eam_branch_20080324/ippconfig/system.config	(revision 17278)
@@ -0,0 +1,59 @@
+## system-wide options : these are concepts not specific to any camera or recipe
+
+### Setups for each camera system
+CAMERAS		METADATA
+	MEGACAM			STR	megacam/camera.config
+	CFH12K			STR	cfh12k/camera.config
+	ISP-Apogee     		STR	isp/camera.config
+	MOSAIC2 		STR	ctio_mosaic2/camera.config	# CTIO MOSAIC2 camera, for ESSENCE
+	SDSS			STR	sdss/camera.config		# Sloan Digital Sky Survey
+	GPC1			STR	gpc1/camera.config
+	ESOWFI			STR	esowfi/camera.config		# ESO Wide-Field Imager
+	LBCRED			STR	lbc_red/camera.config		# Large Binocular Camera Red
+	SIMMOSAIC		STR	simmosaic/camera.config		# Simulated mosaic, for testing
+	SIMTEST			STR	simtest/camera.config
+	SIMPLE			STR	simple/camera.config
+END
+
+## directories exist for other camera, introduce if they are tested and working
+#	UCAM			STR	ucam/camera.config
+#	LRIS_BLUE		STR	lris_blue/camera.config
+#	TC3			STR	tc3/camera.config		# Pan-STARRS Test Camera III
+#	LRIS_RED		STR	lris_red/camera.config
+
+### camera names as expected by DVO
+DVO.CAMERAS		METADATA
+	MEGACAM			STR	megacam
+	CFH12K			STR	cfh12k
+	ISP-Apogee     		STR	isp
+	MOSAIC2 		STR	ctio_mosaic2
+	SDSS			STR	sdss
+	GPC1			STR	gpc1
+	ESOWFI			STR	esowfi
+	SIMMOSAIC		STR	simmosaic
+	SIMTEST			STR	simtest
+	SIMPLE			STR	simple
+END
+
+# Header keywords for skycell concepts; required because DVO doesn't read HIERARCH
+SKYCELLS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	CELL.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
+
+RECIPES		METADATA		# Site-level recipes
+	MASKS		STR		recipes/masks.config	# Mask values
+	REJECTIONS	STR		recipes/rejections.config # Rejection for detrend creation
+	PPIMAGE		STR		recipes/ppImage.config  # Image reduction
+	PPMERGE		STR		recipes/ppMerge.config	# Image combination
+ 	PPSTATS		STR		recipes/ppStats.config	# Image statistics
+	PSPHOT		STR     	recipes/psphot.config	# Photometry
+	PSASTRO		STR		recipes/psastro.config	# Astrometry
+	PPSTACK		STR		recipes/ppStack.config	# Combination
+	PSWARP		STR		recipes/pswarp.config   # Warping
+	PPSIM		STR		recipes/ppSim.config	# Simulations
+	PPSUB		STR		recipes/ppSub.config	# Subtraction
+END
Index: /branches/eam_branch_20080324/psModules/src/camera/pmFPA.h
===================================================================
--- /branches/eam_branch_20080324/psModules/src/camera/pmFPA.h	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/camera/pmFPA.h	(revision 17278)
@@ -0,0 +1,232 @@
+/* @file pmFPA.h
+ * @brief Defines the focal plane hierarchy, along with functions for interacting with it
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-31 22:39:06 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_H
+#define PM_FPA_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// Return chip position, given FPA position; calculations are all done in pixel units
+#define PM_FPA_TO_CHIP(pos, chip0, chipParity) \
+    (((pos) - (chip0))*(chipParity))
+
+// Return cell position, given chip position; calculations are all done in pixel units
+#define PM_CHIP_TO_CELL(pos, cell0, cellParity, binning) \
+    (((pos) - (cell0))*(cellParity)/(binning))
+
+// Return chip position, given a cell position; calculations are all done in pixel units
+#define PM_CELL_TO_CHIP(pos, cell0, cellParity, binning) \
+    ((pos)*(binning)*(cellParity) + (cell0))
+
+// Return FPA position, given a chip position; calculations are all done in pixel units
+#define PM_CHIP_TO_FPA(pos, chip0, chipParity) \
+    ((pos)*(chipParity) + (chip0))
+
+/// Focal plane array (the entirety of the camera)
+///
+/// The FPA is the top-level camera structure, and consists of one or more chips.  It also contains the
+/// concepts metadata appropriate to this level, a summary of analysis tasks that have been performed, the
+/// camera configuration information, any HDU that corresponds to this level for the file of interest, and
+/// astrometric transformations.  The astrometric transformations encode how to transform from the tangent
+/// plane to the sky, and back.
+typedef struct {
+    // Astrometric transformations
+    psPlaneTransform *fromTPA;  ///< Transformation from tangent plane to focal plane, or NULL
+    psPlaneTransform *toTPA;  ///< Transformation from focal plane to tangent plane, or NULL
+    psProjection *toSky;         ///< Projection from tangent plane to sky, or NULL
+    // Information
+    psMetadata *concepts;               ///< FPA-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *analysis;               ///< FPA-level analysis metadata
+    const psMetadata *camera;           ///< Camera configuration
+    psArray *chips;                     ///< The component chips
+    pmHDU *hdu;                         ///< FITS header data unit of interest, or NULL
+} pmFPA;
+
+/// A chip (contiguous detector element)
+///
+/// The chip is the mid-level camera structure, being part of an FPA, and consisting of one or more cells
+/// (e.g., a CCD).  It also contains the concepts metadata appropriate to this level, a summary of analysis
+/// tasks that have been performed, status flags, any HDU that corresponds to this level for the file of
+/// interest, and astrometric transformations.  The astrometric transformations provide transforms between the
+/// chip and FPA coordinates and back.
+typedef struct {
+    // Astrometric transformations
+    psPlaneTransform *toFPA;            ///< Transformation from chip to FPA coordinates, or NULL
+    psPlaneTransform *fromFPA;          ///< Transformation from FPA to chip coordinates, or NULL
+    // Information
+    psMetadata *concepts;               ///< Chip-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *analysis;               ///< Chip-level analysis metadata
+    psArray *cells;                     ///< The component cells
+    pmFPA *parent;                      ///< Parent FPA
+    bool process;                       ///< Do we bother about reading and working with this chip?
+    bool file_exists;                   ///< Does the file for this chip exist (read case only)?
+    bool data_exists;                   ///< Does the data for this chip exist (read case only)?
+    pmHDU *hdu;                         ///< FITS header data unit of interest,
+} pmChip;
+
+/// A cell (smallest logical unit)
+///
+/// A cell is the lowest-level camera structure, being part of a chip (e.g., an amplifier).  It may consist of
+/// one or more readouts, which are individual reads of the cell.  It also contains the concepts metadata
+/// appropriate to this level, the cell configuration information (for convenience) from the camera
+/// configuration, a summary of analysis tasks that have been performed, status flags, and any HDU that
+/// corresponds to this level for the file of interest
+
+/** Cell data structure
+ *
+ *  A cell consists of one or more readouts.  It also contains a pointer to the
+ *  cell's metadata, and its parent chip.  On the astrometry side, it also
+ *  contains coordinate transforms from the cell to chip, from the cell to
+ *  focal-plane, as well as a "quick and dirty" tranform from the cell to
+ *  sky coordinates.
+ *
+ */
+typedef struct {
+    psMetadata *concepts;               ///< Cell-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *config;                 ///< Cell configuration information (from CELLS in the camera config)
+    psMetadata *analysis;               ///< Cell-level analysis metadata
+    psArray *readouts;                  ///< The component readouts
+    pmChip *parent;                     ///< Parent chip
+    bool process;                       ///< Do we bother about reading and working with this cell?
+    bool file_exists;                   ///< Does the file for this cell exist (read case only)?
+    bool data_exists;                   ///< Does the data for this cell exist (read case only)?
+    pmHDU *hdu;                         ///< FITS header data unit of interest
+} pmCell;
+
+/// A readout (individual read of a cell)
+///
+/// A readout corresponds to an individual read of a cell (e.g., a single image as part of a video sequence,
+/// or one of multiple coadds).  It contains the actual pixels used in analysis (along with mask and weight
+/// maps).  When reading from a FITS file, the images are subimages (from CELL.TRIMSEC) of the pixels read
+/// from the appropriate HDU (at the FPA, chip or cell level).  The readout also contains a list of bias
+/// sections (prescans or overscans, or otherwise), a summary of analysis tasks that have been performed,
+/// status flags, and the offsets used for reading a FITS file incrementally.
+typedef struct {
+    int col0;                           ///< Column offset; non-zero if reading in columns incrementally
+    int row0;                           ///< Row offset; non-zero if reading in rows incrementally
+    psImage *image;                     ///< Imaging area of readout (corresponds to CELL.TRIMSEC region)
+    psImage *mask;                      ///< Mask of input image (corresponds to CELL.TRIMSEC region)
+    psImage *weight;                    ///< Weight of input image (corresponds to CELL.TRIMSEC region)
+    psList *bias;                       ///< List of bias (prescan/overscan) images
+    psMetadata *analysis;               ///< Readout-level analysis metadata
+    pmCell *parent;                     ///< Parent cell
+    bool process;                       ///< Do we bother about reading and working with this readout?
+    bool file_exists;                   ///< Does the file for this readout exist (read case only)?
+    bool data_exists;                   ///< Does the data for this readout exist (read case only)?
+    int imageScan, maskScan, weightScan;///< Separate tracking numbers for reading images incrementally
+} pmReadout;
+
+/// Free all readouts within a cell
+void pmCellFreeReadouts(pmCell *cell);    ///< Cell for which to free readouts
+
+/// Free all cells within a chip
+void pmChipFreeCells(pmChip *chip);       ///< Chip for which to free cells
+
+/// Free all data within a readout
+void pmReadoutFreeData(pmReadout *readout); ///< Readout for which to free data
+
+/// Free all data within a cell (all readouts as well as metadata)
+void pmCellFreeData(pmCell *cell);        ///< Cell for which to free data
+
+/// Free all data within a chip (all cells as well as metadata)
+void pmChipFreeData(pmChip *chip);        ///< Chip for which to free data
+
+/// Free all data within an FPA (all chips as well as metadata)
+void pmFPAFreeData(pmFPA *fpa);           ///< FPA for which to free data
+
+/// Allocate a readout associated with a cell
+pmReadout *pmReadoutAlloc(pmCell *cell);  ///< Parent cell, or NULL
+bool psMemCheckReadout(psPtr ptr);
+
+/// Allocate a cell associated with a chip
+///
+/// The name is used to set CELL.NAME within the concepts.
+pmCell *pmCellAlloc(pmChip *chip,       ///< Parent chip, or NULL
+                    const char *name);  ///< Name of cell, for CELL.NAME
+bool psMemCheckCell(psPtr ptr);
+
+
+/// Allocate a chip associated with an FPA
+///
+/// The name is used to set CHIP.NAME within the concepts
+pmChip *pmChipAlloc(pmFPA *fpa,         ///< Parent FPA, or NULL
+                    const char *name);  ///< Name of chip, for CHIP.NAME
+bool psMemCheckChip(psPtr ptr);
+
+
+/// Allocate an FPA
+pmFPA *pmFPAAlloc(const psMetadata *camera); ///< Camera configuration (to store in FPA)
+bool psMemCheckFPA(psPtr ptr);
+
+/// Check parent links within an FPA
+///
+/// Iterates through the FPA to verify that the "parent" links in the chip, cell and readout are set
+/// correctly.  If there are any incorrect links, they are fixed, and the function returns false.
+bool pmFPACheckParents(pmFPA *fpa);     ///< FPA to check
+
+
+/// Assertions
+
+/// Check that the fundamentals of a readout are set
+#define PM_ASSERT_READOUT_NON_NULL(READOUT, RETVAL) { \
+    if (!(READOUT) || !(READOUT)->bias || !(READOUT)->analysis) { \
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Readout %s or one of its components is NULL.", #READOUT); \
+        return RETVAL; \
+    } \
+    int numCols = 0, numRows = 0; /* Size of readout images */ \
+    psImage *image = (READOUT)->image; /* Image pixels */ \
+    if (image) { \
+        PS_ASSERT_IMAGE_TYPE((READOUT)->image, PS_TYPE_F32, RETVAL); \
+        numCols = image->numCols; \
+        numRows = image->numRows; \
+    } \
+    psImage *mask = (READOUT)->mask; /* Mask pixels */ \
+    if (mask) { \
+        PS_ASSERT_IMAGE_NON_NULL((READOUT)->mask, RETVAL); \
+        if ((numCols != 0 || numRows != 0) && (mask->numCols != numCols || mask->numRows != numRows)) { \
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Mask in readout %s has wrong size (%dx%d vs %dx%d)", \
+                    #READOUT, mask->numCols, mask->numRows, numCols, numRows); \
+            return RETVAL; \
+        } else { \
+            numCols = mask->numCols; \
+            numRows = mask->numRows; \
+        } \
+    } \
+    psImage *weight = (READOUT)->weight; /* Weight map pixels */ \
+    if (weight) { \
+        PS_ASSERT_IMAGE_NON_NULL((READOUT)->weight, RETVAL); \
+        if ((numCols != 0 || numRows != 0) && (weight->numCols != numCols || weight->numRows != numRows)) { \
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Weight in readout %s has wrong size (%dx%d vs %dx%d)", \
+                    #READOUT, weight->numCols, weight->numRows, numCols, numRows); \
+            return RETVAL; \
+        } \
+    } \
+}
+
+/// Assert that a readout contains an image
+#define PM_ASSERT_READOUT_IMAGE(READOUT, RETVAL) \
+    PS_ASSERT_IMAGE_NON_NULL((READOUT)->image, RETVAL);
+
+/// Assert that a readout contains a mask
+#define PM_ASSERT_READOUT_MASK(READOUT, RETVAL) \
+    PS_ASSERT_IMAGE_NON_NULL((READOUT)->mask, RETVAL);
+
+/// Assert that a readout contains a weight map
+#define PM_ASSERT_READOUT_WEIGHT(READOUT, RETVAL) \
+    PS_ASSERT_IMAGE_NON_NULL((READOUT)->weight, RETVAL);
+
+/// @}
+#endif // #ifndef PM_FPA_H
Index: /branches/eam_branch_20080324/psModules/src/camera/pmFPAMaskWeight.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/camera/pmFPAMaskWeight.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/camera/pmFPAMaskWeight.c	(revision 17278)
@@ -0,0 +1,376 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmFPAMaskWeight.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Create the parent mask images that reside in the HDU
+static void createParentMasks(pmHDU *hdu // The HDU for which to create
+                             )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *masks = hdu->masks;        // Array of masks
+    if (!masks) {
+        masks = psArrayAlloc(images->n);
+        hdu->masks = masks;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || masks->data[i]) {
+            continue;
+        }
+        masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+        psImageInit(masks->data[i], 0);
+    }
+
+    return;
+}
+
+// Create the parent weight images that reside in the HDU
+static void createParentWeights(pmHDU *hdu // The HDU for which to create
+                               )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    if (!weights) {
+        weights = psArrayAlloc(images->n);
+        hdu->weights = weights;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || weights->data[i]) {
+            continue;
+        }
+        weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+    }
+
+    return;
+}
+
+// Identify a readout within the HDU, on the basis of the image pointer.
+// This is a little dirty, but hopefully should work....
+static long identifyReadout(pmHDU *hdu, // The HDU containing the readouts
+                            pmReadout *readout // The readout to be identified
+                           )
+{
+    assert(hdu);
+    assert(readout);
+
+    long index = -1;                    // Index of the readout
+    for (long i = 0; i < hdu->images->n && index == -1; i++) {
+        if (hdu->images->data[i] == readout->image->parent) {
+            index = i;
+        }
+    }
+
+    return index;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutSetMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float saturation = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION"); // Saturation level
+    if (!mdok || isnan(saturation)) {
+        psError(PS_ERR_IO, true, "CELL.SATURATION is not set --- unable to set mask.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok || isnan(bad)) {
+        psError(PS_ERR_IO, true, "CELL.BAD is not set --- unable to set mask.\n");
+        return false;
+    }
+    psTrace("psModules.camera", 5, "Saturation: %f, bad: %f\n", saturation, bad);
+
+
+    // Set up the mask
+    psImage *image = readout->image;    // The image pixels
+    if (!readout->mask) {
+        // Generate a (throwaway) mask image, if required
+        readout->mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+    }
+    psImage *mask = readout->mask;      // The mask pixels
+    psImageInit(mask, 0);
+
+    // Dereference pointers for speed
+    psF32 **imageData = image->data.F32;// The image
+    psU8  **maskData  = mask->data.U8;  // The mask
+
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+            if (imageData[i][j] >= saturation) {
+                maskData[i][j] |= satMask;
+            }
+            if (imageData[i][j] <= bad) {
+                maskData[i][j] |= badMask;
+            }
+            if (!isfinite(imageData[i][j])) {
+                maskData[i][j] |= badMask;
+            }
+        }
+    }
+
+    return true;
+}
+
+// XXX this function creates the mask pixels, or uses the existing mask
+// pixels.  currently, it will set mask bits if (value <= BAD) or (value >= SATURATION)
+// should we optionally ignore these tests?
+bool pmReadoutGenerateMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the mask image if required
+    if (!readout->mask) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set mask.\n");
+            return false;
+        }
+
+        pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+        PS_ASSERT_PTR_NON_NULL(hdu, false);
+        if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+            return false;
+        }
+
+        createParentMasks(hdu);
+
+        // Need to identify which readout we're working with....
+        long index = identifyReadout(hdu, readout); // Index of the readout
+        if (index == -1) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+            return false;
+        }
+
+        psImage *mask = psImageSubset(hdu->masks->data[index], *trimsec); // The mask pixels
+        if (!mask) {
+            psString trimsecString = psRegionToString(*trimsec);
+            psError(PS_ERR_UNKNOWN, false, "Unable to set mask from HDU with trimsec: %s.\n", trimsecString);
+            psFree(trimsecString);
+            return false;
+        }
+        psImageInit(mask, 0);
+        assert (readout->mask == NULL); // or else this is a memory leak.
+        readout->mask = mask;
+    }
+
+    return pmReadoutSetMask(readout, satMask, badMask);
+}
+
+bool pmReadoutSetWeight(pmReadout *readout, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float gain = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN"); // Cell gain
+    if (!mdok || isnan(gain)) {
+        psError(PS_ERR_IO, true, "CELL.GAIN is not set --- unable to set weight.\n");
+        return false;
+    }
+    float readnoise = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    if (!mdok || isnan(readnoise)) {
+        psError(PS_ERR_IO, true, "CELL.READNOISE is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    if (poisson) {
+        // Set weight image to the variance in ADU = f/g + rn^2
+        psImage *image = readout->image;    // The image pixels
+        readout->weight = (psImage*)psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+
+        // a negative weight is non-sensical. if the image value drops below 1, the weight must be 1.
+        readout->weight = (psImage*)psUnaryOp(readout->weight, readout->weight, "abs");
+        readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "max",
+                                               psScalarAlloc(1, PS_TYPE_F32));
+    } else {
+        // Just use the read noise
+        if (!readout->weight) {
+            readout->weight = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+        }
+        psImageInit(readout->weight, 0.0);
+    }
+
+    readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "+",
+                                           psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+
+    return true;
+}
+
+// this function creates the weight pixels, or uses the existing weight pixels.  it will set
+// the noise pixel values only if the weight image is not supplied
+bool pmReadoutGenerateWeight(pmReadout *readout, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the weight image if required
+    if (readout->weight)
+        return true;
+
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+    if (!mdok || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+        return false;
+    }
+
+    createParentWeights(hdu);
+
+    // Need to identify which readout we're working with....
+    long index = identifyReadout(hdu, readout); // Index of the readout
+    if (index == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+        return false;
+    }
+
+    psImage *weight = psImageSubset(hdu->weights->data[index], *trimsec); // The weight pixels
+    if (!weight) {
+        psString trimsecString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, false, "Unable to set weight from HDU with trimsec: %s.\n",
+                trimsecString);
+        psFree(trimsecString);
+        return false;
+    }
+    psImageInit(weight, 0);
+    readout->weight = weight;
+
+    return pmReadoutSetWeight(readout, poisson);
+}
+
+bool pmReadoutGenerateMaskWeight(pmReadout *readout, psMaskType satMask, psMaskType badMask, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    bool success = true;                // Was everything successful?
+
+    success &= pmReadoutGenerateMask(readout, satMask, badMask);
+    success &= pmReadoutGenerateWeight(readout, poisson);
+
+    return success;
+}
+
+bool pmCellGenerateMaskWeight(pmCell *cell, psMaskType satMask, psMaskType badMask, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    bool success = true;                // Was everything successful?
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout
+        success &= pmReadoutGenerateMaskWeight(readout, poisson, satMask, badMask);
+    }
+
+    return success;
+}
+
+
+bool pmReadoutWeightRenorm(const pmReadout *readout, psMaskType maskVal, psStatsOptions meanStat,
+                           psStatsOptions stdevStat, int width, psRandom *rng)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+    PM_ASSERT_READOUT_IMAGE(readout, false);
+    PM_ASSERT_READOUT_WEIGHT(readout, false);
+    PS_ASSERT_INT_POSITIVE(width, false);
+
+    if (!psMemIncrRefCounter(rng)) {
+        rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    }
+
+    psImage *image = readout->image, *mask = readout->mask, *weight = readout->weight; // Readout images
+    int numCols = image->numCols, numRows = image->numRows; // Size of images
+    int xNum = numCols / width + 1, yNum = numRows / width + 1; // Number of renormalisation regions
+    float xSize = numCols / (float)xNum, ySize = numRows / (float)yNum; // Size of renormalisation regions
+
+    psStats *meanStats = psStatsAlloc(meanStat), *stdevStats = psStatsAlloc(stdevStat); // Statistics
+    psVector *buffer = NULL;
+
+    for (int j = 0; j < yNum; j++) {
+        // Bounds in y
+        int yMin = j * ySize;
+        int yMax = (j + 1) * ySize;
+        for (int i = 0; i < xNum; i++) {
+            // Bounds in x
+            int xMin = i * xSize;
+            int xMax = (i + 1) * xSize;
+
+            psRegion region = psRegionSet(xMin, xMax, yMin, yMax); // Region of interest
+            psImage *subImage = psImageSubset(image, region); // Sub-image of the image pixels
+            psImage *subWeight = psImageSubset(weight, region); // Sub image of the weight pixels
+            psImage *subMask = mask ? psImageSubset(mask, region) : NULL; // Sub-image of the mask pixels
+
+            if (!psImageBackground(stdevStats, &buffer, subImage, subMask, maskVal, rng) ||
+                !psImageBackground(meanStats, &buffer, subWeight, subMask, maskVal, rng)) {
+                // Nothing we can do about it, but don't want to keel over and die, so do our best to flag it.
+                psString regionStr = psRegionToString(region); // String with region
+                psWarning("Unable to measure statistics over %s", regionStr);
+                psFree(regionStr);
+                psErrorClear();
+                psImageInit(subWeight, NAN);
+                if (subMask) {
+                    psImageInit(subMask, maskVal);
+                }
+            } else {
+                float meanVar = psStatsGetValue(meanStats, meanStat); // Mean of variance map
+                float stdev = psStatsGetValue(stdevStats, stdevStat); // Standard deviation of image
+                psBinaryOp(subWeight, subWeight, "*", psScalarAlloc(PS_SQR(stdev) / meanVar, PS_TYPE_F32));
+            }
+
+            psFree(subImage);
+            psFree(subWeight);
+            psFree(subMask);
+        }
+    }
+    psFree(meanStats);
+    psFree(stdevStats);
+    psFree(rng);
+    psFree(buffer);
+
+    return true;
+}
Index: /branches/eam_branch_20080324/psModules/src/camera/pmFPAMaskWeight.h
===================================================================
--- /branches/eam_branch_20080324/psModules/src/camera/pmFPAMaskWeight.h	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/camera/pmFPAMaskWeight.h	(revision 17278)
@@ -0,0 +1,111 @@
+/* @file pmFPAHeader.h
+ * @brief Functions read FITS headers for FPA components
+ *
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-31 22:39:06 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_MASK_WEIGHT_H
+#define PM_FPA_MASK_WEIGHT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+#if 0
+/// Pixel mask values
+typedef enum {
+    PM_MASK_CLEAR    = 0x00,            ///< The pixel is not masked
+    PM_MASK_BLANK    = 0x01,            ///< The pixel is blank or has no (valid) data
+    PM_MASK_FLAT     = 0x02,            ///< The pixel is non-positive in the flat-field
+    PM_MASK_DETECTOR = 0x02,            ///< The detector pixel is bad (e.g., bad column, charge trap)
+    PM_MASK_SAT      = 0x04,            ///< The pixel is saturated in the image of interest
+    PM_MASK_BAD      = 0x04,            ///< The pixel is low in the image of interest
+    PM_MASK_RANGE    = 0x04,            ///< The pixel is out of range in the image of interest
+    PM_MASK_CR       = 0x08,            ///< The pixel is probably a CR
+    PM_MASK_SPARE1   = 0x10,            ///< Spare mask value
+    PM_MASK_SPARE2   = 0x20,            ///< Spare mask value
+    PM_MASK_SUSPECT  = 0x40,            ///< The pixel is suspected of being bad, but may not be
+    PM_MASK_MARK     = 0x80,            ///< The pixel is marked as temporarily ignored
+} pmMaskValue;
+#else
+#define PM_MASK_MARK 0x80            ///< The pixel is marked as temporarily ignored
+#define PM_MASK_SAT  0x04            ///< The pixel is saturated in the image of interest
+#endif
+
+/// Set a temporary readout mask using CELL.SATURATION and CELL.BAD
+///
+/// Identifies pixels that are saturated (>= CELL.SATURATION) or bad (<= CELL.BAD).  The mask that is produced
+/// within the readout is temporary --- it is not added to the HDU.  This is intended for when the user is
+/// iterating using pmReadoutReadNext, in which case the HDU can't be generated.
+bool pmReadoutSetMask(pmReadout *readout, ///< Readout for which to set mask
+                      psMaskType sat,   ///< Mask value to give saturated pixels
+                      psMaskType bad    ///< Mask value to give bad (low) pixels
+    );
+
+
+/// Set a temporary readout weight map using CELL.GAIN and CELL.READNOISE
+///
+/// Calculates weights (actually variances) for each pixel using photon statistics and the cell gain
+/// (CELL.GAIN) and read noise (CELL.READNOISE).  The weight map that is produced within the readout is
+/// temporary --- it is not added to the HDU.  This is intended for when the user is iterating using
+/// pmReadoutReadNext, in which case the HDU can't be generated.
+bool pmReadoutSetWeight(pmReadout *readout, ///< Readout for which to set weight
+                        bool poisson    ///< Use poisson weights (in addition to read noise)?
+                       );
+
+/// Generate a readout mask (suitable for output) using CELL.SATURATION and CELL.BAD
+///
+/// Identifies pixels that are saturated (>= CELL.SATURATION) or bad (<= CELL.BAD).  The mask that is produced
+/// is suitable for output (complete with HDU entry).  This is intended for most operations.
+bool pmReadoutGenerateMask(pmReadout *readout, ///< Readout for which to generate mask
+                           psMaskType sat, ///< Mask value to give saturated pixels
+                           psMaskType bad ///< Mask value to give bad (low) pixels
+    );
+
+/// Generate a weight map (suitable for output) using CELL.GAIN and CELL.READNOISE
+///
+/// Calculates weights (actually variances) for each pixel using photon statistics and the cell gain
+/// (CELL.GAIN) and read noise (CELL.READNOISE).  The weight map that is produced within the readout is
+/// suitable for output (complete with HDU entry).  This is intended for most operations.
+bool pmReadoutGenerateWeight(pmReadout *readout, ///< Readout for which to generate weight
+                             bool poisson    ///< Use poisson weights (in addition to read noise)?
+                            );
+
+/// Generate mask and weight map for a readout
+///
+/// Calls pmReadoutGenerateMask and pmReadoutGenerateWeight for the readout
+bool pmReadoutGenerateMaskWeight(pmReadout *readout, ///< Readout for which to generate mask and weights
+                                 psMaskType sat, ///< Mask value to give saturated pixels
+                                 psMaskType bad, ///< Mask value to give bad (low) pixels
+                                 bool poisson ///< Use poisson weights (in addition to read noise)?
+                                );
+
+/// Generate mask and weight maps for all readouts within a cell
+///
+/// Calls pmReadoutGenerateMaskWeight for each readout within the cell.
+bool pmCellGenerateMaskWeight(pmCell *cell, ///< Cell for which to generate mask and weights
+                              psMaskType sat, ///< Mask value to give saturated pixels
+                              psMaskType bad, ///< Mask value to give bad (low) pixels
+                              bool poisson ///< Use poisson weights (in addition to read noise)?
+                             );
+
+/// Renormalise the weight map to match the actual variance
+///
+/// The variance in the image is measured in patches, and the variance map is adjusted so that the mean for
+/// that patch corresponds.
+bool pmReadoutWeightRenorm(const pmReadout *readout, // Readout to normalise
+                           psMaskType maskVal, // Value to mask
+                           psStatsOptions meanStat, // Statistic to measure the mean (of the variance map)
+                           psStatsOptions stdevStat, // Statistic to measure the stdev (of the image)
+                           int width,   // Width of patch (pixels)
+                           psRandom *rng // Random number generator (for sub-sampling images)
+    );
+
+
+
+/// @}
+#endif
Index: /branches/eam_branch_20080324/psModules/src/camera/pmFPARead.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/camera/pmFPARead.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/camera/pmFPARead.c	(revision 17278)
@@ -0,0 +1,1178 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+#include "pmFPARead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Definitions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Specify what to read
+typedef enum {
+    FPA_READ_TYPE_IMAGE,                // Read image
+    FPA_READ_TYPE_MASK,                 // Read mask
+    FPA_READ_TYPE_WEIGHT,               // Read weight map
+    FPA_READ_TYPE_HEADER                // Read header
+} fpaReadType;
+
+// Desired type for pixels; the index corresponds to the fpaReadType, above.
+static psElemType pixelTypes[] = {
+    PS_TYPE_F32,
+    PS_TYPE_MASK,
+    PS_TYPE_F32,
+    0
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Return pointer to appropriate value for tracking scans
+static int *readoutScansByType(pmReadout *readout, // Readout of interest
+                               fpaReadType type // Type of image
+    )
+{
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        return &readout->imageScan;
+      case FPA_READ_TYPE_MASK:
+        return &readout->maskScan;
+      case FPA_READ_TYPE_WEIGHT:
+        return &readout->weightScan;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+}
+
+// Return pointer to appropriate image
+static psImage **readoutImageByType(pmReadout *readout, // Readout of interest
+                                    fpaReadType type // Type of image
+    )
+{
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        return &readout->image;
+      case FPA_READ_TYPE_MASK:
+        return &readout->mask;
+      case FPA_READ_TYPE_WEIGHT:
+        return &readout->weight;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+}
+
+// Determine number of readouts in the FITS file
+// In the process, reads the header and concepts
+static bool cellNumReadouts(pmCell *cell,    // Cell of interest
+                            psFits *fits     // FITS file
+    )
+{
+    assert(cell);
+    assert(fits);
+
+    // Get the HDU and read the header
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu || hdu->blankPHU) {
+        psError(PS_ERR_IO, true, "Unable to find HDU");
+        return false;
+    }
+    if (!pmCellReadHeader(cell, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                            PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.\n");
+        return false;
+    }
+
+    // Get the size of the third dimension
+    bool mdok;                          // Status of MD lookup
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read
+        psError(PS_ERR_IO, true, "No pixels in extension %s.", hdu->extname);
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+    int naxis3;                     // Number of image planes
+    if (naxis == 3) {
+        naxis3 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS3");
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS3 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+    } else {
+        naxis3 = 1;
+    }
+
+    return naxis3;
+}
+
+
+// Determine readout scan properties: the next and last scans
+// Requires that cellNumReadouts() has been called before (for header and concepts to have been read)
+// In the process, adjusts the TRIMSEC
+static bool readoutScanProperties(int *next, // Index of next scan
+                                  int *last, // Index of last scan
+                                  pmReadout *readout, // Readout of interest
+                                  int numScans, // Number of scans to read at a time
+                                  fpaReadType type // Type of image
+    )
+{
+    assert(next);
+    assert(last);
+    assert(readout);
+
+    psImage *image = *readoutImageByType(readout, type); // Appropriate image from readout
+
+    // Header and concepts have been read by a call to cellNumReadouts(), so we can just assume they're there.
+
+    // Get the trim and bias sections
+    pmCell *cell = readout->parent;     // Parent cell
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    pmHDU *hdu = pmHDUFromCell(cell);   // HDU for data
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+
+    // Rationalize trimsec against naxis1, naxis2:  valid range for trimsec is 1-Nx,1-Ny
+    if (trimsec->x1 < 1) {
+        int naxis1 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS1"); // The number of columns
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS1 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+        trimsec->x1 = naxis1 + trimsec->x1;
+    }
+    if (trimsec->y1 < 1) {
+        int naxis2 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS2"); // The number of columns
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS2 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+        trimsec->y1 = naxis2 + trimsec->y1;
+    }
+
+    // Calculate the segment offset and upper limit
+    if (numScans == 0) {
+        *next = (readdir == 1) ? trimsec->y0 : trimsec->x0;
+    } else {
+        *next = image ? *readoutScansByType(readout, type) + numScans : 0;
+    }
+    *last = (readdir == 1) ? trimsec->y1 : trimsec->x1;
+
+    return true;
+}
+
+static bool readoutMore(pmReadout *readout, // Readout of interest
+                        psFits *fits,    // FITS file
+                        int z,          // Plane number
+                        int numScans,   // Number of scans to read at a time
+                        fpaReadType type // Type of image
+    )
+{
+    assert(readout);
+    assert(fits);
+
+    psImage *image = *readoutImageByType(readout, type);
+
+    if (!image) {
+        return true;
+    } else if (numScans == 0) {
+        // Can only read the entire image once
+        return false;
+    }
+
+    pmCell *cell = readout->parent;     // Parent cell
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find parent cell.");
+        return false;
+    }
+    int naxis3 = cellNumReadouts(cell, fits); // Number of planes
+    if (z >= naxis3) {
+        // No more to read
+        return false;
+    }
+
+    int next;                           // Next position
+    int last;                           // Last position
+    if (!readoutScanProperties(&next, &last, readout, numScans, type)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine readout properties.");
+        return false;
+    }
+
+    return (next < last);
+}
+
+// Carve a readout from the image pixels
+static bool readoutCarve(pmReadout *readout, // Readout to be carved up
+                         psImage *image, // Image that will be carved
+                         const psRegion *trimsec, // Trim section
+                         const psList *biassecs, // Bias sections
+                         fpaReadType type // Type of image
+                        )
+{
+    assert(readout);
+    assert(image);
+    assert(trimsec);
+    assert(biassecs);
+
+    // The image corresponding to the trim region
+    if (psRegionIsNaN(*trimsec)) {
+        psString regionString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, true, "Invalid trim section: %s\n", regionString);
+        psFree(regionString);
+        psFree(readout);
+        return false;
+    }
+    psRegion region = psRegionSet(PS_MAX(trimsec->x0 - readout->col0, 0), // x0
+                                  PS_MIN(trimsec->x1 - readout->col0, image->numCols), // x1
+                                  PS_MAX(trimsec->y0 - readout->row0, 0), // y0
+                                  PS_MIN(trimsec->y1 - readout->row0, image->numRows) // y1
+                                 );
+
+    // Place the image subset in the appropriate target location, freeing if needed
+    psImage **target = readoutImageByType(readout, type); // Target image
+    if (*target) {
+        psFree(*target);
+    }
+    *target = psImageSubset(image, region);
+
+    // Get the list of overscans: only for IMAGE types (no overscan for MASK and WEIGHT)
+    if (type == FPA_READ_TYPE_IMAGE) {
+        if (readout->bias->n != 0) {
+            // Make way!
+            psFree(readout->bias);
+            readout->bias = psListAlloc(NULL);
+        }
+        psListIterator *iter = psListIteratorAlloc((psList*)biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // A BIASSEC region from the list
+        while ((biassec = psListGetAndIncrement(iter))) {
+            if (psRegionIsNaN(*biassec)) {
+                psString regionString = psRegionToString(*biassec);
+                psError(PS_ERR_IO, true, "Invalid bias section: %s\n", regionString);
+                psFree(regionString);
+                psFree(readout);
+                return false;
+            }
+            psRegion region = psRegionSet(PS_MAX(biassec->x0 - readout->col0, 0), // x0
+                                          PS_MIN(biassec->x1 - readout->col0, image->numCols), // x1
+                                          PS_MAX(biassec->y0 - readout->row0, 0), // y0
+                                          PS_MIN(biassec->y1 - readout->row0, image->numRows) // y1
+                );
+            psImage *overscan = psImageSubset(image, region);
+            psListAdd(readout->bias, PS_LIST_TAIL, overscan);
+            psFree(overscan);
+        }
+        psFree(iter);
+    }
+
+    return true;
+}
+
+// Read a component of a readout.  We read in only the rows from min to max for plane z, for
+// the full region requested.  if we request a range outside the region, we will pad to fill
+// out the edges of the region with 'bad' pixels.  The output image always has max-min rows.
+// The region represents the maximum bounds of the full image
+static psImage *readoutReadComponent(psImage *image, // Image into which to read
+                                     psFits *fits, // FITS file from which to read
+                                     const psRegion *fullImage, // full image region, read a subset
+                                     int readdir, // Read direction (1=rows, 2=cols)
+                                     int min,  // Minimum row/col number to read
+                                     int max,   // Maximum row/col number to read
+                                     int z,     // Image plane to read
+                                     float bad, // Bad value
+                                     psElemType type // Expected type for image
+    )
+{
+    assert(fits);
+    assert(fullImage);
+    assert((readdir == 1) || (readdir == 2));
+
+    int nRead = 0;                      // Number of scans read
+    int nScans = max - min;             // Number of scans desired
+    assert(nScans > 0);
+
+    psRegion toRead = *fullImage;  // full image region
+
+    int dX = 0, dY = 0;                 // Offset from image in FITS file to lower left corner of what's read
+    int nX = 0, nY = 0;                 // Size of region to read
+
+    if (readdir == 1) {
+        toRead.y0 = PS_MAX(toRead.y0, min);
+        toRead.y1 = PS_MIN(toRead.y1, max);
+        nRead = toRead.y1 - toRead.y0;
+        if (min < fullImage->y0) {
+            dY = toRead.y0;
+        }
+        nX = toRead.x1 - toRead.x0;
+        nY = nScans;
+    } else {
+        toRead.x0 = PS_MAX(toRead.x0, min);
+        toRead.x1 = PS_MIN(toRead.x1, max);
+        nRead = toRead.x1 - toRead.x0;
+        if (min < fullImage->x0) {
+            dX = toRead.x0;
+        }
+        nX = nScans;
+        nY = toRead.y1 - toRead.y0;
+    }
+
+    psTrace("psModules.camera", 5, "Reading section [%.0f:%.0f,%.0f:%.0f]\n",
+            toRead.x0, toRead.x1, toRead.y0, toRead.y1);
+    image = psFitsReadImageBuffer(image, fits, toRead, z); // Desired pixels
+    psTrace("psModules.camera", 7, "Image is %dx%d\n", image->numCols, image->numRows);
+
+    // Ensure the pixel type corresponds to what we desire
+    if (image->type.type != type) {
+        psImage *temp = psImageCopy(NULL, image, type);
+        psFree(image);
+        image = temp;
+    }
+
+    // Resize the image so that it matches the number of scans requested
+    // XXX this modification is not carried back up stream: it affects readout->row0,col0
+    //
+    // XXXXX Do we really want to do this???  Why???
+    if (nRead < nScans) {
+        // The region of interest is smaller than the number of pixels we want.
+        psTrace("psModules.camera", 5, "Resizing image to %d,%d\n", nX, nY);
+        psImage *temp = psImageAlloc(nX, nY, image->type.type);
+        psImageInit(temp, bad);
+        psImageOverlaySection(temp, image, dX, dY, "=");
+        psFree(image);
+        image = temp;
+    }
+
+    return image;
+}
+
+// Read a chunk of a readout (or the whole lot)
+static bool readoutReadChunk(pmReadout *readout, // Readout into which to read
+                             psFits *fits, // FITS file
+                             int z,     // Desired image plane
+                             int numScans, // Number of scans (row or col depends on CELL.READDIR); 0 for all
+                             int overlap, // Number of scans (row/col) to overlap between scans
+                             fpaReadType type // Type of image
+    )
+{
+    assert(readout);
+    assert(fits);
+    assert(z >= 0);
+    assert(numScans >= 0);
+    assert(overlap >= 0);
+
+    psImage **image = readoutImageByType(readout, type); // Pointer to the image of interest
+    if (*image && numScans == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Already read entire image --- won't clobber.");
+        return false;
+    }
+
+    pmCell *cell = readout->parent;     // The parent cell
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find parent cell.");
+        return false;
+    }
+
+    int naxis3 = cellNumReadouts(cell, fits); // Number of image planes
+    if (z >= naxis3) {
+        psError(PS_ERR_IO, false, "Desired image plane (%d) exceeds available number (%d).",
+                z, naxis3);
+        return false;
+    }
+
+    int next;                           // Next position
+    int last;                           // Last position
+    if (!readoutScanProperties(&next, &last, readout, numScans, type)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine readout properties.");
+        return false;
+    }
+    if (next >= last) {
+        psError(PS_ERR_IO, true, "No more of the readout to read.");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    assert(hdu && !hdu->blankPHU);      // Checked by cellNumReadouts()
+
+    bool mdok;                          // Status of MD lookup
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+
+    // Check the third dimension
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read
+        psError(PS_ERR_IO, true, "No pixels in extension %s.", hdu->extname);
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+
+    // Update number of scans read
+    int *scansTracker = readoutScansByType(readout, type); // Tracker for how many scans have been read
+    *scansTracker = next;
+    if (next == 0) {
+        overlap = 0;
+    }
+    next -= overlap;
+
+    // Calculate limits, adjust readout->row0,col0
+    // XXX Should row0,col0 be adjusted, since they are used for astrometry???
+    // Just moved to using the "scansTracker" for tracking position in chunk reads, so row0,col0 not required
+    if (readdir == 1) {
+        // Reading rows
+        readout->row0 = next;
+        readout->col0 = trimsec->x0;
+        if (numScans == 0) {
+            numScans = trimsec->y1 - trimsec->y0;
+        }
+    } else {
+        // Reading cols
+        readout->col0 = next;
+        readout->row0 = trimsec->y0;
+        if (numScans == 0) {
+            numScans = trimsec->x1 - trimsec->x0;
+        }
+    }
+    int upper = next + numScans + overlap;        // Upper limit to next section
+
+    // Blow away existing data.
+    // Do this before returning, so that we're not returning data from a previous read
+    psFree(*image);
+    *image = NULL;
+    *image = readoutReadComponent(*image, fits, trimsec, readdir, next, upper, z, bad, pixelTypes[type]);
+
+    // Read overscans only for "image" type --- weights and masks shouldn't record overscans
+    if (type == FPA_READ_TYPE_IMAGE) {
+        // Blow away existing data
+        while (readout->bias->n > 0) {
+            psListRemove(readout->bias, PS_LIST_HEAD);
+        }
+
+        // Get the new bias sections
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!mdok || !biassecs) {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC is not set.\n");
+            return false;
+        }
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;           // Bias section from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter))) {
+            psImage *bias = readoutReadComponent(NULL, fits, biassec, readdir, next, upper, z,
+                                                 bad, pixelTypes[type]); // The bias
+            psListAdd(readout->bias, PS_LIST_TAIL, bias);
+            psFree(bias);                   // Drop reference
+        }
+        psFree(biassecsIter);
+    }
+
+    return true;
+}
+
+// Read into an cell; this is the engine for pmCellRead, pmCellReadMask, pmCellReadWeight
+// Does most of the work for the reading --- reads the HDU, and portions the HDU into readouts.
+static bool cellRead(pmCell *cell,      // Cell into which to read
+                     psFits *fits,      // FITS file from which to read
+                     psDB *db,          // Database handle, for concepts ingest
+                     fpaReadType type   // Type to read
+                    )
+{
+    assert(cell);
+    assert(fits);
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return true;                    // We read everything we could
+    }
+
+    // check if we have read the desired data, read it if needed
+    bool (*hduReadFunc)(pmHDU*, psFits*) = NULL; // Function to use to read the HDU
+    void *dataPointer = NULL;           // pointer to location of desired data
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        hduReadFunc = pmHDURead;
+        dataPointer = hdu->images;
+        break;
+      case FPA_READ_TYPE_HEADER:
+        hduReadFunc = pmHDUReadHeader;
+        dataPointer = hdu->header;
+        break;
+      case FPA_READ_TYPE_MASK:
+        hduReadFunc = pmHDUReadMask;
+        dataPointer = hdu->masks;
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        hduReadFunc = pmHDUReadWeight;
+        dataPointer = hdu->weights;
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // do we have the data we want (image, header, or etc).
+    if (!dataPointer) {
+        // attempt to read in the desired data
+        if (!hduReadFunc(hdu, fits)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+            return false;
+        }
+    }
+
+    // load in the concept information for this cell
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        //psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        //return false;
+        psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+    }
+
+    // skip the image arrays completely for the header-only files
+    if (type == FPA_READ_TYPE_HEADER) {
+        pmCellSetDataStatus(cell, true);
+        return true;
+    }
+
+    // set up pointers for the different possible image arrays
+    psArray *imageArray = NULL; // Array of images in the HDU
+    psElemType imageType = pixelTypes[type]; // Expected type for image
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        imageArray = hdu->images;
+        break;
+      case FPA_READ_TYPE_MASK:
+        imageArray = hdu->masks;
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        imageArray = hdu->weights;
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // Having read the cell, we now have to cut it up
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+    psList *biassecs = psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC");
+    if (psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, false, "CELL.TRIMSEC is not set --- can't read cell.\n");
+        return false;
+    }
+
+    // Iterate over each of the image planes, converting type if necessary, and extracting the bits that
+    // matter (CELL.TRIMSEC, CELL.BIASSEC) into readouts with readoutCarve.
+    for (int i = 0; i < imageArray->n; i++) {
+        psImage *source = imageArray->data[i]; // Source image, from the i-th plane
+        PS_ASSERT_IMAGE_NON_NULL(source, false);
+
+        // Type conversion here to support the modules, which don't have multiple type support yet
+        if (source->type.type != imageType) {
+            psImage *temp = psImageCopy(NULL, source, imageType); // Temporary image
+            psFree(imageArray->data[i]);
+            imageArray->data[i] = temp;
+            source = temp;
+        }
+
+        pmReadout *readout;             // Readout into which to read
+        if (cell->readouts->n > i && cell->readouts->data[i]) {
+            readout = psMemIncrRefCounter(cell->readouts->data[i]);
+        } else {
+            readout = pmReadoutAlloc(cell);
+        }
+
+        if (!readoutCarve(readout, source, trimsec, biassecs, type)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to carve readout into image and bias sections for %d the plane.", i);
+            return NULL;
+        }
+        psFree(readout);                // Drop reference
+    }
+
+    pmCellSetDataStatus(cell, true);
+    return true;
+}
+
+
+// Read into an chip; this is the engine for pmChipRead, pmChipReadMask, pmChipReadWeight
+// Iterates over component cells, reading each
+static bool chipRead(pmChip *chip,      // Chip into which to read
+                     psFits *fits,      // FITS file from which to read
+                     psDB *db,          // Database handle, for concepts ingest
+                     fpaReadType type   // Type to read
+                    )
+{
+    assert(chip);
+    assert(fits);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        success |= cellRead(cell, fits, db, type);
+    }
+    if (success) {
+        if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+            return false;
+        }
+        // XXX probably could just use chip->data_exists
+        pmChipSetDataStatus(chip, true);
+    }
+
+    return success;
+}
+
+
+// Read into an FPA; this is the engine for pmFPARead, pmFPAReadMask, pmFPAReadWeight
+// Iterates over component chips, reading each
+static bool fpaRead(pmFPA *fpa,         // FPA into which to read
+                    psFits *fits,       // FITS file from which to read
+                    psDB *db,           // Database handle, for concepts ingest
+                    fpaReadType type    // Type to read
+                   )
+{
+    assert(fpa);
+    assert(fits);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The cell of interest
+        success |= chipRead(chip, fits, db, type);
+    }
+    if (success) {
+        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read any chips in FPA");
+    }
+
+    return success;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading images
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// pmReadoutReadNext is maintained here (for now) to maintain backwards compatibility.
+// pmReadoutReadNext has been replaced by pmReadoutRead, pmReadoutReadChunk, pmReadoutMore
+bool pmReadoutReadNext(bool *status, pmReadout *readout, psFits *fits, int z, int numScans)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    assert (numScans || !readout->image); // cannot have readout->image and !numScans
+
+    *status = false;
+
+    // Get the HDU and read the header
+    pmCell *cell = readout->parent;     // The parent cell
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu || hdu->blankPHU) {
+        // XXX is this an error condition?
+        *status = true;
+        return false;
+    }
+
+    if (!pmCellReadHeader(cell, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+
+    // Make sure we have the information we need
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                            PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.\n");
+        return false;
+    }
+
+    // Get the trim and bias sections
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+    psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!mdok || !biassecs) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC is not set.\n");
+        return false;
+    }
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.BAD is not set --- assuming zero.\n");
+        bad = 0.0;
+    }
+
+    // Check the third dimension
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read, as for a PHU.
+        *status = true;
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+    int naxis3 = 1;                     // The number of image planes
+    if (naxis == 3) {
+        naxis3 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS3");
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS3 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    if (z >= naxis3) {
+        // Nothing to see here.  Move along.
+        *status = true;
+        return false;
+    }
+
+    // Get the size of the image plane
+    int naxis1 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS1"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS1 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS2"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS2 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+
+    // rationalize trimsec against naxis1, naxis2
+    // valid range for trimsec is 1-Nx,1-Ny
+    // if (trimsec->x0 == 0) trimsec->x0 = 1;
+    if (trimsec->x1 <  1)
+        trimsec->x1 = naxis1 + trimsec->x1;
+    // if (trimsec->y0 == 0) trimsec->y0 = 1;
+    if (trimsec->y1 <  1)
+        trimsec->y1 = naxis2 + trimsec->y1;
+
+    int maxSize;                        // Number of cols,rows in image
+    if (readdir == 1) {
+        maxSize = PS_MIN(naxis2, trimsec->y1 - trimsec->y0);
+    } else {
+        maxSize = PS_MIN(naxis1, trimsec->x1 - trimsec->x0);
+    }
+
+    int offset;                         // start of the segment
+    int upper;                          // end of the segment
+    int lastScan;                       // last possible scan
+
+    // Calculate the segment offset and upper limit, adjust readout->row0,col0
+    if (readdir == 1) {
+        // Reading rows
+        offset = (readout->image) ? readout->row0 + numScans : 0; // extend to next section or start at beginning?
+        offset = (numScans == 0) ? trimsec->x0 : offset; // full array ? read full trimsec : read section
+        readout->row0 = offset;
+        readout->col0 = trimsec->x0;
+        lastScan = trimsec->y1;
+    } else {
+        // Reading cols
+        offset = (readout->image) ? readout->col0 + numScans : 0;
+        offset = (numScans == 0) ? trimsec->y0 : offset; // full array ? read full trimsec : read section
+        readout->col0 = offset;
+        readout->row0 = trimsec->y0;
+        lastScan = trimsec->x1;
+    }
+    upper = offset + numScans;
+
+    // Blow away existing data.
+    // Do this before returning, so that we're not returning data from a previous read
+    psFree(readout->image);
+    readout->image = NULL;
+
+    while (readout->bias->n > 0) {
+        psListRemove(readout->bias, PS_LIST_HEAD);
+    }
+
+    if (offset >= lastScan) {
+        // We've read everything there is
+        psTrace("psModules.camera", 7, "Read everything.\n");
+        *status = true;
+        return false;
+    }
+
+    psTrace("psModules.camera", 7, "offset=%d, upper = %d, image is %dx%d, trimsec [%.0f:%.0f,%.0f:%.0f]\n",
+            offset, upper, naxis1, naxis2, trimsec->x0, trimsec->x1, trimsec->y0, trimsec->y1);
+
+    // Get the new the trim section
+    readout->image = readoutReadComponent(readout->image, fits, trimsec, readdir, offset, upper, z, bad,
+                                          PS_TYPE_F32); // The image
+
+    // Get the new bias sections
+    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator for BIASSEC
+    psRegion *biassec = NULL;           // Bias section from iteration
+    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+        psImage *bias = readoutReadComponent(NULL, fits, biassec, readdir, offset, upper, z, bad,
+                                             PS_TYPE_F32); // The bias
+        psListAdd(readout->bias, PS_LIST_TAIL, bias);
+        psFree(bias);                   // Drop reference
+    }
+    psFree(biassecsIter);
+
+    *status = true;
+    return true;
+}
+
+
+
+bool pmReadoutMore(pmReadout *readout, psFits *fits, int z, int numScans)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutMore(readout, fits, z, numScans, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmReadoutReadChunk(pmReadout *readout, psFits *fits, int z, int numScans, int overlap)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    return readoutReadChunk(readout, fits, z, numScans, overlap, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmReadoutRead(pmReadout *readout, psFits *fits, int z)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutReadChunk(readout, fits, z, 0, 0, FPA_READ_TYPE_IMAGE);
+}
+
+int pmCellNumReadouts(pmCell *cell, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellNumReadouts(cell, fits);
+}
+
+bool pmCellRead(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmChipRead(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmFPARead(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_IMAGE);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the mask
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutMoreMask(pmReadout *readout, psFits *fits, int z, int numScans)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutMore(readout, fits, z, numScans, FPA_READ_TYPE_MASK);
+}
+
+bool pmReadoutReadChunkMask(pmReadout *readout, psFits *fits, int z, int numScans, int overlap)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    return readoutReadChunk(readout, fits, z, numScans, overlap, FPA_READ_TYPE_MASK);
+}
+
+bool pmReadoutReadMask(pmReadout *readout, psFits *fits, int z)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutReadChunk(readout, fits, z, 0, 0, FPA_READ_TYPE_MASK);
+}
+
+bool pmCellReadMask(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_MASK);
+}
+
+bool pmChipReadMask(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_MASK);
+}
+
+bool pmFPAReadMask(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_MASK);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the weight map
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutMoreWeight(pmReadout *readout, psFits *fits, int z, int numScans)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutMore(readout, fits, z, numScans, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmReadoutReadChunkWeight(pmReadout *readout, psFits *fits, int z, int numScans, int overlap)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    return readoutReadChunk(readout, fits, z, numScans, overlap, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmReadoutReadWeight(pmReadout *readout, psFits *fits, int z)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutReadChunk(readout, fits, z, 0, 0, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmCellReadWeight(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmChipReadWeight(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmFPAReadWeight(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_WEIGHT);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the image header
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeaderSet(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_HEADER);
+}
+
+bool pmChipReadHeaderSet(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_HEADER);
+}
+
+bool pmFPAReadHeaderSet(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_HEADER);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading FITS tables
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+int pmCellReadTable(pmCell *cell, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, 0);
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    const char *chipName = psMetadataLookupStr(NULL, cell->parent->concepts, "CHIP.NAME"); // Name of chip
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+    psString extname = NULL;            // Extension name
+    psStringAppend(&extname, "%s_%s_%s", name, chipName, cellName);
+
+    // XXX Could do a table lookup from the camera format, in case the input file isn't laid out with
+    // NAME_CHIP_CELL extension names --- use these as keys, and the value as the proper extension name.
+    // Allow interpolation of concepts, e.g., "{CHIP.NAME}" --> "ccd13".
+
+    if (!psFitsMoveExtName(fits, extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+        psFree(extname);
+        return 0;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // The FITS header
+    if (!header) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(extname);
+        psFree(header);
+        return 0;
+    }
+
+    psString headerName = NULL;         // Name for header
+    psStringAppend(&headerName, "%s.HEADER", name);
+    if (!psMetadataAdd(cell->analysis, PS_LIST_TAIL, headerName, PS_DATA_METADATA,
+                       "FITS table header", header)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add header from extension %s to analysis metadata "
+                "for chip %s, cell %s\n", extname, chipName, cellName);
+        psFree(headerName);
+        psFree(header);
+        psFree(extname);
+        return 0;
+    }
+    psFree(headerName);
+    psFree(header);
+
+    psArray *table = psFitsReadTable(fits); // The table
+    if (!psMetadataAdd(cell->analysis, PS_LIST_TAIL, name, PS_DATA_ARRAY, "FITS table", table)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add table from extension %s to analysis metadata "
+                "for chip %s, cell %s\n", extname, chipName, cellName);
+        psFree(table);
+        psFree(extname);
+        return 0;
+    }
+
+    psFree(extname);
+    psFree(table);                      // Dropping reference
+
+    return 1;
+}
+
+
+int pmChipReadTable(pmChip *chip, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, 0);
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numRead = 0;                    // Number of reads
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        numRead += pmCellReadTable(cell, fits, name);
+    }
+
+    return numRead;
+}
+
+
+int pmFPAReadTable(pmFPA *fpa, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, 0);
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numRead = 0;                    // Number of reads
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        numRead += pmChipReadTable(chip, fits, name);
+    }
+
+    return numRead;
+}
Index: /branches/eam_branch_20080324/psModules/src/camera/pmReadoutStack.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/camera/pmReadoutStack.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/camera/pmReadoutStack.c	(revision 17278)
@@ -0,0 +1,184 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmReadoutStack.h"
+
+psImage *pmReadoutAnalysisImage(pmReadout *readout, // Readout containing image
+                                const char *name, // Name of image in analysis metadata
+                                int numCols, int numRows, // Expected size of image
+                                psElemType type, // Expected type of image
+                                double init // Initial value
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    bool mdok;                          // Status of MD lookup
+    psImage *image = psMetadataLookupPtr(&mdok, readout->analysis, name);
+    if (!image) {
+        image = psImageAlloc(numCols, numRows, type);
+        psMetadataAddImage(readout->analysis, PS_LIST_TAIL, name, 0, "Analysis image from " __FILE__, image);
+        psImageInit(image, init);
+        return image;
+    }
+    if (image->numCols != numCols || image->numRows != numRows) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Analysis image %s has incorrect size (%dx%d vs %dx%d)",
+                name, image->numCols, image->numRows, numCols, numRows);
+        return NULL;
+    }
+    if (image->type.type != type) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Analysis image %s has incorrect type (%x vs %x)",
+                name, image->type.type, type);
+        return NULL;
+    }
+    return psMemIncrRefCounter(image);
+}
+
+bool pmReadoutUpdateSize(pmReadout *readout, int minCols, int minRows,
+                         int numCols, int numRows, bool mask, bool weight,
+                         psMaskType blank)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    if (readout->image) {
+        readout->col0 = PS_MIN(minCols, readout->col0);
+        readout->row0 = PS_MIN(minRows, readout->row0);
+    } else {
+        readout->col0 = minCols;
+        readout->row0 = minRows;
+    }
+
+    // (reAllocate the images
+    if (!readout->image) {
+        readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        psImageInit(readout->image, NAN);
+    }
+    if (readout->image->numCols < numCols || readout->image->numRows < numRows) {
+        // Generate the new output image by extending the current one, or making a whole new one
+        psImage *newImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        psImageInit(newImage, NAN);
+        psImageOverlaySection(newImage, readout->image, readout->col0, readout->row0, "=");
+        psFree(readout->image);
+        readout->image = newImage;
+    }
+
+    if (mask) {
+        if (!readout->mask) {
+            readout->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            psImageInit(readout->mask, blank);
+        }
+        if (readout->mask->numCols < numCols || readout->mask->numRows < numRows) {
+            psImage *newMask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            psImageInit(newMask, blank);
+            psImageOverlaySection(newMask, readout->mask, readout->col0, readout->row0, "=");
+            psFree(readout->mask);
+            readout->mask = newMask;
+        }
+    }
+
+    if (weight) {
+        if (!readout->weight) {
+            readout->weight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            psImageInit(readout->weight, NAN);
+        }
+        if (readout->weight->numCols < numCols || readout->weight->numRows < numRows) {
+            psImage *newWeight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            psImageInit(newWeight, NAN);
+            psImageOverlaySection(newWeight, readout->weight, readout->col0, readout->row0, "=");
+            psFree(readout->weight);
+            readout->weight = newWeight;
+        }
+    }
+
+    return true;
+}
+
+bool pmReadoutStackValidate(int *minInputColsPtr, int *maxInputColsPtr, int *minInputRowsPtr,
+                            int *maxInputRowsPtr, int *numColsPtr, int *numRowsPtr,
+                            const psArray *inputs)
+{
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_PTR_NON_NULL(minInputColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(maxInputColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(minInputRowsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(maxInputRowsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(numColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(numRowsPtr, false);
+
+    // Step through each readout in the input image list to determine how big of an output image is needed to
+    // combine these input images.
+    int maxInputCols = 0;               // The largest input column value
+    int maxInputRows = 0;               // The largest input row value
+    int minInputCols = INT_MAX;         // The smallest input column value
+    int minInputRows = INT_MAX;         // The smallest input row value
+    int xSize = 0, ySize = 0;           // The size of the output image
+
+    int xMin = INT_MAX;
+    int yMin = INT_MAX;
+    int xMax = 0;
+    int yMax = 0;
+
+    bool valid = false;                 // Do we have a single valid input?
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+
+        if (!readout) {
+            continue;
+        }
+        if (!readout->image) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Input readout %ld has NULL image.\n", i);
+            return false;
+        }
+
+        // use the trimsec to define the max full range of the output pixels
+        pmCell *cell = readout->parent; // The parent cell
+        bool mdok = true;       // Status of MD lookup
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psWarning("CELL.TRIMSEC is not set for readout %ld --- ignored.\n", i);
+        } else {
+            xSize = PS_MAX(xSize, trimsec->x1 - trimsec->x0);
+            ySize = PS_MAX(ySize, trimsec->y1 - trimsec->y0);
+            xMin  = PS_MIN(xMin,  trimsec->x0);
+            xMax  = PS_MAX(xMax,  trimsec->x1);
+            yMin  = PS_MIN(yMin,  trimsec->y0);
+            yMax  = PS_MAX(yMax,  trimsec->y1);
+        }
+
+        valid = true;
+
+        // Range of pixels on output images
+        minInputCols = PS_MAX(xMin, PS_MIN(minInputCols, readout->col0));
+        maxInputCols = PS_MIN(xMax, PS_MAX(maxInputCols, readout->col0 + readout->image->numCols));
+        minInputRows = PS_MAX(yMin, PS_MIN(minInputRows, readout->row0));
+        maxInputRows = PS_MIN(yMax, PS_MAX(maxInputRows, readout->row0 + readout->image->numRows));
+        psTrace("psModules.camera", 7, "Readout %ld: offset %d,%d; size %dx%d\n", i,
+                readout->col0, readout->row0, readout->image->numCols, readout->image->numRows);
+    }
+
+    if (minInputColsPtr) {
+        *minInputColsPtr = minInputCols;
+    }
+    if (maxInputColsPtr) {
+        *maxInputColsPtr = maxInputCols;
+    }
+    if (minInputRowsPtr) {
+        *minInputRowsPtr = minInputRows;
+    }
+    if (maxInputRowsPtr) {
+        *maxInputRowsPtr = maxInputRows;
+    }
+    if (numColsPtr) {
+        *numColsPtr = xSize;
+    }
+    if (numRowsPtr) {
+        *numRowsPtr = ySize;
+    }
+
+    return valid;
+}
Index: /branches/eam_branch_20080324/psModules/src/camera/pmReadoutStack.h
===================================================================
--- /branches/eam_branch_20080324/psModules/src/camera/pmReadoutStack.h	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/camera/pmReadoutStack.h	(revision 17278)
@@ -0,0 +1,34 @@
+#ifndef PM_READOUT_STACK_H
+#define PM_READOUT_STACK_H
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+#define PM_READOUT_STACK_ANALYSIS_COUNT "STACK.COUNT" // Name for count image in analysis metadata
+#define PM_READOUT_STACK_ANALYSIS_SIGMA "STACK.SIGMA" // Name for sigma image in analysis metadata
+
+/// Update an output readout (for a stack) with the correct col0,row0 and the image size
+bool pmReadoutUpdateSize(pmReadout *readout, ///< Readout which to update
+                         int minCols, int minRows, ///< Minimum coordinates
+                         int numCols, int numRows, ///< Size of images
+                         bool mask,     ///< Worry about the mask?
+                         bool weight,   ///< Worry about the weight?
+                         psMaskType blank ///< Mask value to give to blank pixels
+    );
+
+/// Determine how large an output image is needed to combine the input readouts
+bool pmReadoutStackValidate(int *minInputColsPtr, int *maxInputColsPtr, ///< Min and max size in x
+                            int *minInputRowsPtr, int *maxInputRowsPtr, ///< Min and max size in y
+                            int *numColsPtr, int *numRowsPtr, ///< Size of image
+                            const psArray *inputs ///< Array of pmReadouts
+    );
+
+/// Return an image from analysis metadata, produced while stacking
+psImage *pmReadoutAnalysisImage(pmReadout *readout, // Readout containing image
+                                const char *name, // Name of image in analysis metadata
+                                int numCols, int numRows, // Expected size of image
+                                psElemType type, // Expected type of image
+                                double init // Initial value
+    );
+
+#endif
Index: /branches/eam_branch_20080324/psModules/src/config/pmConfig.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/config/pmConfig.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/config/pmConfig.c	(revision 17278)
@@ -0,0 +1,1514 @@
+/** @file  pmConfig.h
+ *
+ *  @author PAP (IfA)
+ *  @author EAM (IfA)
+ *
+ *  Copyright 2007 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <unistd.h>
+#include <libgen.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmErrorCodes.h"
+#include "pmFPALevel.h"
+#include "pmConfigRecipes.h"
+#include "pmConfigCamera.h"
+
+#ifdef HAVE_NEBCLIENT
+#include <nebclient.h>
+#endif // ifdef HAVE_NEBCLIENT
+
+#define IPPRC_ENV "IPPRC"        // Name of the environment variable containing the top-level config file
+#define IPPRC_FILE ".ipprc"      // Default top-level config file
+
+#define DEFAULT_LOG STDERR_FILENO       // Default file descriptor for log messages
+#define DEFAULT_TRACE STDERR_FILENO     // Default file descriptor for trace messages
+
+static bool readCameraConfig = true;    // Read the camera config on startup (with pmConfigRead)?
+static psArray *configPath = NULL;      // Search path for configuration files
+
+bool pmConfigReadParamsSet(bool newReadCameraConfig)
+{
+    bool oldReadCameraConfig = readCameraConfig;
+    readCameraConfig = newReadCameraConfig;
+    return oldReadCameraConfig;
+}
+
+static void configFree(pmConfig *config)
+{
+    psFree(config->user);
+    psFree(config->site);
+    psFree(config->system);
+    psFree(config->complete);
+    psFree(config->files);
+    psFree(config->camera);
+    psFree(config->cameraName);
+    psFree(config->format);
+    psFree(config->formatName);
+    psFree(config->recipes);
+    psFree(config->recipeSymbols);
+    psFree(config->arguments);
+    psFree(config->database);
+
+    // Close log and trace files
+    if (config->logFD != STDOUT_FILENO && config->logFD != STDERR_FILENO) {
+        close(config->logFD);
+    }
+    if (config->traceFD != STDOUT_FILENO && config->traceFD != STDERR_FILENO) {
+        close(config->traceFD);
+    }
+
+    return;
+}
+
+pmConfig *pmConfigAlloc()
+{
+    pmConfig *config = psAlloc(sizeof(pmConfig));
+    (void)psMemSetDeallocator(config, (psFreeFunc)configFree);
+
+    // Initialise
+    config->user = NULL;
+    config->site = NULL;
+    config->system = NULL;
+    config->complete = NULL;
+    config->camera = NULL;
+    config->cameraName = NULL;
+    config->format = NULL;
+    config->formatName = NULL;
+    config->recipes = psMetadataAlloc();
+    config->recipesRead = PM_RECIPE_SOURCE_NONE;
+    config->recipeSymbols = psMetadataAlloc();
+    config->arguments = psMetadataAlloc();
+    config->database = NULL;
+    config->defaultRecipe = NULL;
+
+    config->traceFD = DEFAULT_TRACE;
+    config->logFD = DEFAULT_LOG;
+
+    // the file structure is used to carry pmFPAfiles
+    config->files = psMetadataAlloc ();
+    return config;
+}
+
+// Resolve environment variables within a directory name; returns the resolved directory string.
+// The returned string is likely a new pointer; the old pointer should be freed by psStringSubstitute.
+static psString resolveEnvVar(psString dir // Directory to check for environment variables
+                             )
+{
+    char *envStart;                     // Start of any environment variable
+    while ((envStart = strchr(dir, '$'))) {
+        char *envName = envStart + 1;   // Start of the environment variable name
+        if (envName[0] == '\0') {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Path %s contains a bad environment variable.\n", dir);
+            return NULL;
+        }
+        if (envName[0] == '{') {
+            envName++;
+            if (envName[0] == '\0') {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                        "Path %s contains a bad environment variable.\n", dir);
+                return NULL;
+            }
+        }
+        char *envStop = strpbrk(envStart, "}/"); // End of the environment variable
+        ssize_t nameLength = envStop ? envStop - envName : strlen(envName); // Length of the name
+        psString name = psStringNCopy(envName, nameLength); // The environment variable name
+        char *value = getenv(name);     // Value of the environment variable
+        psFree(name);
+        psString valueSlash = NULL;    // Value with appended slash
+        psStringAppend(&valueSlash, "%s/", value);
+
+        ssize_t envvarLength = envStop ? envStop - envStart : strlen(envStart); // Length, w/o '}'
+        psString envvar = psStringNCopy(envStart, envvarLength + 1);  // Environment variable, with $, {, }
+
+        psTrace("psModules.config", 7, "Replacing %s with %s in directory %s\n", envvar, valueSlash, dir);
+        psStringSubstitute(&dir, valueSlash, envvar);
+        psFree(envvar);
+        psFree(valueSlash);
+    }
+
+    return dir;
+}
+
+
+void pmConfigSet(const char *path)
+{
+    PS_ASSERT_STRING_NON_EMPTY(path,);
+
+    assert (configPath == NULL);
+    // XXX why was this being called?  pmConfigSet should only be called once...
+    // pmConfigDone();
+
+    psList *list = psStringSplit(path, ":", false);
+    configPath = psListToArray(list);
+    // Resolve environment variables
+    for (long i = 0; i < configPath->n; i++) {
+        configPath->data[i] = resolveEnvVar(configPath->data[i]);
+        psTrace("psModules.config", 4, "Path %ld: %s\n", i, (char*)configPath->data[i]);
+    }
+    psFree(list);
+}
+
+void pmConfigDone(void)
+{
+    if (configPath) {
+        psFree(configPath);
+    }
+    configPath = NULL;
+
+    return;
+}
+
+bool pmConfigFileRead(psMetadata **config, const char *name, const char *description)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+    PS_ASSERT_STRING_NON_EMPTY(description, false);
+
+    char *realName = NULL;
+    unsigned int numBadLines = 0;
+    struct stat filestat;
+
+    psTrace("psModules.config", 3, "Loading %s configuration from file %s\n",
+            description, name);
+
+    uid_t uid = getuid();
+    gid_t gid = getgid();
+
+    // we try: name, path[0]/name, path[1]/name, ...
+    // find the first existing entry in the path (starting with the bare name)
+    realName = psStringCopy (name);
+    psTrace ("psModules.config", 8, "trying %s\n", realName);
+
+    int status = stat (realName, &filestat);
+    if (status == 0) {
+        if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR)) {
+            goto found;
+        }
+        if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP)) {
+            goto found;
+        }
+        if (filestat.st_mode & S_IROTH) {
+            goto found;
+        }
+    }
+    psFree (realName);
+
+    if (configPath == NULL) {
+        psError(PS_ERR_IO, true, "Cannot find %s configuration file (%s) in path\n", description, name);
+        return false;
+    }
+
+    for (int i = 0; i < configPath->n; i++) {
+        realName = psStringCopy (configPath->data[i]);
+        psStringAppend (&realName, "/%s", name);
+        psTrace ("psModules.config", 8, "trying %s\n", realName);
+
+        status = stat (realName, &filestat);
+        if (status == 0) {
+            if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR)) {
+                goto found;
+            }
+            if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP)) {
+                goto found;
+            }
+            if (filestat.st_mode & S_IROTH) {
+                goto found;
+            }
+        }
+        psFree (realName);
+    }
+
+    psError(PS_ERR_IO, true, "Cannot find %s configuration file %s in path\n", description, name);
+    return false;
+
+found:
+    *config = psMetadataConfigRead(NULL, &numBadLines, realName, true);
+    if (numBadLines > 0) {
+        psError(PS_ERR_IO, false, "%d bad lines in %s configuration file (%s)",
+                numBadLines, description, realName);
+        psFree (realName);
+
+        return false;
+    }
+    if (!*config) {
+        psError(PS_ERR_IO, true, "Unable to read %s configuration from %s",
+                description, realName);
+        psFree (realName);
+        return false;
+    }
+
+    psFree (realName);
+    return true;
+}
+
+// Read a file into a metadataItem, if required
+static bool metadataItemReadFile(psMetadataItem *item, // Item into which to read file
+                                 const char *description // Description, for error messages
+    )
+{
+    assert(item);
+    assert(description);
+
+    if (item->type == PS_DATA_METADATA) {
+        return true;                    // We've already read it
+    }
+    if (item->type != PS_DATA_STRING) {
+        psTrace("config", 2, "Element %s in %s metadata is not of type STR.\n",
+                item->name, description);
+        return false;
+    }
+
+    psTrace("config", 2, "Reading %s %s: %s\n", description, item->name, item->data.str);
+    psMetadata *new = NULL;         // New metadata
+    if (!pmConfigFileRead(&new, item->data.str, item->name)) {
+        psError(PM_ERR_CONFIG, false, "Trouble reading reading %s %s.\n",
+                description, item->name);
+        psFree(new);
+        return false;
+    }
+
+    // Muck around under the hood to replace the filename with the metadata; don't try this at home, kids
+    item->type = PS_DATA_METADATA;
+    psFree(item->data.str);
+    item->data.md = new;
+
+    return true;
+}
+
+// Read metadata config files in a metadata
+// The metadata contains file names, which will be replaced with the metadata that are in the files.
+static bool metadataReadFiles(psMetadata *source, // Source metadata
+                              const char *description // Description, for error messages
+                             )
+{
+    assert(source);
+    psMetadataIterator *iter = psMetadataIteratorAlloc(source, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (!metadataItemReadFile(item, description)) {
+            psError(PM_ERR_CONFIG, false, "Unable to read %s %s.", description, item->name);
+            psFree(iter);
+            return false;
+        }
+    }
+    psFree(iter);
+
+    return true;
+}
+
+// Read the formats for a camera
+static bool cameraReadFormats(psMetadata *camera, // Camera for which to read the formats
+                              const char *name // Name of the camera, for error messages
+                             )
+{
+    assert(camera);
+    assert(name);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // Formats
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FORMATS in camera configuration %s.\n", name);
+        return false;
+    }
+    if (!metadataReadFiles(formats, "camera format")) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n", name);
+        return false;
+    }
+
+    return true;
+}
+
+// Read the calibrations for a camera
+static bool cameraReadCalibrations(psMetadata *camera, // Camera for which to read the formats
+                                   const char *cameraName // Name of the camera, for error messages
+    )
+{
+    assert(camera);
+    assert(cameraName);
+
+    psMetadataItem *darkNorm = psMetadataLookup(camera, "DARK.NORM"); // The dark normalisation calibration
+    if (darkNorm) {
+        if (darkNorm->type == PS_DATA_STRING) {
+            const char *darkNormName = darkNorm->data.str; // The file name
+            psTrace("config", 2, "Reading %s dark normalisation: %s\n", cameraName, darkNormName);
+            psMetadata *new = NULL;         // New metadata
+            if (!pmConfigFileRead(&new, darkNormName, "Dark normalisation")) {
+                psError(PM_ERR_CONFIG, false, "Trouble reading reading %s dark normalisation %s --- "
+                        "ignored.\n", cameraName, darkNormName);
+                psFree(new);
+                return false;
+            }
+
+            // Muck around under the hood to replace the filename with the metadata;
+            // don't try this at home, kids
+            darkNorm->type = PS_DATA_METADATA;
+            psFree(darkNorm->data.str);
+            darkNorm->data.md = new;
+        } else if (darkNorm->type != PS_DATA_METADATA) {
+            psWarning("DARK.NORM in camera %s is not of type STR or METADATA (%x)", cameraName, darkNorm->type);
+        }
+    } else {
+        // Add a dummy entry
+        psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Dummy polynomial
+        poly->coeff[0] = 0.0;
+        poly->coeff[1] = 1.0;
+        psMetadata *polyMD = psMetadataAlloc(); // Container for the polynomial
+        (void)psPolynomial1DtoMetadata(polyMD, poly, "_DEFAULT"); // Metadata to insert
+        psFree(poly);
+        psMetadataAddMetadata(camera, PS_LIST_TAIL, "DARK.NORM", 0, "Dark normalisation polynomial",
+                              polyMD);
+        psMetadataAddStr(camera, PS_LIST_TAIL, "DARK.NORM.KEY", 0, "Key for dark normalisation", "_DEFAULT");
+        psFree(polyMD);
+    }
+
+    return true;
+}
+
+pmConfig *pmConfigRead(int *argc, char **argv, const char *defaultRecipe)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_INT_POSITIVE(*argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    pmConfig *config = pmConfigAlloc(); // The configuration, containing site, camera and recipes
+    config->defaultRecipe = defaultRecipe;
+
+    // The following section of code attempts to determine which file to use as the
+    // top-level the configuration file.  At the end of this code block, the configFile
+    // variable will contain the name of the configuration file.
+
+    char *configFile = NULL;
+    //
+    // First, try command line
+    //
+    psS32 argNum = psArgumentGet(*argc, argv, "-ipprc");
+    if (argNum != 0) {
+        // remove the "-ipprc" argument from argv, check and remove filename
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-ipprc command-line switch provided without the required filename --- ignored.\n");
+        } else {
+            configFile = psStringCopy(argv[argNum]);
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+    //
+    // Next, try environment variable
+    //
+    if (!configFile) {
+        configFile = getenv(IPPRC_ENV);
+        if (configFile) {
+            configFile = psStringCopy (configFile);
+        }
+    }
+
+    //
+    // Last chance is ~/.ipprc
+    //
+    if (!configFile) {
+        char *home = getenv("HOME");
+        configFile = psStringCopy(home);
+        psStringAppend(&configFile, "/%s", IPPRC_FILE);
+    }
+
+    // We have the configuration filename; now we read and parse the config
+    // file and store in psMetadata struct user.
+    // XXX move this section to pmConfigReadUser.c ?
+
+    if (!pmConfigFileRead(&config->user, configFile, "user")) {
+        psFree(config);
+        psFree(configFile);
+        return NULL;
+    }
+    psFree(configFile);
+
+    // XXX why was this being called here?  Is someone calling pmConfigRead multiple times?
+    // pmConfigDone();
+    assert (configPath == NULL);
+
+    // define the config-file search path (configPath).
+    psString path = psMetadataLookupStr(NULL, config->user, "PATH");
+    pmConfigSet (path);
+
+    // read the SITE file
+    psString siteFile = psMetadataLookupStr(NULL, config->user, "SITE");
+    if (!pmConfigFileRead(&config->site, siteFile, "site")) {
+        psFree(config);
+        return NULL;
+    }
+
+    // load the SYSTEM file
+    psString systemFile = psMetadataLookupStr(NULL, config->user, "SYSTEM");
+    if (!pmConfigFileRead(&config->system, systemFile, "system")) {
+        psFree(config);
+        return NULL;
+    }
+
+    // interpolate USER, SITE and SYSTEM into the config->complete metadata
+    config->complete = psMetadataCopy (NULL,             config->user);
+    config->complete = psMetadataCopy (config->complete, config->site);
+    config->complete = psMetadataCopy (config->complete, config->system);
+
+    // Set LOG and TRACE options based on the user configuration.  These must be set AFTER
+    // the SITE and SYSTEM config files are read so path:// entries here can be resolved.
+    {
+        bool mdok = true;   // Status of MD lookup result
+
+        // Set logging level
+        int logLevel = psMetadataLookupS32(&mdok, config->user, "LOGLEVEL");
+        if (mdok && logLevel >= 0)
+        {
+            psTrace("psModules.config", 7, "Setting log level to %d\n", logLevel);
+            psLogSetLevel(logLevel);
+        }
+
+
+        // Set logging format
+        psString logFormat = psMetadataLookupStr(&mdok, config->user, "LOGFORMAT");
+        if (mdok && logFormat)
+        {
+            psTrace("psModules.config", 7, "Setting log format to %s\n", logFormat);
+            psLogSetFormat(logFormat);
+        }
+
+        // Set logging destination first from command line, second from user configuration
+        psString logDest = NULL;        // Logging destination
+        argNum = psArgumentGet(*argc, argv, "-log");
+        if (argNum > 0) {
+            psArgumentRemove(argNum, argc, argv);
+            if (argNum >= *argc) {
+                psWarning("-log command-line switch provided without the required log destination "
+                          "--- ignored.");
+            } else {
+                logDest = psStringCopy(argv[argNum]);
+                psArgumentRemove(argNum, argc, argv);
+            }
+        }
+        if (!logDest) {
+            logDest = psMemIncrRefCounter(psMetadataLookupStr(&mdok, config->user, "LOGDEST"));
+        }
+        if (logDest) {
+            psString resolved = pmConfigConvertFilename(logDest, config, true); // Resolved filename
+            if (!resolved || strlen(resolved) == 0) {
+                psWarning("Unable to resolve log destination: %s --- ignored", logDest);
+            } else {
+                config->logFD = psMessageDestination(resolved);
+            }
+            psFree(resolved);
+            psFree(logDest);
+        }
+        if (!psLogSetDestination(config->logFD)) {
+            psError(PS_ERR_IO, false, "Unable to set log destination to file number %d --- ignored",
+                    config->logFD);
+            psFree(config);
+            return NULL;
+        }
+
+        // Set trace levels
+        psMetadata *trace = psMetadataLookupMetadata(&mdok, config->user, "TRACE");
+        if (mdok && trace) {
+            psMetadataIterator *traceIter = psMetadataIteratorAlloc(trace, PS_LIST_HEAD, NULL); // Iterator
+            psMetadataItem *traceItem = NULL; // Item from MD iteration
+            while ((traceItem = psMetadataGetAndIncrement(traceIter))) {
+                if (traceItem->type != PS_DATA_S32) {
+                    psWarning("The level for trace component %s is not of type S32 (%x)\n",
+                             traceItem->name, traceItem->type);
+                    continue;
+                }
+                psTrace("psModules.config", 7, "Setting trace level for %s to %d\n",
+                        traceItem->name, traceItem->data.S32);
+                (void)psTraceSetLevel(traceItem->name, traceItem->data.S32);
+            }
+            psFree(traceIter);
+        }
+
+        // Set trace formats
+        psString traceFormat = psMetadataLookupStr(&mdok, config->user, "TRACEFORMAT");
+        if (mdok && traceFormat) {
+            psTrace("psModules.config", 7, "Setting trace format to %s\n", traceFormat);
+            (void)psTraceSetFormat(traceFormat);
+        }
+
+        // Set trace destinations
+        psString traceDest = NULL;      // Trace destination
+        argNum = psArgumentGet(*argc, argv, "-tracedest");
+        if (argNum > 0) {
+            psArgumentRemove(argNum, argc, argv);
+            if (argNum >= *argc) {
+                psWarning("-tracedest command-line switch provided without the required trace destination "
+                          "--- ignored.\n");
+            } else {
+                traceDest = psStringCopy(argv[argNum]);
+                psArgumentRemove(argNum, argc, argv);
+            }
+        }
+        if (!traceDest) {
+            traceDest = psMemIncrRefCounter(psMetadataLookupStr(&mdok, config->user, "TRACEDEST"));
+        }
+        if (traceDest) {
+            psString resolved = pmConfigConvertFilename(traceDest, config, true); // Resolved filename
+            if (!resolved || strlen(resolved) == 0) {
+                psWarning("Unable to resolve trace destination: %s --- ignored", traceDest);
+            } else {
+                config->traceFD = psMessageDestination(resolved);
+            }
+            psFree(resolved);
+            psFree(traceDest);
+        }
+        if (!psTraceSetDestination(config->traceFD)) {
+            psError(PS_ERR_IO, false, "Unable to set trace destination to file number %d --- ignored",
+                    config->traceFD);
+            psFree(config);
+            return NULL;
+        }
+
+        // Allow command line options to override defaults for logging.
+        // XXX: Is it appropriate to use the ArgVerbosity function for this?
+        //   A: it removes the options from the command line.
+        //   B: will the pmConfigRead function always be called on initialization.
+        //
+        psArgumentVerbosity(argc, argv);
+        // XXX: substitute the string for the default log level for "2".
+    }
+
+    // XXX read TIME from SITE (or system?)
+    {
+        bool mdok = true;
+
+        // Initialise the psLib time handling
+        // XXX is this still needed / desired?
+        psString timeName = psMetadataLookupStr(&mdok, config->complete, "TIME");
+        if (mdok && timeName)
+        {
+            psTrace("psModules.config", 7, "Initialising psTime with file %s\n", timeName);
+            psTimeInit(timeName);
+        }
+    }
+
+    // Next, we do a similar thing for the camera configuration file.  The
+    // file is read and parsed into psMetadata struct "camera".
+    argNum = psArgumentGet(*argc, argv, "-camera");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-camera command-line switch provided without the required camera or filename --- "
+                      "ignored.\n");
+        } else {
+            bool mdok = true;           // Status of MD lookup
+            char *cameraName = argv[argNum]; // symbolic name of the camera
+
+            // look for the CAMERAS list in config->complete
+            psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS");
+            if (!cameras) {
+                psError(PS_ERR_IO, false, "Unable to find CAMERAS in site configuration.\n");
+                psFree(config);
+                return NULL;
+            }
+
+            // look for the symbolic camera name in the CAMERAS metadata
+            char *cameraFile = psMetadataLookupStr(&mdok, cameras, cameraName); // The filename
+            if (!cameraFile) {
+                psError(PS_ERR_IO, false, "%s is not listed in the site CAMERAS list\n", cameraName);
+                psFree(config);
+                return NULL;
+            }
+
+            // load this camera's configuration informatoin
+            if (!pmConfigFileRead(&config->camera, cameraFile, "camera")) {
+                psError(PM_ERR_CONFIG, false, "Problem reading %s", cameraName);
+                psFree(config);
+                return NULL;
+            }
+            // save the name for future uses
+            config->cameraName = psStringCopy (cameraName);
+
+            psArgumentRemove(argNum, argc, argv);
+
+            // Read in the formats
+            if (!cameraReadFormats(config->camera, cameraFile)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n",
+                        cameraFile);
+                psFree(config);
+                return NULL;
+            }
+
+            // Read in any camera-specific calibrations
+            if (!cameraReadCalibrations(config->camera, cameraName)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to read calibrations within camera configuration %s.\n",
+                        cameraName);
+                psFree(config);
+                return NULL;
+            }
+
+            psMetadataAddMetadata(cameras, PS_LIST_HEAD, cameraName, PS_META_REPLACE,
+                                  "Camera specified on command line", config->camera);
+
+            if (!pmConfigCameraSkycellVersion(config->complete, cameraName)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to generate skycell versions of specified camera %s.\n",
+                        cameraName);
+                psFree(config);
+                return NULL;
+            }
+
+            if (!pmConfigCameraMosaickedVersions(config->complete, cameraName)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to generate mosaicked versions of specified camera %s.\n",
+                        cameraName);
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+
+    // Read the camera configurations, if not already defined, and not turned off
+    if (!config->camera && readCameraConfig) {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS"); // List of cameras
+        if (!mdok || !cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+            return false;
+        }
+
+        if (!metadataReadFiles(cameras, "camera configuration")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within system configuration.\n");
+            psFree(config);
+            return NULL;
+        }
+
+        // Now fill in the formats and calibrations
+        psMetadataIterator *iter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            assert(item->type == PS_DATA_METADATA);
+            if (!cameraReadFormats(item->data.md, item->name)) {
+                psWarning("Unable to read formats for camera %s: removed.\n", item->name);
+                psErrorStackPrint(stderr, "errors from read failure\n");
+                psErrorClear();
+                psMetadataRemoveKey(cameras, item->name);
+                continue;
+            }
+            if (!cameraReadCalibrations(item->data.md, item->name)) {
+                psWarning("Unable to read calibrations for camera %s: removed.\n", item->name);
+                psErrorStackPrint(stderr, "errors from read failure\n");
+                psErrorClear();
+                psMetadataRemoveKey(cameras, item->name);
+                continue;
+            }
+        }
+        psFree(iter);
+
+        if (!pmConfigCameraSkycellVersionsAll(config->complete)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate skycell versions of cameras.\n");
+            psFree(config);
+            return NULL;
+        }
+        if (!pmConfigCameraMosaickedVersionsAll(config->complete)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate mosaicked versions of cameras.\n");
+            psFree(config);
+            return NULL;
+        }
+    }
+
+    // Load the recipes from the camera file, if appropriate
+    if(!pmConfigReadRecipes(config, PM_RECIPE_SOURCE_SYSTEM | PM_RECIPE_SOURCE_CAMERA)) {
+        psError(PS_ERR_IO, false, "Failed to read recipes from camera file");
+        psFree(config);
+        return NULL;
+    }
+
+    // load command-line options of the form -recipe NAME RECIPE
+    pmConfigLoadRecipeArguments(argc, argv, config);
+
+    // read in command-line options to specific recipe values
+    pmConfigLoadRecipeOptions(argc, argv, config, "-D");
+    pmConfigLoadRecipeOptions(argc, argv, config, "-Di");
+    pmConfigLoadRecipeOptions(argc, argv, config, "-Df");
+    pmConfigLoadRecipeOptions(argc, argv, config, "-Db");
+
+    // Look for command-line options for files to replace
+    argNum = psArgumentGet(*argc, argv, "-F");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum + 1 >= *argc) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Filerule switch (-F) provided without old and new filerule.");
+            psFree(config);
+            return NULL;
+        }
+
+        const char *old = argv[argNum]; // The old file, to be replaced
+        psArgumentRemove(argNum, argc, argv);
+        const char *new = argv[argNum]; // The new file, the replacement
+        psArgumentRemove(argNum, argc, argv);
+
+        psMetadata *cameras = psMetadataLookupMetadata(NULL, config->complete, "CAMERAS"); // List of cameras
+        if (!cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find CAMERAS in the site configuration.\n");
+            return false;
+        }
+
+        psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *cameraItem;     // Item from iteration
+        while ((cameraItem = psMetadataGetAndIncrement(camerasIter))) {
+            // Silently ignore problems --- they will be caught later, because if the user wants the nominated
+            // file and it's not available for that camera, then they will know.
+
+            if (cameraItem->type != PS_DATA_METADATA) {
+                psTrace("psModules.config", 2,
+                        "Entry %s in CAMERAS is not of type METADATA --- ignored.", cameraItem->name);
+                continue;
+            }
+            psMetadata *camera = cameraItem->data.md; // Camera configuration
+
+            psMetadata *newRule = pmConfigFileRule(config, camera, new); // The rule of interest
+            if (!newRule) {
+                psTrace("psModules.config", 2,
+                        "Unable to find filerule %s in camera %s --- ignored.", new, cameraItem->name);
+                continue;
+            }
+
+            // By calling pmConfigFileRule, we've assured that the FILERULES is now a metadata
+            psMetadata *filerules = psMetadataLookupMetadata(NULL, camera, "FILERULES"); // File rules
+            if (!filerules) {
+                psTrace("psModules.config", 2,
+                        "Can't find FILERULES of type METADATA in camera %s --- ignored.", cameraItem->name);
+                continue;
+            }
+
+            psMetadataAddMetadata(filerules, PS_LIST_TAIL, old, PS_META_REPLACE,
+                                  "Original replaced by -F option", newRule);
+        }
+        psFree(camerasIter);
+    }
+
+    // check for values that override DB* keywords
+    argNum = psArgumentGet(*argc, argv, "-dbserver");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbserver command-line switch provided without the required server name --- ");
+        } else {
+            char *dbserver = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBSERVER", PS_META_REPLACE, NULL, dbserver)) {
+                psWarning("Failed to overwrite .ipprc DBSERVER value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbname");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbname command-line switch provided without the required database name");
+        } else {
+            char *dbname = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBNAME", PS_META_REPLACE, NULL, dbname)) {
+                psWarning("Failed to overwrite .ipprc DBNAME value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbuser");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbuser command-line switch provided without the required database name");
+        } else {
+            char *dbuser = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBUSER", PS_META_REPLACE, NULL, dbuser)) {
+                psWarning("Failed to overwrite .ipprc DBUSER value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbpassword");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbpassword command-line switch provided without the required password");
+        } else {
+            char *dbpassword = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBPASSWORD", PS_META_REPLACE,
+                                  NULL, dbpassword)) {
+                psWarning("Failed to overwrite .ipprc DBPASSWORD value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbport");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbpport command-line switch provided without the required port number");
+        } else {
+            char *dbport = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddS32(config->complete, PS_LIST_TAIL, "DBPORT", PS_META_REPLACE, NULL,
+                                  (psS32)atoi(dbport))) {
+                psWarning("Failed to overwrite .ipprc DBPORT value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+
+    psErrorClear();   // we may have failed to find some items in the metadata
+
+    return config;
+}
+
+
+// does this header match the specified camera format?  answer is supplied to 'valid' the
+// return value defines the error condition. error only on config errors
+bool pmConfigValidateCameraFormat(bool *valid, const psMetadata *cameraFormat, const psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(cameraFormat, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    // Read the rule for that camera format
+    bool mdStatus = true;
+
+    psMetadata *rule = psMetadataLookupMetadata(&mdStatus, cameraFormat, "RULE");
+    if (! mdStatus || ! rule) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read rule for camera.");
+        *valid = false;
+        return false;
+    }
+
+    // grab the metadata items in sequence by key so we get the MULTI entry
+    psList *keyList = psMetadataKeys (rule);
+    psArray *keys = psListToArray (keyList);
+    if (! keys) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read rule for camera.");
+        *valid = false;
+        return false;
+    }
+
+    *valid = true;
+    for (int i = 0; *valid && (i < keys->n); i++) {
+
+        // get the ruleItem for this key
+        psMetadataItem *ruleItem = psMetadataLookup(rule, keys->data[i]);
+
+        // Check for the existence of the rule in the header
+        psMetadataItem *headerItem = psMetadataLookup(header, ruleItem->name);
+        if (! headerItem) {
+            // rule item not found in header
+            psTrace("psModules.config", 5, "Can't find %s", ruleItem->name);
+            *valid = false;
+            continue;
+        }
+
+        // if the RULE type is a primitive type (int, float, etc) or string compare directly
+        if (PS_DATA_IS_PRIMITIVE (ruleItem->type) || (ruleItem->type == PS_DATA_STRING)) {
+            // Check to see if the rule works
+            if (!psMetadataItemCompare(headerItem, ruleItem)) {
+                psTrace("psModules.config", 5, "%s doesn't match.", ruleItem->name);
+                *valid = false;
+            }
+            continue;
+        }
+
+        // for MULTI, try each one & succeed if any match (valid = true is default state)
+        if (ruleItem->type == PS_DATA_METADATA_MULTI) {
+            bool found = false;
+            for (int j = 0; j < ruleItem->data.list->n; j++) {
+                psMetadataItem *entry = psListGet (ruleItem->data.list, j);
+                assert (entry);
+                if (psMetadataItemCompare(headerItem, entry)) {
+                    found = true;
+                    psTrace("psModules.config", 5, "%s in multi list matches.", ruleItem->name);
+                    break;
+                }
+            }
+            if (!found) {
+                *valid = false;
+                psTrace("psModules.config", 5, "%s doesn't match.", ruleItem->name);
+            }
+            continue;
+        }
+
+        psError(PS_ERR_UNKNOWN, false, "Invalid type for RULE %s.", ruleItem->name);
+        *valid = false;
+        psFree (keyList);
+        psFree (keys);
+        return false;
+    }
+
+    psFree (keyList);
+    psFree (keys);
+    return true;
+}
+
+// Given a camera and a header, see if any of the camera formats match the header
+static bool formatFromHeader(psMetadata **format, // Format to return
+                             psString *name, // Name to return
+                             psMetadata *camera, // Camera configuration
+                             const psMetadata *header, // FITS header
+                             const char *cameraName // Name of camera
+                            )
+{
+    assert(format);
+    assert(camera);
+    assert(header);
+    assert(cameraName);
+    assert(*cameraName);
+
+    bool result = false;                // Did we find the first match?
+
+    // Read the list of formats
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // List of formats
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find list of FORMATS in camera %s", cameraName);
+        return false;
+    }
+
+    if (!metadataReadFiles(formats, "camera format")) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read cameras formats within camera configuration.\n");
+        return false;
+    }
+
+    // Iterate over the formats
+    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL);
+    psMetadataItem *formatsItem = NULL; // Item from formats
+    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
+        assert(formatsItem->type == PS_DATA_METADATA); // Since we have just read it in or deleted it
+        psMetadata *testFormat = formatsItem->data.md; // Format to test against
+
+        psTrace("psModules.config", 5, "trying format %s", formatsItem->name);
+
+        bool valid = false;
+        if (!pmConfigValidateCameraFormat(&valid, testFormat, header)) {
+            psError (PS_ERR_UNKNOWN, false, "Error in config scripts for camera %s, format %s\n",
+                     cameraName, formatsItem->name);
+            return false;
+        }
+        if (valid) {
+            if (!*format) {
+                psLogMsg("psModules.config", PS_LOG_INFO, "Camera %s, format %s matches header.\n",
+                         cameraName, formatsItem->name);
+                *format = psMemIncrRefCounter(testFormat);
+                *name = psStringCopy(formatsItem->name);
+                result = true;
+            } else {
+                psWarning("Camera %s, format %s also matches header --- ignored.\n",
+                         cameraName, formatsItem->name);
+            }
+        }
+    }
+    psFree(formatsIter);
+    return result;
+}
+
+// we only need to read the recipe for the main camera images.  for additional images (eg, sky
+// cells) do not need to re-read the recipe files!
+psMetadata *pmConfigCameraFormatFromHeader(pmConfig *config, const psMetadata *header, bool readRecipes)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(header, NULL);
+
+    psMetadata *format = NULL;          // The winning format
+    psString name = NULL;               // Name of the winning format
+
+    // If we don't know what sort of camera we have, we try all that we know
+    if (! config->camera) {
+        bool mdok;                      // Metadata lookup status
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS");
+        if (! mdok || !cameras) {
+            psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+            return NULL;
+        }
+
+        if (!metadataReadFiles(cameras, "camera configuration")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within site configuration.\n");
+            return NULL;
+        }
+
+        // Iterate over the cameras
+        psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL);
+        psMetadataItem *camerasItem = NULL; // Item from the metadata
+        while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+            // Open the camera information
+            psTrace("psModules.config", 3, "Inspecting camera %s (%s)\n", camerasItem->name,
+                    camerasItem->comment);
+            assert(camerasItem->type == PS_DATA_METADATA); // It should be because we've read it in or deleted
+            psMetadata *testCamera = camerasItem->data.md; // Camera to test against what we've got:
+            if (formatFromHeader(&format, &name, testCamera, header, camerasItem->name)) {
+                // Set the defaults, if they are not already set
+                if (!config->camera && !config->cameraName) {
+                    config->camera = psMemIncrRefCounter(testCamera);
+                    config->cameraName = psStringCopy(camerasItem->name);
+                }
+                if (!config->format && !config->formatName) {
+                    config->formatName = name;
+                    config->format = format;
+                } else {
+                    psFree(name);
+                    psFree(format);
+                }
+            } else {
+              // XXX have to be careful with this construction: it catches other errors cluttering up the stack...
+                psErr *error = psErrorLast();
+                if (error->code != PS_ERR_NONE) {
+                    psError(PS_ERR_IO, false, "Error reading camera config data for %s", camerasItem->name);
+                    return NULL;
+                }
+                psFree(error);
+            }
+        } // Done looking at all cameras
+        psFree(camerasIter);
+
+        if (! config->camera) {
+            psError(PS_ERR_IO, false, "Unable to find a camera that matches input FITS header!");
+            return NULL;
+        }
+
+        // Now we have the camera, we can read the recipes
+        if (readRecipes && !pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CAMERA | PM_RECIPE_SOURCE_CL)) {
+            psError(PS_ERR_IO, false, "Error reading recipes from camera config for %s", config->cameraName);
+            return NULL;
+        }
+        return psMemIncrRefCounter(format);
+    }
+
+    // Otherwise, try the specific camera
+    if (!formatFromHeader(&format, &name, config->camera, header, config->cameraName)) {
+        psError(PS_ERR_IO, true, "Unable to find a format with the specified camera (%s) that matches the "
+                "given header.\n", config->cameraName);
+        return NULL;
+    }
+
+    // Now that we have the camera, we can apply recipes based on command-line arguments
+    if (readRecipes && !pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CL)) {
+        psError(PS_ERR_IO, false, "Error reading recipes from camera config for %s", config->cameraName);
+        return NULL;
+    }
+
+    if (!config->format && !config->formatName) {
+        config->formatName = name;
+        config->format = format;
+    } else {
+        psFree(format);
+        psFree(name);
+    }
+    return psMemIncrRefCounter(format);
+}
+
+// Return the requested camera configuration
+psMetadata *pmConfigCameraByName(pmConfig *config, const char *cameraName)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(cameraName, NULL);
+
+    psMetadata *cameras = psMetadataLookupMetadata(NULL, config->complete, "CAMERAS");
+    if (!cameras) {
+        psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+        return NULL;
+    }
+
+    char *cameraPath = psMetadataLookupStr(NULL, cameras, cameraName);
+    if (!cameraPath) {
+        psError(PS_ERR_IO, true, "Unable to find requested CAMERA in the configuration.");
+        return NULL;
+    }
+
+    psMetadata *camera = NULL; // Camera to test against what we've got:
+
+    if (!pmConfigFileRead(&camera, cameraPath, cameraName)) {
+        psWarning("Trouble reading reading camera configuration %s", cameraName);
+        psFree(camera);
+        return NULL;
+    }
+
+    return camera;
+}
+
+psDB *pmConfigDB(pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->complete, NULL);
+
+    #ifndef HAVE_PSDB
+
+    psError(PS_ERR_UNKNOWN, false,
+            "Cannot configure database: psModules was compiled without database support.");
+    return NULL;
+
+    #else
+
+    psBool mdStatus01 = false;
+    psBool mdStatus02 = false;
+    psBool mdStatus03 = false;
+    psBool mdStatus04 = false;
+    psBool mdStatus05 = false;
+
+    // XXX leaky strings
+    psString dbServer = psMetadataLookupStr(&mdStatus01, config->complete, "DBSERVER");
+    psString dbUsername = psMetadataLookupStr(&mdStatus02, config->complete, "DBUSER");
+    psString dbPassword = psMetadataLookupStr(&mdStatus03, config->complete, "DBPASSWORD");
+    psString dbName = psMetadataLookupStr(&mdStatus04, config->complete, "DBNAME");
+    psS32 dbPort = psMetadataLookupS32(&mdStatus05, config->complete, "DBPORT");
+    if (!mdStatus05) {
+        dbPort = 0;
+    }
+    if (!(mdStatus01 && mdStatus02 && mdStatus03 && mdStatus04)) {
+        psWarning("Could not determine database server, name, user, and password from site metadata.\n");
+        return NULL;
+    }
+
+    return psDBInit(dbServer, dbUsername, dbPassword, dbName, dbPort);
+    #endif
+}
+
+
+bool pmConfigConformHeader(psMetadata *header, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(header, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *rules = psMetadataLookupMetadata(&mdok, format, "RULE"); // How to identify this format
+    if (!mdok || !rules) {
+        psError(PS_ERR_IO, true, "Unable to find RULE in camera format.\n");
+        return false;
+    }
+
+    psMetadataIterator *rulesIter = psMetadataIteratorAlloc(rules, PS_LIST_HEAD, NULL); // Iterator for rules
+    psMetadataItem *rulesItem = NULL;   // Item from iteration
+    while ((rulesItem = psMetadataGetAndIncrement(rulesIter))) {
+        // this will insert each of the MULTI entries, writing over the previous copies
+        if (PS_DATA_IS_PRIMITIVE (rulesItem->type) || (rulesItem->type == PS_DATA_STRING)) {
+            psMetadataItem *newItem = psMetadataItemCopy(rulesItem); // Copy of item
+            psMetadataAddItem(header, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+            psFree(newItem);                // Drop reference
+            continue;
+        }
+
+        # if (0) // XXX not needed
+        // for MULTI entries, supply the first one
+        if (rulesItem->type == PS_DATA_METADATA_MULTI) {
+            psMetadataItem *entry = psListGet (rulesItem->data.list, PS_LIST_HEAD);
+            if (!entry) {
+                psError(PS_ERR_UNKNOWN, true, "No entries for MULTI RULE item.");
+                return false;
+            }
+            psMetadataItem *newItem = psMetadataItemCopy(entry); // Copy of item
+            psMetadataAddItem(header, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+            psFree(newItem);                // Drop reference
+            continue;
+        }
+        # endif
+
+        psError(PS_ERR_UNKNOWN, false, "Invalid type for RULE %s.", rulesItem->name);
+        return false;
+    }
+    psFree(rulesIter);
+
+    return true;
+}
+
+psArray *pmConfigFileSets(int *argc, char **argv, const char *file, const char *list)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(*argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    int Narg;                           // Argument number
+
+    // we load all input files onto a psArray, to be parsed later
+    psArray *input = psArrayAllocEmpty(16);
+
+    // load the list of filenames the supplied file
+    // maybe a comma-separated list of words
+    // each word may be a glob: "file*.fits"
+    if (file && strlen(file) > 0 && (Narg = psArgumentGet (*argc, argv, file))) {
+
+        // select the word after 'file' and split by comma
+        psArgumentRemove (Narg, argc, argv);
+        psArray *words = psStringSplitArray (argv[Narg], ",", true);
+        psArgumentRemove (Narg, argc, argv);
+
+        // parse the word as a glob
+        glob_t globList;
+        for (int i = 0; i < words->n; i++) {
+            globList.gl_offs = 0;
+            glob (words->data[i], 0, NULL, &globList);
+
+            // if the glob does not match, save the literal word:
+            // otherwise save all glob matches
+            if (globList.gl_pathc == 0) {
+                psArrayAdd (input, 16, words->data[i]);
+            } else {
+                for (int j = 0; j < globList.gl_pathc; j++) {
+                    char *filename = psStringCopy (globList.gl_pathv[j]);
+                    psArrayAdd (input, 16, filename);
+                    psFree (filename);
+                }
+            }
+        }
+        psFree (words);
+    }
+
+    // load the list from the supplied text file
+    if (list && strlen(list) > 0 && (Narg = psArgumentGet(*argc, argv, list))) {
+        psArgumentRemove (Narg, argc, argv);
+        FILE *f = fopen(argv[Narg], "r");
+        if (!f) {
+            psError(PS_ERR_IO, true, "Unable to open specified list file");
+            psFree(input);
+            return NULL;
+        }
+
+        // XXX Reading the list should be reimplemented using psSlurp
+
+        char line[1024]; // XXX limits the list lines to 1024 chars
+        while (fgets(line, 1024, f) != NULL) {
+            char word[1024];
+            int nItems = sscanf(line, "%s", word);
+            switch (nItems) {
+              case 0:
+                break;
+              case 1: {
+                  psString filename = psStringCopy(word);
+                  psArrayAdd(input, 16, filename);
+                  psFree(filename);
+                  break;
+              }
+              default:
+                // rigid format, no comments allowed?
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to parse file list: spaces detected.");
+                psFree(input);
+                fclose(f);
+                return NULL;
+            }
+        }
+        psArgumentRemove(Narg, argc, argv);
+        fclose(f);
+    }
+
+    return input;
+}
+
+bool pmConfigFileSetsMD(psMetadata *metadata, int *argc, char **argv, const char *name,
+                        const char *file, const char *list)
+{
+    PS_ASSERT_PTR_NON_NULL(metadata, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    psErrorClear();   // pmConfigFileSets may or may not call psError, so
+    // if files->n == 0 we'll want to call psError(..., false, ...)
+    psArray *files = pmConfigFileSets(argc, argv, file, list);
+    if (files->n == 0) {
+        // psError(PS_ERR_IO, false, "pmConfigFileSets failed to find %s in the argument list", name);
+        psFree (files);
+        return false;
+    }
+
+    psMetadataAddPtr(metadata, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", files);
+    psFree (files);
+    return true;
+}
+
+// convert the supplied name, create a new output psString
+psString pmConfigConvertFilename(const char *filename, const pmConfig *config, bool create)
+{
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // strip file:// from front of name
+    if (!strncasecmp(filename, "file:", strlen("file:"))) {
+        psString newName = psStringCopy(filename);
+
+        char *point = newName + strlen("file:");
+        while (*point == '/')
+            point ++;
+        char *tmpName = NULL;
+        psStringAppend (&tmpName, "/%s", point);
+        psFree (newName);
+        newName = tmpName;
+
+        if (!create && access(newName, R_OK) != 0) {
+            psError(PS_ERR_IO, true, "Unable to access file %s", newName);
+            psFree(newName);
+            return NULL;
+        }
+
+        return newName;
+    }
+
+    // replace path://PATH with matched datapath
+    if (!strncasecmp(filename, "path://", strlen("path://"))) {
+        PS_ASSERT_METADATA_NON_NULL(config->complete, NULL);
+
+        psString newName = psStringCopy(filename);
+
+        // filename should be of the form: path://PATH/rest/of/file
+        // replace PATH with matching name from config->complete:DATAPATH
+        psMetadata *datapath = psMetadataLookupPtr (NULL, config->complete, "DATAPATH");
+        if (datapath == NULL) {
+            psError(PS_ERR_UNKNOWN, true, "DATAPATH is not defined in config.site");
+            psFree (newName);
+            return NULL;
+        }
+
+        char *point = newName + strlen("path://");
+        char *mark = strchr (point, '/');
+        if (mark == NULL) {
+            psError(PS_ERR_UNKNOWN, true, "syntax error in PATH-style name %s", newName);
+            psFree (newName);
+            return false;
+        }
+
+        psString path = psStringNCopy (point, mark - point);
+        char *realpath = psMetadataLookupStr (NULL, datapath, path);
+        if (realpath == NULL) {
+            psError(PS_ERR_UNKNOWN, true,
+                    "path (%s) not defined in config.site:DATAPATH for PATH-style name %s",
+                    path, newName);
+            psFree(newName);
+            psFree(path);
+            return false;
+        }
+        psFree(path);
+
+        char *tmpName = NULL;
+        psStringAppend(&tmpName, "%s/%s", realpath, mark + 1);
+        psFree(newName);
+        newName = tmpName;
+
+        if (!create && access(newName, R_OK) != 0) {
+            psError(PS_ERR_IO, true, "Unable to access file %s", newName);
+            psFree(newName);
+            return NULL;
+        }
+        return newName;
+    }
+
+    // substitute neb://name with matched nebulous name
+    if (!strncasecmp(filename, "neb://", strlen("neb://"))) {
+        #ifdef HAVE_NEBCLIENT
+
+        bool status = false;
+        psString neb_server = NULL;
+
+        // check the env first
+        neb_server = getenv("NEB_SERVER");
+
+        // if env isn't set, check the config system
+        if (!neb_server) {
+            neb_server = psMetadataLookupStr(&status, config->complete, "NEB_SERVER");
+            if (!status) {
+                psError(PM_ERR_CONFIG, true, "failed to lookup config value for NEB_SERVER.");
+                return NULL;
+            }
+        }
+
+        if (!neb_server) {
+            psError(PM_ERR_CONFIG, true, "Could not determine nebulous server URI.");
+            return NULL;
+        }
+
+        nebServer *server = nebServerAlloc(neb_server);
+        if (!server) {
+            psError(PM_ERR_SYS, true, "failed to create a nebServer object: %s", nebErr(server));
+            nebServerFree(server);
+            return NULL;
+        }
+
+        // ignore the leading neb://
+        char *cleanFilename = (char *)filename + strlen("neb://");
+
+        char *nebfile = NULL;
+        if (create) {
+            nebfile = nebCreate(server, cleanFilename, NULL, NULL);
+            if (!nebfile) {
+                psError(PM_ERR_SYS, true, "failed to create a new nebulous key: %s", nebErr(server));
+                nebServerFree(server);
+                return NULL;
+            }
+        } else {
+            nebfile = nebFind(server, cleanFilename);
+            if (!nebfile) {
+                psError(PM_ERR_SYS, true, "failed to find nebulous key: %s", nebErr(server));
+                nebServerFree(server);
+                return NULL;
+            }
+        }
+
+        // convert nebfile into a psString
+        psString path = psStringCopy(nebfile);
+        nebFree(nebfile);
+        nebServerFree(server);
+
+        return path;
+
+        #else // ifdef HAVE_NEBCLIENT
+
+        psError(PM_ERR_PROG, true, "psModules was compiled without nebulous support.");
+        return NULL;
+        #endif // ifdef HAVE_NEBCLIENT
+
+    }
+
+    // if we go this far, do nothing
+    return psStringCopy(filename);
+}
+
+psMetadata *pmConfigFileRule(const pmConfig *config, const psMetadata *camera, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_METADATA_NON_NULL(camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    psMetadataItem *item = psMetadataLookup(camera, "FILERULES"); // Item with the file rule of interest
+    if (!item) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find FILERULES in the camera configuration.");
+        return NULL;
+    }
+
+    if (!metadataItemReadFile(item, "file rules ")) {
+        psError(PM_ERR_CONFIG, false, "Unable to read file rules for camera.");
+        return NULL;
+    }
+
+    assert(item->type == PS_DATA_METADATA);
+    psMetadata *filerules = item->data.md; // File rules from the camera configuration
+
+    // select the name from the FILERULES
+    // check for alias name (type == STR, name is aliased name)
+    bool mdok;                          // Status of MD lookup
+    const char *realname = psMetadataLookupStr(&mdok, filerules, name); // Name of file rule to look up
+    if (!realname || strlen(realname) == 0) {
+        realname = name;
+    }
+
+    return psMetadataLookupMetadata(&mdok, filerules, realname);
+}
+
Index: /branches/eam_branch_20080324/psModules/src/detrend/pmDark.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/detrend/pmDark.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/detrend/pmDark.c	(revision 17278)
@@ -0,0 +1,759 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "psPolynomialMD.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmReadoutStack.h"
+
+#include "pmDark.h"
+
+#define PM_DARK_FITS_EXTNAME "PS_DARK"  // FITS extension name for ordinates table
+#define PM_DARK_FITS_NAME    "NAME"     // Column name for concept name in ordinates table
+#define PM_DARK_FITS_ORDER   "ORDER"    // Column name for polynomial order in ordinates table
+#define PM_DARK_FITS_SCALE   "SCALE"    // Column name for scaling option in ordinates table
+#define PM_DARK_FITS_MIN     "MIN"      // Column name for minimum value in ordinates table
+#define PM_DARK_FITS_MAX     "MAX"      // Column name for maximum value in ordinates table
+
+
+
+// Look up the value of an ordinate in a readout
+static bool ordinateLookup(float *value, // Value of ordinate, to return
+                           const char *name, // Name of ordinate (concept name)
+                           bool scale,  // Scale the value?
+                           float min, float max, // Minimum and maximum values for scaling
+                           const pmReadout *ro // Readout of interest
+                           )
+{
+    assert(value);
+    assert(name);
+    assert(ro);
+
+    pmCell *cell = ro->parent; // Parent cell
+    if (!cell) {
+        return false;
+    }
+    psMetadataItem *item = psMetadataLookup(cell->concepts, name);
+    if (!item) {
+        pmChip *chip = cell->parent; // Parent chip
+        if (!chip) {
+            return false;
+        }
+        item = psMetadataLookup(chip->concepts, name);
+        if (!item) {
+            pmFPA *fpa = chip->parent; // Parent FPA
+            if (!fpa) {
+                return false;
+            }
+            item = psMetadataLookup(fpa->concepts, name);
+            if (!item) {
+                psWarning("Unable to find concept %s in readout", name);
+                return false;
+            }
+        }
+    }
+
+    *value = psMetadataItemParseF32(item); // Value of interest
+    if (!isfinite(*value)) {
+        psWarning("Non-finite value (%f) of concept %s in readout", *value, name);
+        return false;
+    }
+    if (scale) {
+        if (*value < min || *value > max) {
+            psWarning("Value of concept %s (%f) outside range (%f:%f)", name, *value, min, max);
+            return false;
+        }
+        *value = 2.0 * (*value - min) / (max - min) - 1.0;
+    }
+
+    return true;
+}
+
+static void darkOrdinateFree(pmDarkOrdinate *ord)
+{
+    psFree(ord->name);
+    return;
+}
+
+pmDarkOrdinate *pmDarkOrdinateAlloc(const char *name, int order)
+{
+    pmDarkOrdinate *ord = psAlloc(sizeof(pmDarkOrdinate)); // Ordinate data, to return
+    psMemSetDeallocator(ord, (psFreeFunc)darkOrdinateFree);
+
+    ord->name = psStringCopy(name);
+    ord->order = order;
+    ord->scale = false;
+    ord->min = NAN;
+    ord->max = NAN;
+
+    return ord;
+}
+
+
+bool pmDarkCombine(pmCell *output, const psArray *inputs, psArray *ordinates, const char *normConcept,
+                   int iter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_ARRAY_NON_NULL(ordinates, false);
+    PS_ASSERT_INT_NONNEGATIVE(iter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+
+    // Extract fitting orders
+    int numOrdinates = ordinates->n;    // Number of ordinates
+    int numInputs = inputs->n;          // Number of inputs
+    int numBadInputs = 0;               // Number of bad inputs
+    psArray *values = psArrayAlloc(numInputs);
+    psVector *roMask = psVectorAlloc(numInputs, PS_TYPE_U8); // Mask for bad readouts
+    psVectorInit(roMask, 0);
+    psVector *norm = normConcept ? psVectorAlloc(numInputs, PS_TYPE_F32) : NULL; // Normalisations for each
+    for (int i = 0; i < numInputs; i++) {
+        values->data[i] = psVectorAlloc(numOrdinates, PS_TYPE_F32);
+        if (norm) {
+            pmReadout *ro = inputs->data[i]; // Readout of interest
+            float normValue;            // Normalisation value
+            if (!ordinateLookup(&normValue, normConcept, false, NAN, NAN, ro)) {
+                psWarning("Unable to find value of %s for readout %d", normConcept, i);
+                roMask->data.U8[i] = 0xff;
+                norm->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            if (normValue == 0.0) {
+                psWarning("Normalisation value (%s) for readout %d is zero", normConcept, i);
+                roMask->data.U8[i] = 0xff;
+                norm->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            norm->data.F32[i] = 1.0 / normValue;
+        }
+    }
+    psVector *orders = psVectorAlloc(numOrdinates, PS_TYPE_U8); // Orders for each concept
+    for (int i = 0; i < numOrdinates; i++) {
+        pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate information
+        if (ord->order <= 0) {
+            psError(PS_ERR_UNKNOWN, true, "Bad order for concept %s (%d) --- ignored", ord->name, ord->order);
+            psFree(values);
+            psFree(roMask);
+            psFree(orders);
+            return false;
+        }
+        orders->data.U8[i] = ord->order;
+
+        // Mask the readout and move on
+        #define MASK_READOUT_VALUE { \
+            roMask->data.U8[j] = 0xff; \
+            val->data.F32[i] = NAN; \
+            numBadInputs++; \
+            continue; \
+        }
+
+        for (int j = 0; j < numInputs; j++) {
+            psVector *val = values->data[j]; // Value vector for readout
+            if (roMask->data.U8[j]) {
+                val->data.F32[i] = NAN;
+                continue;
+            }
+
+            pmReadout *ro = inputs->data[j]; // Readout of interest
+            float value = NAN;          // Value of ordinate
+            if (!ordinateLookup(&value, ord->name, ord->scale, ord->min, ord->max, ro)) {
+                roMask->data.U8[j] = 0xff;
+                val->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            val->data.F32[i] = value;
+        }
+    }
+
+    if (psTraceGetLevel("psModules.detrend") > 9) {
+        for (int i = 0; i < numInputs; i++) {
+            psVector *val = values->data[i];
+            (void) val; // avoid unused variable message when tracing is compiled out
+            for (int j = 0; j < numOrdinates; j++) {
+                psTrace("psModules.detrend", 9, "Image %d, ordinate %d: %f\n", i, j, val->data.F32[j]);
+            }
+        }
+    }
+
+    psPolynomialMD *poly = psPolynomialMDAlloc(orders); // Polynomial for fitting
+    psFree(orders);
+    int numTerms = poly->coeff->n;      // Number of terms in polynomial
+    if (numTerms > numInputs - numBadInputs) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Insufficient inputs (%d) to fit polynomial terms (%d).",
+                numInputs - numBadInputs, numTerms);
+        psFree(values);
+        psFree(roMask);
+        psFree(norm);
+        return false;
+    }
+
+    // Set up output
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                inputs)) {
+        psError(PS_ERR_UNKNOWN, false, "No valid input readouts.");
+        psFree(values);
+        psFree(roMask);
+        psFree(norm);
+        return false;
+    }
+    if (output->readouts->n != numTerms) {
+        output->readouts = psArrayRealloc(output->readouts, numTerms);
+    }
+    int outRow0 = 0, outCol0 = 0;       // Output row0 and col0
+    for (int i = 0; i < numTerms; i++) {
+        pmReadout *ro = output->readouts->data[i]; // Readout to update
+        if (!ro) {
+            ro = output->readouts->data[i] = pmReadoutAlloc(output);
+        }
+
+        pmReadoutUpdateSize(ro, minInputCols, minInputRows, xSize, ySize, false, false, 0);
+        if (i == 0) {
+            outRow0 = ro->row0;
+            outCol0 = ro->col0;
+        } else {
+            assert(ro->row0 == outRow0);
+            assert(ro->col0 == outCol0);
+        }
+    }
+
+    psImage *counts = pmReadoutAnalysisImage(output->readouts->data[0], PM_READOUT_STACK_ANALYSIS_COUNT,
+                                             xSize, ySize, PS_TYPE_U16, 0);
+    if (!counts) {
+        return false;
+    }
+    psImage *sigma = pmReadoutAnalysisImage(output->readouts->data[0], PM_READOUT_STACK_ANALYSIS_SIGMA,
+                                            xSize, ySize, PS_TYPE_F32, NAN);
+    if (!sigma) {
+        psFree(counts);
+        return false;
+    }
+
+    // Iterate over pixels, fitting polynomial
+    psVector *pixels = psVectorAlloc(numInputs, PS_TYPE_F32); // Stack of pixels
+    psVector *mask   = psVectorAlloc(numInputs, PS_TYPE_MASK); // Mask for stack
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - outRow0; // y position on output readout
+#ifdef SHOW_BUSY
+        if (psTraceGetLevel("psModules.detrend") > 9) {
+            printf("Processing row %d\r", i);
+            fflush(stdout);
+        }
+#endif
+
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - outCol0; // x position on output readout
+
+            psVectorInit(mask, 0);
+            for (int r = 0; r < inputs->n; r++) {
+                if (roMask->data.U8[r]) {
+                    mask->data.PS_TYPE_MASK_DATA[r] = 0xff;
+                    continue;
+                }
+                pmReadout *readout = inputs->data[r]; // Input readout
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+
+                pixels->data.F32[r] = readout->image->data.F32[yIn][xIn];
+                if (norm) {
+                    pixels->data.F32[r] *= norm->data.F32[r];
+                }
+                if (readout->mask) {
+                    mask->data.PS_TYPE_MASK_DATA[r] = readout->mask->data.PS_TYPE_MASK_DATA[yIn][xIn];
+                }
+            }
+
+            if (!psPolynomialMDClipFit(poly, pixels, NULL, mask, maskVal, values, iter, rej)) {
+                psErrorClear();         // Nothing we can do about it
+                psVectorInit(poly->coeff, NAN);
+            }
+            for (int k = 0; k < numTerms; k++) {
+                pmReadout *ro = output->readouts->data[k]; // Readout of interest
+                ro->image->data.F32[yOut][xOut] = poly->coeff->data.F64[k];
+            }
+            counts->data.U16[yOut][xOut] = poly->numFit;
+            sigma->data.F32[yOut][xOut] = poly->stdevFit;
+        }
+    }
+
+    psFree(norm);
+    psFree(roMask);
+    psFree(poly);
+    psFree(pixels);
+    psFree(mask);
+    psFree(values);
+
+    psMetadataAddPtr(output->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                     PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+    psMetadataAddStr(output->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM,
+                     PS_META_REPLACE, "Dark normalisation", normConcept);
+
+    for (int i = 0; i < numTerms; i++) {
+        pmReadout *ro = output->readouts->data[i]; // Readout to update
+        ro->data_exists = true;
+    }
+    output->data_exists = true;
+    output->parent->data_exists = true;
+
+    return true;
+}
+
+
+
+bool pmDarkApply(pmReadout *readout, const pmCell *dark, psMaskType bad)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(dark, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    int numCols = readout->image->numCols, numRows = readout->image->numRows; // Size of image
+    if (readout->mask) {
+        PS_ASSERT_IMAGE_NON_NULL(readout->mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(readout->mask, readout->image, false);
+        PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, false);
+    }
+    int numTerms = dark->readouts->n;   // Number of polynomial terms
+    for (int i = 0; i < numTerms; i++) {
+        pmReadout *ro = dark->readouts->data[i]; // Dark readout
+        PS_ASSERT_PTR_NON_NULL(ro, false);
+        PS_ASSERT_IMAGE_NON_NULL(ro->image, false);
+        PS_ASSERT_IMAGE_SIZE(ro->image, numCols, numRows, false);
+        PS_ASSERT_IMAGE_TYPE(ro->image, PS_TYPE_F32, false);
+    }
+    psArray *ordinates = psMetadataLookupPtr(NULL, dark->analysis, PM_DARK_ANALYSIS_ORDINATES); // Ordinates
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find dark ordinates.");
+        return false;
+    }
+    bool mdok;                          // Status of MD lookup
+    psString normConcept = psMetadataLookupStr(&mdok, dark->analysis, PM_DARK_ANALYSIS_NORM); // Normalisation
+
+    int numOrdinates = ordinates->n;    // Number of ordinates
+    psVector *values = psVectorAlloc(numOrdinates, PS_TYPE_F32); // Values of ordinates
+    for (int i = 0; i < numOrdinates; i++) {
+        pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate of interest
+        float value = NAN;              // Value for ordinate
+        if (!ordinateLookup(&value, ord->name, ord->scale, ord->min, ord->max, readout)) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to find value for %s", ord->name);
+            psFree(values);
+            return false;
+        }
+        values->data.F32[i] = value;
+    }
+    float norm = NAN;                   // Normalisation value
+    bool doNorm = false;                // Do normalisation?
+    if (normConcept && strlen(normConcept) > 0) {
+        if (!ordinateLookup(&norm, normConcept, false, NAN, NAN, readout)) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to find value for %s", normConcept);
+            psFree(values);
+            return false;
+        }
+        doNorm = true;
+    }
+
+    psVector *orders = psVectorAlloc(numOrdinates, PS_TYPE_U8); // Order for each polynomial
+    for (int i = 0; i < numOrdinates; i++) {
+        pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate information
+        if (ord->order <= 0) {
+            psError(PS_ERR_UNKNOWN, true, "Bad order for concept %s (%d) --- ignored", ord->name, ord->order);
+            psFree(values);
+            psFree(orders);
+            return false;
+        }
+        orders->data.U8[i] = ord->order;
+    }
+
+    psPolynomialMD *poly = psPolynomialMDAlloc(orders); // Polynomial to apply
+    psFree(orders);
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            for (int i = 0; i < numTerms; i++) {
+                pmReadout *ro = dark->readouts->data[i]; // Dark readout
+                poly->coeff->data.F64[i] = ro->image->data.F32[y][x];
+            }
+            float value = psPolynomialMDEval(poly, values); // Value of dark current
+            if (doNorm) {
+                value *= norm;
+            }
+            readout->image->data.F32[y][x] -= value;
+            if (readout->mask && !isfinite(value)) {
+                readout->mask->data.PS_TYPE_MASK_DATA[y][x] = bad;
+            }
+        }
+    }
+
+    psFree(poly);
+    psFree(values);
+
+    return true;
+}
+
+bool pmFPAWriteDark(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmFPAWrite(fpa, fits, db, blank, recurse)) {
+        psError(PS_ERR_IO, false, "Unable to write FPA dark images");
+        return false;
+    }
+
+    pmHDU *hdu = fpa->hdu;
+    if (blank || !hdu) {
+        // No more to do
+        return true;
+    }
+
+    psArray *ordinates = NULL;      // Dark ordinates, to write
+    const char *normConcept = NULL;     // Normalisation concept
+    psArray *chips = fpa->chips;    // Component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i]; // Chip of interest
+        psArray *cells = chip->cells; // Component cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            bool mdok;              // Status of MD lookup
+            psArray *newOrd = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_ORDINATES); // Ords
+            if (!mdok) {
+                continue;
+            }
+            psString newNorm = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_NORM); // Norm
+            if (ordinates) {
+                if (newOrd != ordinates) {
+                    psError(PS_ERR_UNKNOWN, true, "Dark ordinates differ across cells.");
+                    return false;
+                }
+                if ((normConcept && (!newNorm || strcmp(normConcept, newNorm) != 0)) ||
+                    (!normConcept && newNorm)) {
+                    psError(PS_ERR_UNKNOWN, true, "Dark normalisations differ across cells.");
+                    return false;
+                }
+            } else {
+                ordinates = newOrd;
+                normConcept = newNorm;
+            }
+        }
+    }
+
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find dark ordinates.");
+        return false;
+    }
+
+    return pmDarkWrite(fits, hdu->header, ordinates, normConcept);
+}
+
+bool pmChipWriteDark(pmChip *chip, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmChipWrite(chip, fits, db, blank, recurse)) {
+        psError(PS_ERR_IO, false, "Unable to write chip dark images");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromChip(chip);
+    if (blank || !hdu) {
+        // No more to do
+        return true;
+    }
+
+    psArray *ordinates = NULL;          // Dark ordinates, to write
+    const char *normConcept = NULL;     // Normalisation concept
+    psArray *cells = chip->cells;       // Component cells
+    for (int j = 0; j < cells->n; j++) {
+        pmCell *cell = cells->data[j]; // Cell of interest
+        bool mdok;                      // Status of MD lookup
+        psArray *newOrd = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_ORDINATES); // Ordinates
+        if (!mdok) {
+            continue;
+        }
+        psString newNorm = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_NORM); // Normalisation
+        if (ordinates) {
+            if (newOrd != ordinates) {
+                psError(PS_ERR_UNKNOWN, true, "Dark ordinates differ across cells.");
+                return false;
+            }
+            if ((normConcept && (!newNorm || strcmp(normConcept, newNorm) != 0)) ||
+                (!normConcept && newNorm)) {
+                psError(PS_ERR_UNKNOWN, true, "Dark normalisations differ across cells.");
+                return false;
+            }
+        } else {
+            ordinates = newOrd;
+            normConcept = newNorm;
+        }
+    }
+
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find dark ordinates.");
+        return false;
+    }
+
+    return pmDarkWrite(fits, hdu->header, ordinates, normConcept);
+}
+
+bool pmCellWriteDark(pmCell *cell, psFits *fits, psDB *db, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    // Allow the usual pmFPAWrite functions to handle the heavy lifting for the images
+    if (!pmCellWrite(cell, fits, db, blank)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to write dark cell.");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);
+    if (blank || !hdu) {
+        // No more to do
+        return true;
+    }
+
+    psArray *ordinates = psMetadataLookupPtr(NULL, cell->analysis, PM_DARK_ANALYSIS_ORDINATES);
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find dark ordinates.");
+        return false;
+    }
+    bool mdok;                          // Status of MD lookup
+    psString normConcept = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_NORM); // Normalisation
+
+    return pmDarkWrite(fits, hdu->header, ordinates, normConcept);
+}
+
+bool pmDarkWrite(psFits *fits, psMetadata *header, const psArray *ordinates, const char *normConcept)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_ARRAY_NON_NULL(ordinates, false);
+
+    // Only write table once per FITS file
+    bool write = false;             // Write table?
+    if (psFitsGetSize(fits) <= 1) {
+        write = true;
+    } else {
+        psMetadata *headers = psFitsReadHeaderSet(NULL, fits); // FITS headers
+        if (!psMetadataLookup(headers, PM_DARK_FITS_EXTNAME)) {
+            write = true;
+        }
+        psFree(headers);
+    }
+    if (!write) {
+        return true;
+    }
+
+    if (!psMemIncrRefCounter((psMetadata*)header)) {
+        header = psMetadataAlloc();
+    }
+    psMetadataAddStr(header, PS_LIST_TAIL, PM_DARK_HEADER_NORM, PS_META_REPLACE,
+                     "Dark normalisation concept", normConcept);
+
+    if (ordinates->n > 0) {
+        // Format ordinates into FITS table
+        int numOrdinates = ordinates->n;// Number of ordinates
+        psArray *table = psArrayAlloc(numOrdinates); // FITS table, constructed from ordinates
+        for (int i = 0; i < ordinates->n; i++) {
+            pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate of interest
+            psMetadata *row = psMetadataAlloc(); // FITS table row
+            psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_NAME, 0, "Concept name", ord->name);
+            psMetadataAddS32(row, PS_LIST_TAIL, PM_DARK_FITS_ORDER, 0, "Polynomial order", ord->order);
+            psMetadataAddBool(row, PS_LIST_TAIL, PM_DARK_FITS_SCALE, 0, "Scale values?", ord->scale);
+            psMetadataAddF32(row, PS_LIST_TAIL, PM_DARK_FITS_MIN, 0, "Minimum value", ord->min);
+            psMetadataAddF32(row, PS_LIST_TAIL, PM_DARK_FITS_MAX, 0, "Maximum value", ord->max);
+            table->data[i] = row;
+        }
+
+        if (!psFitsWriteTable(fits, header, table, PM_DARK_FITS_EXTNAME)) {
+            psError(PS_ERR_IO, false, "Unable to write dark ordinates.");
+            psFree(table);
+            psFree(header);
+            return false;
+        }
+        psFree(table);
+    } else {
+        // No ordinates to write to a table, so write to a blank header.
+        if (!psFitsWriteBlank(fits, header, PM_DARK_FITS_EXTNAME)) {
+            psError(PS_ERR_IO, false, "Unable to write dark header.");
+            psFree(header);
+            return false;
+        }
+    }
+    psFree(header);
+
+    return true;
+}
+
+
+bool pmFPAReadDark(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmFPARead(fpa, fits, db)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read dark FPA.");
+        return false;
+    }
+
+    psString normConcept = NULL;        // Normalisation concept
+    psArray *ordinates = pmDarkRead(&normConcept, fits); // Dark ordinates
+    if (!ordinates) {
+        psError(PS_ERR_IO, false, "Unable to read dark ordinates.");
+        return false;
+    }
+
+    psArray *chips = fpa->chips;        // Component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        psArray *cells = chip->cells;   // Component cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                             PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+            psMetadataAddStr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM, PS_META_REPLACE,
+                             "Dark normalisation", normConcept);
+        }
+    }
+    psFree(ordinates);
+    psFree(normConcept);
+
+    return true;
+}
+
+bool pmChipReadDark(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmChipRead(chip, fits, db)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read dark chip.");
+        return false;
+    }
+
+    psString normConcept = NULL;        // Normalisation concept
+    psArray *ordinates = pmDarkRead(&normConcept, fits); // Dark ordinates
+    if (!ordinates) {
+        psError(PS_ERR_IO, false, "Unable to read dark ordinates.");
+        return false;
+    }
+
+    psArray *cells = chip->cells;       // Component cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                         PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+        psMetadataAddStr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM, PS_META_REPLACE,
+                         "Dark normalisation", normConcept);
+    }
+    psFree(ordinates);
+    psFree(normConcept);
+
+    return true;
+}
+
+
+bool pmCellReadDark(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    // Allow the usual pmFPARead functions to handle the heavy lifting for the images
+    if (!pmCellRead(cell, fits, db)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read dark cell.");
+        return false;
+    }
+
+    psString normConcept = NULL;        // Normalisation concept
+    psArray *ordinates = pmDarkRead(&normConcept, fits); // Dark ordinates
+    if (!ordinates) {
+        psError(PS_ERR_IO, false, "Unable to read dark ordinates.");
+        return false;
+    }
+    psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                     PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+    psMetadataAddStr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM, PS_META_REPLACE,
+                     "Dark normalisation", normConcept);
+    psFree(ordinates);
+    psFree(normConcept);
+
+    return true;
+}
+
+psArray *pmDarkRead(psString *normConcept, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(normConcept, NULL);
+    PS_ASSERT_PTR_NULL(*normConcept, NULL);
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    if (!psFitsMoveExtName(fits, PM_DARK_FITS_EXTNAME)) {
+        psError(PS_ERR_IO, false, "Unable to find extension with dark ordinates table (%s).",
+                PM_DARK_FITS_EXTNAME);
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Header
+    bool mdok;                          // Status of MD lookup
+    *normConcept = psMemIncrRefCounter(psMetadataLookupStr(&mdok, header, PM_DARK_HEADER_NORM));
+
+    psArray *ordinates = NULL;          // Dark ordinates to return
+
+    psFitsType type = psFitsGetExtType(fits); // Type of FITS extension
+    switch (type) {
+      case PS_FITS_TYPE_IMAGE: {
+          // Check that it's of zero size; otherwise we might have some conflict
+          int numCols = psMetadataLookupS32(&mdok, header, "NAXIS1");
+          int numRows = psMetadataLookupS32(&mdok, header, "NAXIS2");
+          if (numCols != 0 || numRows != 0) {
+              psError(PS_ERR_UNKNOWN, true, "Extension %s is not a DARK table.", PM_DARK_FITS_EXTNAME);
+              psFree(header);
+              return NULL;
+          }
+          // No ordinates fit --- only a constant term
+          ordinates = psArrayAlloc(0);
+          break;
+      }
+      case PS_FITS_TYPE_BINARY_TABLE:
+      case PS_FITS_TYPE_ASCII_TABLE: {
+          psArray *table = psFitsReadTable(fits); // FITS Table with ordinates
+          int numOrdinates = table->n;        // Number of ordinates
+          ordinates = psArrayAlloc(numOrdinates);
+
+          for (int i = 0; i < numOrdinates; i++) {
+              psMetadata *row = table->data[i]; // Row of interest
+              const char *name = psMetadataLookupStr(NULL, row, PM_DARK_FITS_NAME); // Concept name
+              int order = psMetadataLookupS32(NULL, row, PM_DARK_FITS_ORDER); // Polynomial order
+              if (!name || order <= 0) {
+                  psError(PS_ERR_UNKNOWN, false, "Bad value reading dark ordinates table.");
+                  psFree(table);
+                  psFree(ordinates);
+                  return false;
+              }
+              pmDarkOrdinate *ord = pmDarkOrdinateAlloc(name, order); // Ordinate data
+              ord->scale = psMetadataLookupBool(NULL, row, PM_DARK_FITS_SCALE);
+              ord->min = psMetadataLookupF32(NULL, row, PM_DARK_FITS_MIN);
+              ord->max = psMetadataLookupF32(NULL, row, PM_DARK_FITS_MAX);
+
+              ordinates->data[i] = ord;
+          }
+          psFree(table);
+          break;
+      }
+      default:
+        psError(PS_ERR_UNKNOWN, true, "Unrecognised FITS extension type.");
+        return NULL;
+    }
+    psFree(header);
+
+    return ordinates;
+}
+
Index: /branches/eam_branch_20080324/psModules/src/detrend/pmMaskBadPixels.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/detrend/pmMaskBadPixels.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/detrend/pmMaskBadPixels.c	(revision 17278)
@@ -0,0 +1,269 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPAMaskWeight.h"
+#include "pmMaskBadPixels.h"
+
+bool pmMaskBadPixels(pmReadout *input, const pmReadout *mask, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(input, false);
+    PS_ASSERT_PTR_NON_NULL(input->mask, false);
+    PS_ASSERT_IMAGE_TYPE(input->mask, PS_TYPE_MASK, false);
+
+    PS_ASSERT_PTR_NON_NULL(mask, false);
+    PS_ASSERT_PTR_NON_NULL(mask->mask, false);
+    PS_ASSERT_IMAGE_TYPE(mask->mask, PS_TYPE_MASK, false);
+
+    psImage *inMask = input->mask;
+    psImage *exMask = mask->mask;
+
+    // Add mask MD5 to header
+    pmHDU *hdu = pmHDUFromReadout(input);  // HDU of interest
+    psVector *md5 = psImageMD5(mask->mask); // md5 hash
+    psString md5string = psMD5toString(md5); // String
+    psFree(md5);
+    psStringPrepend(&md5string, "MASK image MD5: ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     md5string, "");
+    psFree(md5string);
+
+    int rowMax = input->row0 + inMask->numRows;
+    int colMax = input->col0 + inMask->numCols;
+
+    if (mask->row0 > input->row0 || mask->col0 > input->col0 ||
+            mask->row0 + exMask->numRows < rowMax || mask->col0 + exMask->numCols < colMax) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Input image size exceeds that of mask image: (%d, %d) vs (%d, %d)",
+                inMask->numRows, inMask->numCols, exMask->numRows, exMask->numCols);
+        return false;
+    }
+
+    // Determine total offset based on image offset with chip offset
+    // XXX if we choose to correct for the readout location, apply input->col0,row0 here
+    int offCol = input->col0 - mask->col0;
+    int offRow = input->row0 - mask->row0;
+
+    // masks are both of type PS_TYPE_MASK
+    psMaskType **exVal = exMask->data.U8;
+    psMaskType **inVal = inMask->data.U8;
+
+    // apply exMask values
+    if (maskVal) {
+        // set raised pixels in exMask which are selected by maskVal
+        for (int j = 0; j < inMask->numRows; j++) {
+            int xJ = j - offRow;
+            for (int i = 0; i < inMask->numCols; i++) {
+                int xI = i - offCol;
+                inVal[j][i] |= (maskVal & exVal[xJ][xI]);
+            }
+        }
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Static mask (selecting %x) applied at ", maskVal);
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+    return true;
+}
+
+
+bool pmMaskFlagSuspectPixels(pmReadout *output, const pmReadout *readout, float median, float stdev,
+                             float rej, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    if (readout->mask) {
+        PS_ASSERT_IMAGE_NON_EMPTY(readout->mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(readout->image, readout->mask, false);
+        PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, false);
+    }
+    PS_ASSERT_PTR_NON_NULL(output, false);
+
+    bool mdok;                          // Status of MD lookup
+    psImage *suspect = psMetadataLookupPtr(&mdok, output->analysis, PM_MASK_ANALYSIS_SUSPECT); // Suspect img
+    if (suspect) {
+        PS_ASSERT_IMAGE_NON_EMPTY(suspect, false);
+        PS_ASSERT_IMAGE_TYPE(suspect, PS_TYPE_S32, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(readout->image, suspect, false);
+        psMemIncrRefCounter(suspect);
+    } else {
+        suspect = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_S32);
+        psImageInit(suspect, 0);
+        psMetadataAddImage(output->analysis, PS_LIST_TAIL, PM_MASK_ANALYSIS_SUSPECT, PS_META_REPLACE,
+                           "Suspect pixels", suspect);
+        psMetadataAddS32(output->analysis, PS_LIST_TAIL, PM_MASK_ANALYSIS_NUM, PS_META_REPLACE,
+                         "Number of input images", 0);
+    }
+
+    if (!isfinite(median) || !isfinite(stdev)) {
+        // If we get down here and the statistics are missing, then we should go and mask the entire image
+        psWarning("Missing statistics --- flagging entire image as suspect.");
+        return (psImage*)psBinaryOp(suspect, suspect, "+", psScalarAlloc(1.0, PS_TYPE_S32));
+    }
+
+    psImage *image = readout->image;    // Image of interest
+    psImage *mask = readout->mask;      // Corresponding mask
+
+    psTrace ("psModules.detrend", 3, "suspect: %f +/- %f\n", median, stdev);
+
+    for (int y = 0; y < image->numRows; y++) {
+        for (int x = 0; x < image->numCols; x++) {
+            if (fabs((image->data.F32[y][x] - median) / stdev) >= rej &&
+                    (!mask || !(mask->data.PS_TYPE_MASK_DATA[y][x] & maskVal))) {
+                suspect->data.S32[y][x]++;
+            }
+        }
+    }
+    psFree(suspect);                    // Drop reference
+
+    psMetadataItem *numItem = psMetadataLookup(output->analysis, PM_MASK_ANALYSIS_NUM); // Item with number
+    assert(numItem);
+    numItem->data.S32++;
+
+    return true;
+}
+
+bool pmMaskIdentifyBadPixels(pmReadout *output, psMaskType maskVal, float thresh, pmMaskIdentifyMode mode)
+{
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    psImage *suspects = psMetadataLookupPtr(NULL, output->analysis, PM_MASK_ANALYSIS_SUSPECT); // Suspect img
+    if (!suspects) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find image with suspected bad pixels.");
+        return false;
+    }
+    PS_ASSERT_IMAGE_NON_EMPTY(suspects, false);
+    PS_ASSERT_IMAGE_TYPE(suspects, PS_TYPE_S32, false);
+    if (output->mask) {
+        PS_ASSERT_IMAGE_NON_EMPTY(output->mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(output->mask, suspects, false);
+        PS_ASSERT_IMAGE_TYPE(output->mask, PS_TYPE_MASK, false);
+    } else {
+        output->mask = psImageAlloc(suspects->numCols, suspects->numRows, PS_TYPE_MASK);
+    }
+    int num = psMetadataLookupS32(NULL, output->analysis, PM_MASK_ANALYSIS_NUM); // Number of inputs
+    PS_ASSERT_INT_POSITIVE(num, false);
+
+    float limit = NAN;                  // Limit for masking
+    switch (mode) {
+      case PM_MASK_ID_VALUE:
+        limit = thresh;
+        break;
+
+      case PM_MASK_ID_FRACTION:
+        limit = thresh * num;
+        break;
+
+      case PM_MASK_ID_SIGMA: {
+        psStats *stats = psStatsAlloc(PS_STAT_CLIPPED_STDEV); // Statistics
+        stats->clipSigma = 5.0;
+        stats->clipIter = 1;
+        if (!psImageStats(stats, suspects, NULL, 0) || !isfinite(stats->clippedStdev)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to perform statistics.\n");
+            psFree(stats);
+            return NULL;
+        }
+        limit = thresh * stats->clippedStdev;
+        psTrace ("psModules.detrend", 3, "bad: %f -> %f\n", stats->clippedStdev, limit);
+        psFree(stats);
+        break;
+      }
+
+      case PM_MASK_ID_POISSON: {
+        psStats *stats = psStatsAlloc(PS_STAT_MAX); // Statistics
+        if (!psImageStats(stats, suspects, NULL, 0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to perform statistics.\n");
+            psFree(stats);
+            return NULL;
+        }
+        psHistogram *histo = psHistogramAlloc(-0.5, stats->max + 0.5, stats->max + 1);
+        psFree(stats);
+        if (!psImageHistogram(histo, suspects, NULL, 0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate histogram.\n");
+            psFree(histo);
+            return NULL;
+        }
+
+        // Find the mode.  Since this is a Poisson distribution (more or less), this should also be the mean
+        // and variance.
+        int max = 0;                    // Index of the mode
+        for (int i = 0; i < histo->nums->n; i++) {
+            if (histo->nums->data.F32[i] > histo->nums->data.F32[max]) {
+                max = i;
+            }
+        }
+
+        // Since the mode is most likely zero, we add one to get something realistic.  Then "thresh" is
+        // negative, so we subtract instead of add.
+        limit = max + 1.0 - thresh * sqrtf((float)max + 1.0);
+
+        psTrace ("psModules.detrend", 3, "bad: mode: %d, stdev: %f, limit: %f\n",
+                 max, sqrtf((float)max + 1.0), limit);
+        break;
+      }
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Invalid mask identify mode");
+        return NULL;
+    }
+
+    if (psTraceGetLevel("psModules.detrend") > 9) {
+        psStats *stats = psStatsAlloc(PS_STAT_MIN | PS_STAT_MAX); // Statistics
+        psImageStats(stats, suspects, NULL, 0);
+        psHistogram *histo = psHistogramAlloc(-0.5, stats->max + 0.5, stats->max + 1);
+        psImageHistogram(histo, suspects, NULL, 0);
+        for (int i = 0; i < histo->nums->n; i++) {
+            printf("%f --> %f : %f\n", histo->bounds->data.F32[i], histo->bounds->data.F32[i + 1],
+                   histo->nums->data.F32[i]);
+        }
+        psFree(stats);
+        psFree(histo);
+        printf("Threshold: %f\n", limit);
+    }
+
+    psTrace ("psModules.detrend", 3, "bad pixel threshold: %f", limit);
+
+    psImage *badpix = output->mask;     // Bad pixel mask
+    psImageInit(badpix, 0);
+
+    for (int y = 0; y < suspects->numRows; y++) {
+        for (int x = 0; x < suspects->numCols; x++) {
+            if (suspects->data.S32[y][x] >= limit) {
+                badpix->data.PS_TYPE_MASK_DATA[y][x] = maskVal;
+            }
+        }
+    }
+
+    return true;
+}
+
+pmMaskIdentifyMode pmMaskIdentifyModeFromString (const char *string) {
+
+    if (!strcasecmp(string, "VALUE")) {
+      return PM_MASK_ID_VALUE;
+    }
+    if (!strcasecmp(string, "FRACTION")) {
+      return PM_MASK_ID_FRACTION;
+    }
+    if (!strcasecmp(string, "SIGMA")) {
+      return PM_MASK_ID_SIGMA;
+    }
+    if (!strcasecmp(string, "POISSON")) {
+      return PM_MASK_ID_POISSON;
+    }
+    return PM_MASK_ID_NONE;
+}
Index: /branches/eam_branch_20080324/psModules/src/detrend/pmMaskBadPixels.h
===================================================================
--- /branches/eam_branch_20080324/psModules/src/detrend/pmMaskBadPixels.h	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/detrend/pmMaskBadPixels.h	(revision 17278)
@@ -0,0 +1,71 @@
+/* @file pmMaskBadPixels.h
+ * @brief Mask bad pixels
+ *
+ * @author Ross Harman, MHPCC
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.16 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-29 03:10:17 $
+ * Copyright 2004 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_MASK_BAD_PIXELS_H
+#define PM_MASK_BAD_PIXELS_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+#define PM_MASK_ANALYSIS_SUSPECT "MASK.SUSPECT" // Readout analysis metadata keyword for suspect image
+#define PM_MASK_ANALYSIS_NUM "MASK.NUM" // Readout analysis metadata keyword for number of inputs
+
+
+typedef enum {
+  PM_MASK_ID_NONE,
+  PM_MASK_ID_VALUE,
+  PM_MASK_ID_FRACTION,
+  PM_MASK_ID_SIGMA,
+  PM_MASK_ID_POISSON,
+} pmMaskIdentifyMode;
+
+pmMaskIdentifyMode pmMaskIdentifyModeFromString (const char *string);
+
+/// Applies the bad pixel mask to the input
+///
+/// Pixels marked as bad within the mask are marked as bad within the input image's mask.  If maskVal is
+/// non-zero, all pixels in the mask have any of the same bits sets as maskVal shall have the corresponding
+/// bits raised.  If maskVal is zero, any zero pixels in the mask are OR-ed with PM_MASK_BAD.  Position
+/// offsets (such as due to trimming) between the input and mask are applied so that the same pixels are
+/// referred to.  The science readout must already have a supplied mask element (use eg. pmReadoutSetMask).
+/// The supplied mask image must be of MASK type
+bool pmMaskBadPixels(pmReadout *input,  ///< Input science image
+                     const pmReadout *mask, ///< Mask image to apply
+                     psMaskType maskVal ///< Mask value to apply
+                    );
+
+/// Find pixels outlying from the background, flagging suspect pixels
+///
+/// Pixels more than "rej" standard deviations from the background level (in flat-fielded,
+/// background-subtracted images) have the corresponding pixel in the "suspect pixels" image
+/// incremented.  After accumulating over a suitable sample of images, bad pixels should have a
+/// high value in the suspect pixels image, allowing them to be identified.  The suspect pixels
+/// image is of type S32.  The relevant median and standard deviation must be supplied in the
+/// readout->analysis metadata as READOUT.MEDIAN, READOUT.STDEVe
+bool pmMaskFlagSuspectPixels(pmReadout *output, ///< Output readout, optionally with suspect pixels image
+                             const pmReadout *readout, ///< Readout to inspect
+                             float median, ///< Image median
+                             float stdev, ///< Image standard deviation
+                             float rej, ///< Rejection threshold (standard deviations)
+                             psMaskType maskVal ///< Mask value for statistics
+    );
+
+/// Identify bad pixels from the suspect pixels image
+///
+/// Bad pixels are identified from the suspect pixels image (accumulated over a large number of images),
+/// according to the chosen mode.
+bool pmMaskIdentifyBadPixels(pmReadout *output, ///< Output readout, with suspect pixels imageOut
+                             psMaskType maskVal, ///< Value to set for bad pixels
+                             float thresh, ///< Threshold for bad pixel
+                             pmMaskIdentifyMode mode ///< Mode for identifying bad pixels
+    );
+/// @}
+#endif
Index: /branches/eam_branch_20080324/psModules/src/detrend/pmShutterCorrection.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/detrend/pmShutterCorrection.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/detrend/pmShutterCorrection.c	(revision 17278)
@@ -0,0 +1,1023 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "psVectorBracket.h"
+#include "pmConceptsAverage.h"
+#include "pmReadoutStack.h"
+
+#include "pmShutterCorrection.h"
+
+/// Measure shutter correction:
+///
+/// input  : collection of shutter correction exposures (pre-processed)
+/// output : a shutter correction image
+///
+/// The measurement could be performed on any focal-plane unit at a time. for GPC, the obvious scale is to
+/// measure the effect on the entire focal plane at once, with a single reference point in the field.  this is
+/// a little more complex than just measuring the effect for a single 2D image array.  the reference point and
+/// the detailed analysis points need to be defined for the entire hierarchy rather than just as coordinate
+/// pairs or regions.  a pmFPAview would be a natural element with which to define these points, but at the
+/// moment, the pmFPAview structure defines a band in the CCD, not a coordinate.  An option is to instead
+/// specify the reference locations as a pmFPAview coupled with a psRegion, though we need to be careful not
+/// to over-specify the pixels (ie, conflict between pmFPAview and psRegion).
+///
+/// At each point in an image with exposure time T, we measure f(k;T) = F(k;T) / F(0;T) where k is the
+/// coordinate of the point of interest, 0 is the reference coordinate, and F(k;T) is the measured number of
+/// counts at the point of interest in this image.  given a collection of f(k;T) values, we need to determine
+/// the model f(k;T) = A(k) (T + dTk) / (T + dTo) where dTk is the shutter error at the given position, dTo is
+/// the shutter error at the reference position, and A(k) is the scaling factor for the given position.
+///
+/// The process for generating a shutter correction is as follows:
+/// - for each image
+/// -- measure the reference point counts
+/// - for each analysis region:
+/// -- measure shutter parameters (dTo, dTk, A):
+/// --- for each image:
+/// ---- measure counts at the region
+/// ---- divide by the reference counts
+/// --- linear extrapolation to find f(inf) = A(k)
+/// --- linear extrapolation to find f(0) = A(k) dTk / dTo
+/// --- linear interpolation to find coordinate where f(dTo) = A (1 + dTk/dTo) / 2
+/// --- non-linear fit of T, f(T) to f(k;T) = A(k) (T + dTk) / (T + dTo)
+/// - use the collection of dTo values to choose a best value for dTo (median)
+/// - for each image pixel
+/// -- divide by the reference counts
+/// -- generate the vectors T, f(T)
+/// -- linear fit of T, f(T) to f(k;T) = A(k) (T + dTk) / (T + dTo) using dTo above
+/// -- save dTk, A(k) in output image pixels
+/// -- apply dTk, A(k) to measure residual images
+/// -- generate residual FITS/JPEG images
+
+
+#define MEASURE_SAMPLES 4               // Number of samples to make over the image.  This should only be
+                                        // changed with great caution, since assumptions on its value are in
+                                        // the code (see pmShutterCorrectionDataAlloc).
+
+
+static void pmShutterCorrectionFree(pmShutterCorrection *pars)
+{
+    // Nothing to free
+    return;
+}
+
+pmShutterCorrection *pmShutterCorrectionAlloc()
+{
+    pmShutterCorrection *corr = (pmShutterCorrection*)psAlloc(sizeof(pmShutterCorrection));
+    psMemSetDeallocator(corr, (psFreeFunc)pmShutterCorrectionFree);
+
+    corr->scale  = 0.0;
+    corr->offset = 0.0;
+    corr->offref = 0.0;
+    corr->num = 0;
+    corr->stdev = NAN;
+
+    return corr;
+}
+
+pmShutterCorrection *pmShutterCorrectionGuess(const psVector *exptime, const psVector *counts)
+{
+    // NOTE: vectors must be sorted on input.  It is expensive to sort or check this here, but
+    // it is easy to arrange by sorting the images before generating these vectors.
+
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+
+    long N = exptime->n;                // Number of exposures
+
+    // use interpolation to guess shutter correction parameters given a set of exposures times and normalized
+    // counts (divided by the reference counts for each image)
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction, to be returned
+    psPolynomial1D *line = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Straight line, for extrapolation
+
+    // choose the highest exptime point as the guess for the scale:
+    // XXX we could examine the top 2 or 3 values and decide if we
+    // extended exptime enough or median clip.
+    corr->scale = counts->data.F32[N-1];
+
+    // fit a line to the lowest three points and extrapolate to 0.0
+    psVector *tmpX = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *tmpY = psVectorAlloc(2, PS_TYPE_F32);
+
+    long index;
+    for (index = 0; !isfinite(exptime->data.F32[index]) && index < N - 1; index++)
+        ; // Iterate only
+    if (index == N - 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Not enough good values to guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpX->data.F32[0] = exptime->data.F32[index];
+    tmpY->data.F32[0] = counts->data.F32[index];
+
+    for (index++;
+            (!isfinite(exptime->data.F32[index]) || exptime->data.F32[index] == exptime->data.F32[0]) &&
+            index < N; index++)
+        ; // Iterate only
+    if (index == N) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Exposure times are all identical --- cannot guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpY->data.F32[1] = counts->data.F32[index];
+    tmpX->data.F32[1] = exptime->data.F32[index];
+
+    // fit a line and extrapolate the fit to 0.0
+    if (!psVectorFitPolynomial1D(line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the time offset.\n");
+        goto GUESS_ERROR;
+    }
+    float ratio = psPolynomial1DEval(line, 0.0) / corr->scale;
+
+    // XXX we need a sanity check:
+    // if the mean value of the three points is higher than corr->scale,
+    // then the slope should be negative.
+    // if the mean value of the three points is lower than corr->scale,
+    // then the slope should be positive.
+
+    // find two points bracketing the value counts = A (1 + dTk/dTo) / 2 = corr->scale (1 + ratio) / 2
+    float value = corr->scale * (1 + ratio) / 2.0;
+
+    int Np;                             // Index of the value above (positive side)
+    if (ratio < 1.0) {
+        Np = psVectorBracket(counts, value, true);
+    } else {
+        Np = psVectorBracketDescend(counts, value, true);
+    }
+    int Nm = (Np == 0) ? 1 : Np - 1;    // Index of the value below (negative side)
+
+    tmpX->data.F32[0] = counts->data.F32[Nm];
+    tmpX->data.F32[1] = counts->data.F32[Np];
+    tmpY->data.F32[0] = exptime->data.F32[Nm];
+    tmpY->data.F32[1] = exptime->data.F32[Np];
+
+    // fit a line and extrapolate the fit to counts = A (1 + dTk/dTo) : exptime = dTo
+    if (!psVectorFitPolynomial1D (line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the reference offset.\n");
+        goto GUESS_ERROR;
+    }
+    corr->offref = psPolynomial1DEval(line, value);
+    corr->offset = ratio * corr->offref;
+
+    psFree(line);
+    psFree(tmpX);
+    psFree(tmpY);
+
+    return corr;
+
+GUESS_ERROR:
+    psFree(tmpX);
+    psFree(tmpY);
+    psFree(line);
+    psFree(corr);
+    return NULL;
+}
+
+// linear fit to the counts and exptime, given a value for offref
+pmShutterCorrection *pmShutterCorrectionLinFit(const psVector *exptime, const psVector *counts,
+                                               const psVector *cntError, const psVector *mask, float offref,
+                                               int nIter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
+    PS_ASSERT_FLOAT_LARGER_THAN(offref, 0.0, NULL);
+
+    // this step is identical for all pixels: do it once and save?
+    psVector *x = psVectorAlloc(exptime->n, PS_TYPE_F32);
+    psVector *y = psVectorAlloc(exptime->n, PS_TYPE_F32);
+
+    for (long i = 0; i < exptime->n; i++) {
+        // Should be safe (if expensive) to stick NaNs in --- the fitter deals with them
+        float value = 1.0 / (exptime->data.F32[i] + offref);
+        x->data.F32[i] = exptime->data.F32[i] * value;
+        y->data.F32[i] = value;
+    }
+
+    psPolynomial2D *line = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
+
+    // mask out the terms we will not fit
+    line->coeffMask[0][0] = PS_POLY_MASK_SET;
+    line->coeffMask[1][1] = PS_POLY_MASK_SET;
+    line->coeff[0][0] = 0;
+    line->coeff[1][1] = 0;
+
+    // the stats structure determines how the clipping statistic is measured
+    // too few points to use the robust analysis method
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->clipSigma = rej;
+    stats->clipIter = nIter;
+
+    if (!psVectorClipFitPolynomial2D(line, stats, mask, maskVal, counts, cntError, x, y)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit shutter correction.\n");
+        psFree(stats);
+        psFree(x);
+        psFree(y);
+        psFree(line);
+        return NULL;
+    }
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc();
+    corr->offref = offref;
+    corr->scale  = line->coeff[1][0];
+    corr->offset = line->coeff[0][1] / line->coeff[1][0];
+    corr->num = stats->clippedNvalues;
+    corr->stdev = stats->clippedStdev;
+
+    psFree(stats);
+    psFree(x);
+    psFree(y);
+    psFree(line);
+
+    return corr;
+}
+
+static psF32 pmShutterCorrectionModel(psVector *deriv, const psVector *params, const psVector *x)
+{
+    // This is in a tight loop, so we won't assert here.
+
+    psF32 A = params->data.F32[0];
+    psF32 p = x->data.F32[0] + params->data.F32[1];
+    psF32 q = 1.0 / (x->data.F32[0] + params->data.F32[2]);
+    psF32 f = A * p * q;
+
+    if (deriv) {
+        deriv->data.F32[0] = p * q;
+        deriv->data.F32[1] = A * q;
+        deriv->data.F32[2] = - f * q;
+    }
+    return f;
+}
+
+// non-linear fit to the counts and exptime, given a guess for the three parameters
+pmShutterCorrection *pmShutterCorrectionFullFit(const psVector *exptime, const psVector *counts,
+                                                const psVector *cntError, const pmShutterCorrection *guess)
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
+    PS_ASSERT_PTR_NON_NULL(guess, NULL);
+
+    psMinimization *minInfo = psMinimizationAlloc(15, 0.1); // Minimization information
+
+    psVector *params = psVectorAlloc (3, PS_TYPE_F32); // Fitting parameters
+    params->data.F32[0] = guess->scale;
+    params->data.F32[1] = guess->offset;
+    params->data.F32[2] = guess->offref;
+
+    // XXX for the moment, don't set any constraints
+    // psMinConstraint *constraint = psMinConstraintAlloc();
+    // constrain->checkLimits = pmShutterParamLimits;
+    // constrain->paramMask   = NULL;
+    psMinConstraint *constraint = NULL;   // Constraints on the minimization
+
+    // XXX ignore covariance matrix for the moment
+    // psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+    psImage *covar = NULL;              // Covariance matrix
+
+    // construct the coordinate and value entries (y is counts)
+    psArray *x = psArrayAlloc(exptime->n); // Coordinates
+
+    for (long i = 0; i < exptime->n; i++) {
+        psVector *coord = psVectorAlloc(1, PS_TYPE_F32);
+        coord->data.F32[0] = exptime->data.F32[i];
+        x->data[i] = coord;
+    }
+
+    if (!psMinimizeLMChi2(minInfo, covar, params, constraint, x, counts, cntError, pmShutterCorrectionModel)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for shutter correction.\n");
+        psFree(x);
+        psFree(minInfo);
+        psFree(params);
+        return NULL;
+    }
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction
+    corr->scale  = params->data.F32[0];
+    corr->offset = params->data.F32[1];
+    corr->offref = params->data.F32[2];
+
+    psFree(minInfo);
+    psFree(params);
+    psFree(x);
+
+    return corr;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmShutterCorrectionMeasure(pmReadout *output, const psArray *readouts, int size, psStatsOptions meanStat,
+                                psStatsOptions stdevStat, int nIter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_ARRAY_NON_NULL(readouts, NULL);
+    PS_ASSERT_ARRAY_NON_EMPTY(readouts, NULL);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+
+    long num = readouts->n;             // Number of readouts
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, NULL);
+
+    psArray *images = psArrayAlloc(num);// Array of images
+    psArray *masks = NULL; // Array of masks
+    psArray *weights = NULL; // Array of weights
+    psVector *exptimes = psVectorAlloc(num, PS_TYPE_F32); // Vector of exposure times
+
+    {
+        pmReadout *readout = readouts->data[0]; // Representative readout
+        if (readout->mask)
+        {
+            masks = psArrayAlloc(num);
+        }
+        if (readout->weight)
+        {
+            weights = psArrayAlloc(num);
+        }
+    }
+
+    // Check input sizes, generate first-pass statistics
+    psRegion refRegion;                 // Reference region
+    psVector *refs = psVectorAlloc(num, PS_TYPE_F32); // Reference measurements
+    psVectorInit(refs, 0);
+    psArray *regions = psArrayAlloc(MEASURE_SAMPLES); // Array of sample regions, made on each image
+    psImage *samplesMean = psImageAlloc(num, MEASURE_SAMPLES, PS_TYPE_F32); // Measurements for each file
+    psImage *samplesStdev = psImageAlloc(num, MEASURE_SAMPLES, PS_TYPE_F32); // Errors for each file
+    psStats *stats = psStatsAlloc(meanStat | stdevStat);
+    int numRows = 0, numCols = 0; // Size of images
+    for (long i = 0; i < images->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout) {
+            continue;
+        }
+
+        bool mdok;                      // Status of MD lookup
+        float exptime = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+        if (!mdok || !isfinite(exptime)) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure time for readout %ld is not set.\n", i);
+            goto MEASURE_ERROR;
+        }
+        exptimes->data.F32[i] = exptime;
+
+        psImage *image = readout->image; // Image of interest
+        if (!image) {
+            continue;
+        }
+        images->data[i] = psMemIncrRefCounter(image);
+        if (image->type.type != PS_TYPE_F32) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for image: %x\n", image->type.type);
+            goto MEASURE_ERROR;
+        }
+        if (numRows == 0 || numCols == 0) {
+            numRows = image->numRows;
+            numCols = image->numCols;
+            // Set up the sample regions
+            refRegion = psRegionForSquare(0.5 * numCols, 0.5 * numRows, size);
+            for (int j = 0; j < MEASURE_SAMPLES; j++) {
+                int x = (j % 2) ? size : image->numCols - size;
+                int y = (j > 1) ? size : image->numRows - size;
+                psRegion region = psRegionForSquare(x, y, size);
+                region = psRegionForImage(image, region);
+                regions->data[j] = psRegionAlloc(region.x0, region.x1, region.y0, region.y1);
+            }
+        } else if (numRows != image->numRows || numCols != image->numCols) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Image sizes don't match: %dx%d vs %dx%d\n", image->numCols, image->numRows,
+                    numCols, numRows);
+            goto MEASURE_ERROR;
+        }
+        psImage *mask = readout->mask; // Mask of interest
+        if (mask) {
+            if (!masks) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have masks.\n");
+                goto MEASURE_ERROR;
+            }
+            masks->data[i] = psMemIncrRefCounter(mask);
+
+            if (mask->type.type != PS_TYPE_U8) {
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for mask: %x\n", mask->type.type);
+                goto MEASURE_ERROR;
+            }
+            if (mask->numRows != numRows || mask->numCols != numCols) {
+                psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                        "Mask sizes don't match: %dx%d vs %dx%d\n", mask->numCols, mask->numRows,
+                        numCols, numRows);
+                goto MEASURE_ERROR;
+            }
+        } else if (masks) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have masks.\n");
+            goto MEASURE_ERROR;
+        }
+
+        psImage *weight = readout->weight; // Weight map of interest
+        if (weight) {
+            if (!weights) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have weights.\n");
+                goto MEASURE_ERROR;
+            }
+            weights->data[i] = psMemIncrRefCounter(weight);
+
+            if (weight->type.type != PS_TYPE_F32) {
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for weights: %x\n", weight->type.type);
+                goto MEASURE_ERROR;
+            }
+            if (weight->numRows != numRows || weight->numCols != numCols) {
+                psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                        "Weight sizes don't match: %dx%d vs %dx%d\n", weight->numCols, weight->numRows,
+                        numCols, numRows);
+                goto MEASURE_ERROR;
+            }
+        } else if (weights) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have weights.\n");
+            goto MEASURE_ERROR;
+        }
+
+
+        // Measure statistics
+        if (!psImageStats(stats, image, mask, maskVal)) {
+            psWarning("Unable to measure reference statistics.\n");
+        }
+        refs->data.F32[i] = psStatsGetValue(stats, meanStat);
+        psTrace("psModules.detrend", 3, "Reference value for image %ld = %f\n", i, refs->data.F32[i]);
+        if (refs->data.F32[i] <= 0.0) {
+            psError(PS_ERR_UNKNOWN, true, "Measured non-positive reference value.\n");
+            goto MEASURE_ERROR;
+        }
+        refs->data.F32[i] = 1.0 / refs->data.F32[i];
+        for (int j = 0; j < MEASURE_SAMPLES; j++) {
+            psRegion *region = regions->data[j]; // Region of interest
+            psImage *subImage = psImageSubset(image, *region); // Sub-image
+            psImage *subMask = NULL;
+            if (mask) {
+                subMask = psImageSubset(mask, *region);
+            }
+            if (!psImageStats(stats, subImage, subMask, maskVal)) {
+                psString regionString = psRegionToString(*region);
+                psWarning("Unable to measure sample statistics at %s in image %ld.\n",
+                          regionString, i);
+                psFree(regionString);
+            }
+            psFree(subImage);
+            samplesMean->data.F32[j][i] = psStatsGetValue(stats, meanStat) * refs->data.F32[i];
+            samplesStdev->data.F32[j][i] = psStatsGetValue(stats, stdevStat) * refs->data.F32[i];
+            psTrace("psModules.detrend", 5, "Image %ld, sample %d: %f +/- %f\n", i, j,
+                    samplesMean->data.F32[j][i], samplesStdev->data.F32[j][i]);
+        }
+    }
+    psFree(regions);
+    psFree(stats);
+
+    float meanRef = 0.0;                // Mean reference offset
+    int numGood = 0;                    // Number of good measurements
+    psVector *counts = psVectorAlloc(num, PS_TYPE_F32); // Mean for each image
+    psVector *errors = psVectorAlloc(num, PS_TYPE_F32); // Stdev for each image
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+        counts = psImageRow(counts, samplesMean, i);
+        errors = psImageRow(errors, samplesStdev, i);
+        pmShutterCorrection *guess = pmShutterCorrectionGuess(exptimes, counts); // Guess at correction
+        pmShutterCorrection *corr = pmShutterCorrectionFullFit(exptimes, counts, errors, guess); // Correct'n
+        if (!corr) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to measure shutter reference correction.\n");
+            psFree(guess);
+            psFree(counts);
+            psFree(errors);
+            goto MEASURE_ERROR;
+        }
+        psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
+        if (isfinite(corr->offref)) {
+            meanRef += corr->offref;
+            numGood++;
+        }
+        psFree(corr);
+        psFree(guess);
+    }
+    psFree(samplesMean);
+    psFree(samplesStdev);
+
+    if (numGood == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to measure mean reference offset.\n");
+        psFree(counts);
+        psFree(errors);
+        goto MEASURE_ERROR;
+    }
+    meanRef /= (float)numGood;
+    psTrace("psModules.detrend", 3, "Mean reference value: %f\n", meanRef);
+
+    // Check the weights
+    if (weights && nIter > 1) {
+        for (int i = 0; i < weights->n && nIter > 1; i++) {
+            psImage *weight = weights->data[i]; // Weight image
+            if (!weight) {
+                // We don't have weights, so no realistic errors: turn off iteration
+                if (nIter > 0) {
+                    psWarning("Not all images have weights --- turning iteration off.\n");
+                }
+                nIter = 1;
+            }
+        }
+    }
+
+    psImage *shutter = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Shutter correction image
+    psImage *pattern = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Illumination pattern
+    psVector *mask = psVectorAlloc(num, PS_TYPE_U8); // Mask for each image
+    psVectorInit(mask, 0);
+    psTrace("psModules.detrend", 2, "Performing linear fit on individual pixels...\n");
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            for (int i = 0; i < num; i++) {
+                psImage *image = images->data[i]; // Image of interest
+                counts->data.F32[i] = image->data.F32[y][x] * refs->data.F32[i];
+                psImage *maskImage;     // Mask image
+                if (masks && (maskImage = masks->data[i])) {
+                    mask->data.U8[i] = maskImage->data.U8[y][x];
+                }
+                psImage *weight;        // Weight image
+                if (weights && (weight = weights->data[i])) {
+                    errors->data.F32[i] = sqrtf(weight->data.F32[y][x]) * refs->data.F32[i];
+                } else {
+                    errors->data.F32[i] = sqrtf(image->data.F32[y][x]) * refs->data.F32[i];
+                }
+            }
+
+            pmShutterCorrection *corr = pmShutterCorrectionLinFit(exptimes, counts, errors, mask, meanRef,
+                                        nIter, rej, maskVal);
+            shutter->data.F32[y][x] = corr->offset;
+            pattern->data.F32[y][x] = corr->scale;
+            psFree(corr);
+        }
+    }
+    psFree(mask);
+    psFree(counts);
+    psFree(errors);
+    psFree(refs);
+
+    if (psTraceGetLevel("psModules.detrend") > 5) {
+        psFits *fits = psFitsOpen("pattern.fits", "w");
+        psFitsWriteImage(fits, NULL, pattern, 0, NULL);
+        psFitsClose(fits);
+    }
+    psFree(pattern);
+
+    output->image = shutter;
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    bool success = pmConceptsAverageCells(output->parent, inputCells, NULL, NULL, true);
+    psFree(inputCells);
+
+    // Correct the exposure times --- they don't make sense any more.
+    psMetadataItem *item = psMetadataLookup(output->parent->concepts, "CELL.EXPOSURE");
+    item->data.F32 = NAN;
+    item = psMetadataLookup(output->parent->concepts, "CELL.DARKTIME");
+    item->data.F32 = NAN;
+
+    return success;
+
+
+MEASURE_ERROR:
+    // Clean up after error
+    psFree(exptimes);
+    psFree(images);
+    psFree(masks);
+    psFree(weights);
+    psFree(refs);
+    psFree(regions);
+    psFree(stats);
+    psFree(samplesMean);
+    psFree(samplesStdev);
+    return false;
+}
+
+
+bool pmShutterCorrectionApply(pmReadout *readout, const pmReadout *shutter, psMaskType blank)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(shutter, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_NON_NULL(shutter->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_TYPE(shutter->image, PS_TYPE_F32, false);
+
+    psRegion region = psRegionSet(readout->col0, readout->col0 + readout->image->numCols,
+                                  readout->row0, readout->row0 + readout->image->numRows); // Detector region
+
+    pmCell *cell = readout->parent;     // Parent cell
+    if (!cell) {
+        psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                "Parent cell is NULL --- unable to determine exposure time.\n");
+        return false;
+    }
+    float exptime = psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE"); // Exposure time
+    if (!isfinite(exptime)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Bad exposure time: %f.\n", exptime);
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);// HDU  of interest
+
+    psVector *md5 = psImageMD5(shutter->image); // md5 hash
+    psString md5string = psMD5toString(md5); // String
+    psFree(md5);
+    psStringPrepend(&md5string, "Shutter image MD5: ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, md5string, "");
+    psFree(md5string);
+
+
+    psImage *shutterImage = psImageSubset(shutter->image, region); // Subimage with shutter
+    if (!shutterImage) {
+        psString regionString = psRegionToString(region);
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Size mismatch: %s vs %dx%d\n",
+                regionString, shutter->image->numCols, shutter->image->numRows);
+        psFree(regionString);
+        psFree(shutterImage);
+        return false;
+    }
+    psImage *image = readout->image;    // Image to correct
+    psImage *mask = readout->mask;      // Corresponding mask
+
+    if (exptime <= 0.0) {
+        // In the extreme case that we have exptime <= 0.0, we correct the image to
+        // counts-per-second, rather than counts in the nominal exposure time
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                if (mask && !isfinite(shutterImage->data.F32[y][x])) {
+                    mask->data.PS_TYPE_MASK_DATA[y][x] |= blank;
+                    image->data.F32[y][x] = NAN;
+                    continue;
+                }
+                image->data.F32[y][x] *= 1.0 / (exptime + shutterImage->data.F32[y][x]);
+            }
+        }
+        psMetadataAddF32 (cell->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "exposure time re-normalized to 1.0", 1.0); // Exposure time
+        psString line = NULL;
+        psStringAppend (&line, "extreme exposure time %f, re-normalized to 1.0", exptime);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, line, "");
+        psFree (line);
+    } else {
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                if (mask && !isfinite(shutterImage->data.F32[y][x])) {
+                    mask->data.PS_TYPE_MASK_DATA[y][x] |= blank;
+                    image->data.F32[y][x] = NAN;
+                    continue;
+                }
+                image->data.F32[y][x] *= exptime / (exptime + shutterImage->data.F32[y][x]);
+            }
+        }
+    }
+    psFree(shutterImage);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Shutter correction completed at ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#define IMAGES_BUFFER 10                // Allocate space for this many images at a time
+
+static void shutterCorrectionDataFree(pmShutterCorrectionData *data)
+{
+    psFree(data->regions);
+    psFree(data->mean);
+    psFree(data->stdev);
+
+    psFree(data->exptimes);
+    psFree(data->refs);
+
+    return;
+}
+
+pmShutterCorrectionData *pmShutterCorrectionDataAlloc(int numCols, int numRows, int size)
+{
+    pmShutterCorrectionData *data = psAlloc(sizeof(pmShutterCorrectionData));
+    psMemSetDeallocator(data, (psFreeFunc)shutterCorrectionDataFree);
+
+    data->num = 0;
+    data->numCols = 0;
+    data->numRows = 0;
+
+    data->regions = psArrayAlloc(MEASURE_SAMPLES);
+    for (int j = 0; j < MEASURE_SAMPLES; j++) {
+        int x = (j % 2) ? size : numCols - size - 1;
+        int y = (j > 1) ? size : numRows - size - 1;
+        psRegion region = psRegionForSquare(x, y, size);
+        data->regions->data[j] = psRegionAlloc(region.x0, region.x1, region.y0, region.y1);
+    }
+
+    data->mean = psArrayAlloc(MEASURE_SAMPLES);
+    data->stdev = psArrayAlloc(MEASURE_SAMPLES);
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+        data->mean->data[i] = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+        data->stdev->data[i] = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+    }
+
+    data->exptimes = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+    data->refs = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+
+    return data;
+}
+
+bool pmShutterCorrectionAddReadout(pmShutterCorrectionData *data,
+                                   const pmReadout *readout, ///< Readout to add
+                                   psStatsOptions meanStat, ///< Statistic to use for mean
+                                   psStatsOptions stdevStat, ///< Statistic to use for stdev
+                                   psMaskType maskVal, ///< Mask value
+                                   psRandom *rng ///< Random number generator
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(data, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, NULL);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, NULL);
+    if (data->num == 0) {
+        data->numCols = readout->image->numCols;
+        data->numRows = readout->image->numRows;
+    } else {
+        PS_ASSERT_IMAGE_SIZE(readout->image, data->numCols, data->numRows, NULL);
+    }
+    if (readout->mask) {
+        PS_ASSERT_IMAGE_NON_NULL(readout->mask, NULL);
+        PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, NULL);
+        PS_ASSERT_IMAGE_SIZE(readout->mask, data->numCols, data->numRows, NULL);
+    }
+    if (readout->weight) {
+        PS_ASSERT_IMAGE_NON_NULL(readout->weight, NULL);
+        PS_ASSERT_IMAGE_TYPE(readout->weight, PS_TYPE_F32, NULL);
+        PS_ASSERT_IMAGE_SIZE(readout->weight, data->numCols, data->numRows, NULL);
+    }
+
+    // Add the exposure time
+    float exptime = psMetadataLookupF32(NULL, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!isfinite(exptime)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure time is not set.");
+        return false;
+    }
+    data->exptimes->data.F32[data->exptimes->n] = exptime;
+    data->exptimes = psVectorExtend(data->exptimes, IMAGES_BUFFER, 1);
+
+    // Add the statistics
+
+    // Add the reference value
+    psStats *stats = psStatsAlloc(meanStat | stdevStat); // Statistics to apply
+    if (!rng) {
+        rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    } else {
+        psMemIncrRefCounter(rng);
+    }
+    if (!psImageBackground(stats, NULL, readout->image, readout->mask, maskVal, rng)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to measure reference statistics.\n");
+        psFree(stats);
+        psFree(rng);
+        return false;
+    }
+    psFree(rng);
+    float refValue = psStatsGetValue(stats, meanStat); // Reference value
+    psTrace("psModules.detrend", 3, "Reference value for shutter image = %f\n", refValue);
+    if (refValue <= 0.0) {
+        psError(PS_ERR_UNKNOWN, true, "Measured non-positive reference value.\n");
+        psFree(stats);
+        return false;
+    }
+    refValue = 1.0 / refValue;
+    data->refs->data.F32[data->refs->n] = refValue;
+    data->refs = psVectorExtend(data->refs, IMAGES_BUFFER, 1);
+
+    // Add the region statistics
+    for (int j = 0; j < MEASURE_SAMPLES; j++) {
+        psRegion *region = data->regions->data[j]; // Region of interest
+        psRegion adjusted = *region;    // Adjusted region, compensating for offsets
+        adjusted.x0 += readout->image->col0;
+        adjusted.x1 += readout->image->col0;
+        adjusted.y0 += readout->image->row0;
+        adjusted.y1 += readout->image->row0;
+        psImage *subImage = psImageSubset(readout->image, adjusted); // Sub-image
+        psImage *subMask = NULL;        // Sub-image of mask
+        if (readout->mask) {
+            subMask = psImageSubset(readout->mask, adjusted);
+        }
+        if (!psImageStats(stats, subImage, subMask, maskVal)) {
+            psString regionString = psRegionToString(adjusted);
+            psWarning("Unable to measure sample statistics at %s in image.\n",
+                      regionString);
+            psFree(regionString);
+        }
+        psFree(subImage);
+        psFree(subMask);
+
+        psVector *mean = data->mean->data[j]; // Vector of means for this region
+        psVector *stdev = data->stdev->data[j]; // Vector of standard deviations for this region
+
+        mean->data.F32[mean->n] = psStatsGetValue(stats, meanStat) * refValue;
+        stdev->data.F32[stdev->n] = psStatsGetValue(stats, stdevStat) * refValue;
+        data->mean->data[j] = psVectorExtend(mean, IMAGES_BUFFER, 1);
+        data->stdev->data[j] = psVectorExtend(stdev, IMAGES_BUFFER, 1);
+    }
+
+    data->num++;
+
+    return true;
+}
+
+float pmShutterCorrectionReference(const pmShutterCorrectionData *data)
+{
+    PS_ASSERT_PTR_NON_NULL(data, NAN);
+    PS_ASSERT_INT_POSITIVE(data->num, NAN);
+
+    float meanRef = 0.0;                // Mean reference offset
+    int numGood = 0;                    // Number of good measurements
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+        psVector *counts = data->mean->data[i]; // Mean for each image
+        psVector *errors = data->stdev->data[i]; // Stdev for each image
+        pmShutterCorrection *guess = pmShutterCorrectionGuess(data->exptimes, counts); // Guess at correction
+        pmShutterCorrection *corr = pmShutterCorrectionFullFit(data->exptimes, counts,
+                                                               errors, guess); // The actual correction
+        psFree(guess);
+        if (corr && isfinite(corr->offref)) {
+            psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
+            meanRef += corr->offref;
+            numGood++;
+        }
+        psFree(corr);
+    }
+
+    if (numGood == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to measure mean reference offset.\n");
+        return false;
+    }
+    meanRef /= (float)numGood;
+    psTrace("psModules.detrend", 3, "Mean reference value: %f\n", meanRef);
+    return meanRef;
+}
+
+bool pmShutterCorrectionGenerate(pmReadout *shutter, pmReadout *pattern, const psArray *inputs,
+                                 float reference, const pmShutterCorrectionData *data,
+                                 int nIter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(shutter, false);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_INT_EQUAL(data->num, inputs->n, false);
+    PS_ASSERT_INT_NONNEGATIVE(nIter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                inputs)) {
+        psError(PS_ERR_UNKNOWN, false, "No valid input readouts.");
+        return false;
+    }
+
+    pmReadoutUpdateSize(shutter, minInputCols, minInputRows, xSize, ySize, false, false, maskVal);
+    if (pattern) {
+        pmReadoutUpdateSize(pattern, minInputCols, minInputRows, xSize, ySize, false, false, maskVal);
+    }
+
+    psImage *nums = pmReadoutAnalysisImage(shutter, PM_READOUT_STACK_ANALYSIS_COUNT, xSize, ySize,
+                                           PS_TYPE_U16, 0); // Image with number fitted per pixel
+    if (!nums) {
+        return false;
+    }
+    psImage *sigma = pmReadoutAnalysisImage(shutter, PM_READOUT_STACK_ANALYSIS_SIGMA, xSize, ySize,
+                                            PS_TYPE_F32, NAN); // Image with stdev per pixel
+    if (!sigma) {
+        psFree(nums);
+        return false;
+    }
+
+    psImage *shutterImage = shutter->image; // Shutter correction image
+    psImage *patternImage; // Illumination pattern
+    if (pattern) {
+        patternImage = psMemIncrRefCounter(pattern->image);
+    } else {
+        patternImage = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+    }
+
+    int num = data->num;                // Number of images
+    psVector *counts = psVectorAlloc(num, PS_TYPE_F32); // Counts in each image
+    psVector *errors = psVectorAlloc(num, PS_TYPE_F32); // Counts in each image
+    psVector *mask = psVectorAlloc(num, PS_TYPE_MASK); // Mask for each image
+    psTrace("psModules.detrend", 2, "Performing linear fit on individual pixels...\n");
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - shutter->row0; // y position on output readout
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - shutter->col0; // x position on output readout
+
+            psVectorInit(mask, 0);
+            for (int r = 0; r < num; r++) {
+                pmReadout *readout = inputs->data[r]; // Readout of interest
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+                psImage *image = readout->image; // Image of interest
+                float ref = data->refs->data.F32[r]; // (Inverse) reference value
+                counts->data.F32[r] = image->data.F32[yIn][xIn] * ref;
+                if (readout->mask) {
+                    mask->data.PS_TYPE_MASK_DATA[r] = readout->mask->data.PS_TYPE_MASK_DATA[yIn][xIn];
+                }
+                if (readout->weight) {
+                    errors->data.F32[r] = sqrtf(readout->weight->data.F32[yIn][xIn]) * ref;
+                } else {
+                    errors->data.F32[r] = sqrtf(image->data.F32[yIn][xIn]) * ref;
+                }
+            }
+
+            pmShutterCorrection *corr = pmShutterCorrectionLinFit(data->exptimes, counts, errors, mask,
+                                                                  reference, nIter, rej, maskVal);
+            if (!corr) {
+                // Nothing we can do about it
+                psErrorClear();
+                shutterImage->data.F32[yOut][xOut] = NAN;
+                patternImage->data.F32[yOut][xOut] = NAN;
+                nums->data.U16[yOut][xOut] = 0;
+                sigma->data.F32[yOut][xOut] = NAN;
+                continue;
+            }
+            shutterImage->data.F32[yOut][xOut] = corr->offset;
+            patternImage->data.F32[yOut][xOut] = corr->scale;
+            nums->data.U16[yOut][xOut] = corr->num;
+            sigma->data.F32[yOut][xOut] = corr->stdev;
+            psFree(corr);
+        }
+    }
+    psFree(mask);
+    psFree(errors);
+    psFree(counts);
+    psFree(nums);
+    psFree(sigma);
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    bool success = pmConceptsAverageCells(shutter->parent, inputCells, NULL, NULL, true);
+    psFree(inputCells);
+
+    // Correct the exposure times --- they don't make sense any more.
+    psMetadataItem *item = psMetadataLookup(shutter->parent->concepts, "CELL.EXPOSURE");
+    item->data.F32 = NAN;
+    item = psMetadataLookup(shutter->parent->concepts, "CELL.DARKTIME");
+    item->data.F32 = NAN;
+
+    shutter->data_exists = true;
+    shutter->parent->data_exists = true;
+    shutter->parent->parent->data_exists = true;
+
+    if (pattern) {
+        pattern->data_exists = true;
+        pattern->parent->data_exists = true;
+        pattern->parent->parent->data_exists = true;
+    }
+
+    return success;
+}
Index: /branches/eam_branch_20080324/psModules/src/detrend/pmShutterCorrection.h
===================================================================
--- /branches/eam_branch_20080324/psModules/src/detrend/pmShutterCorrection.h	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/detrend/pmShutterCorrection.h	(revision 17278)
@@ -0,0 +1,195 @@
+/* @file pmShutterCorrection.h
+ * @brief Functions to build and apply a shutter exposure-time correction.
+ *
+ * @author Eugene Magnier, IfA
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-29 03:10:17 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_SHUTTER_CORRECTION_H
+#define PM_SHUTTER_CORRECTION_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/*  A mechanical shutter may not yield uniform exposure times as a function of
+ *  position on the detector.  The typical error consists of a constant
+ *  exposure-time offset relative to the requested value, ie exposure time is
+ *  T_o + dT(x,y).  The exposure error, dT, may be measured with the following
+ *  scheme.  Obtain a set of exposures with different exposures times taken of
+ *  the same flat-field source; the source must be spatially stable between the
+ *  exposures, but need not have a stable amplitude.  For an illuminating flux
+ *  of intensity F(x,y) = F_o f(x,y), the signal recorded by any pixel in the
+ *  detector is given by: S(t,x,y) = F_o(t) f(x,y) (T_o + dT(x,y)) where F_o is
+ *  the F_o(t) is the (variable) overall intensity of the illuminating source
+ *  and f(x,y) is the spatial illumination patter times the flat-field response.
+ *  Choose a reference location in the image (eg, the detector center) and
+ *  divide by the value of that region (ie, mean or median):
+ *
+ *  s(t,x,y) = S(t,x,y) / S(t,0,0)
+ *  s(t,x,y) = F_o(t) f(x,y) (T_o + dT(x,y)) / F_o(t) f(0,0) (T_o + dT(0,0))
+ *  s(t,x,y) = f(x,y) (T_o + dT(x,y)) / f(0,0) (T_o + dT(0,0))
+ *
+ *  we can absorb the term f(0,0) into f(x,y) as we have no motivation for the
+ *  scale of f(x,y).  For any single pixel, over the set of exposures, we thus
+ *  need to solve for dT(x,y), dT(0,0), and f'(x,y) in the equation:
+ *  s(t,x,y) = f'(x,y) (T_o + dT(x,y)) / (T_o + dT(0,0))
+ *
+ *  we avoid directly fitting these values as the process would be a non-linear
+ *  least-squares problem for every pixel in the image, and thus very time
+ *  consuming.  There are linear options which may be used instead.
+ *  First, as T_o goes to a large value, s() approaches the value of f'(x,y).
+ *  Next, as T_o goes to a very small value, s() approaches the value of
+ *  f'(x,y)*dT(x,y)/dT(0,0).  Finally, when s() has the value of
+ *  f'(x,y)*(1 + dT(x,y)/dT(0,0))/2, T_o has the value of dT(0,0).  with data
+ *  points covering a reasonable dynamic range, we can solve for these three
+ *  values by interpolation and/or extrapolation.
+ *
+ *  To take the strategy one step further, we could use the above recipe to
+ *  obtain a guess for the three parameters and then apply non-linear fitting to
+ *  solve more accurately for the parameters.  If we limit this operation to a
+ *  handful of positions in the image (user defined, but the obvious choice would
+ *  be positions near the center, edges, and corners), then we may determine a
+ *  good value for dT(0,0).  Since there is only one dT(0,0) for the image, we
+ *  can apply the resulting measurement to the rest of the pixels in the image.
+ *  If dT(0,0) is not a free parameter, then the fitting process is linear in
+ *  terms of dT(x,y) and f'(x,y)
+ */
+
+/// Shutter correction parameters, applicable for a single pixel
+typedef struct {
+    double scale;                       ///< The normalisation for an exposure, A(k)
+    double offset;                      ///< The time offset, dTk
+    double offref;                      ///< The reference time offset, dTo
+    int num;                            ///< Number of points used
+    float stdev;                        ///< Standard deviation
+} pmShutterCorrection;
+
+/// Allocator for shutter correction parameters
+pmShutterCorrection *pmShutterCorrectionAlloc();
+
+/// Guess a shutter correction, based on plot of counts vs exposure time
+///
+/// This function is used before doing the full non-linear fit, to get parameters close to the true.  Assumes
+/// exptime vector is sorted (ascending order; longest is last) prior to input.
+pmShutterCorrection *pmShutterCorrectionGuess(
+    const psVector *exptime,            ///< Exposure times for each exposure
+    const psVector *counts              ///< Counts for each exposure
+    );
+
+/// Generate shutter correction based on a linear fit
+///
+/// Performs a linear fit to counts as a function of exposure time, with the reference time offset fixed (so
+/// that the system is linear).  Performs iterative clipping, if nIter > 1.
+pmShutterCorrection *pmShutterCorrectionLinFit(
+    const psVector *exptime,            ///< Exposure times for each exposure
+    const psVector *counts,             ///< Counts for each exposure
+    const psVector *cntError,           ///< Error in the counts
+    const psVector *mask,               ///< Mask for each exposure
+    float offref,                       ///< Reference time offset
+    int nIter,                          ///< Number of iterations
+    float rej,                          ///< Rejection threshold (sigma)
+    psMaskType maskVal                  ///< Mask value
+    );
+
+/// Generate shutter correction based on a full non-linear fit
+///
+/// Performs a full non-linear fit to counts as a function of exposure time.  The main purpose is to solve for
+/// the reference time offset, so that future fits may be performed using linear fitting with the reference
+/// time offset fixed.
+pmShutterCorrection *pmShutterCorrectionFullFit(
+    const psVector *exptime,            ///< Exposure times for each exposure
+    const psVector *counts,             ///< Counts for each exposure
+    const psVector *cntError,           ///< Error in the counts
+    const pmShutterCorrection *guess    ///< Initial guess
+    );
+
+/// Measure a shutter correction image from an array of images
+///
+/// Given an array of readouts (with known exposure times from the cell concepts), this function measures the
+/// shutter correction (our principal concern is for the time offset, rather than the normalisation) by
+/// measuring the reference time offset using the full non-linear fit for a small number of representative
+/// regions (middle and corners), and then using that to perform a linear fit to each pixel.
+bool pmShutterCorrectionMeasure(
+    pmReadout *output,                  ///< Output readout
+    const psArray *readouts,            ///< Array of readouts
+    int size,                           ///< Size of samples for statistics for non-linear fit
+    psStatsOptions meanStat,            ///< Statistic to use for mean
+    psStatsOptions stdevStat,           ///< Statistic to use for stdev
+    int nIter,                          ///< Number of iterations
+    float rej,                          ///< Rejection threshold (sigma)
+    psMaskType maskVal                  ///< Mask value
+    );
+
+/// Apply a shutter correction
+///
+/// Given a shutter correction (with dT for each pixel), applies this correction to an input image.
+bool pmShutterCorrectionApply(
+    pmReadout *readout,                 ///< Readout to which to apply shutter correction
+    const pmReadout *shutter,           ///< Shutter correction readout, with dT for each pixel
+    psMaskType blank                    ///< Value to give blank pixels
+    );
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions for doing the shutter correction piece-meal (don't have to read entire image stack into memory at
+// once).  A single read run through the stack is required, calling pmShutterCorrectionAddReadout on each.
+// Then pmShutterCorrectionReference provides the required reference shutter time, so that
+// pmShutterCorrectionGenerate can generate a shutter correction piece by piece as overlapping pixels from
+// each input are read in.
+
+
+/// Data for measuring the shutter correction
+typedef struct {
+    int num;                            ///< Number of images
+    int numCols, numRows;               ///< Size of images
+    psArray *regions;                   ///< Regions at which to measure statistics
+    psArray *mean;                      ///< Vector of means at each region
+    psArray *stdev;                     ///< Vector of standard deviations at each region
+    psVector *exptimes;                 ///< Exposure times for each image
+    psVector *refs;                     ///< Reference fluxes
+} pmShutterCorrectionData;
+
+/// Allocator for pmShutterCorrectionData
+pmShutterCorrectionData *pmShutterCorrectionDataAlloc(int numCols, int numRows, ///< Size of images
+                                                      int size ///< Size of regions
+    );
+
+/// Add a readout to the correction data
+///
+/// Performs statistics on the readout, recording the data
+bool pmShutterCorrectionAddReadout(
+    pmShutterCorrectionData *data,      ///< Correction data
+    const pmReadout *readout,           ///< Readout to add
+    psStatsOptions meanStat,            ///< Statistic to use for mean
+    psStatsOptions stdevStat,           ///< Statistic to use for stdev
+    psMaskType maskVal,                 ///< Mask value
+    psRandom *rng                       ///< Random number generator
+    );
+
+/// Calculate the reference shutter time from the correction data
+float pmShutterCorrectionReference(
+    const pmShutterCorrectionData *data ///< Correction data
+    );
+
+/// Generate a shutter correction
+///
+/// Performs the linear fit to each pixel in the stack.
+bool pmShutterCorrectionGenerate(
+    pmReadout *shutter,                 ///< Shutter correction
+    pmReadout *pattern,                 ///< Background pattern (or NULL)
+    const psArray *inputs,              ///< Stack of input pmReadouts
+    float reference,                    ///< Reference shutter time (from pmShutterCorrectionRef)
+    const pmShutterCorrectionData *data, ///< Correction data
+    int nIter,                          ///< Number of iterations
+    float rej,                          ///< Rejection threshold (sigma)
+    psMaskType maskVal                  ///< Mask value
+    );
+
+
+
+/// @}
+#endif
Index: /branches/eam_branch_20080324/psModules/src/imcombine/pmReadoutCombine.c
===================================================================
--- /branches/eam_branch_20080324/psModules/src/imcombine/pmReadoutCombine.c	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/imcombine/pmReadoutCombine.c	(revision 17278)
@@ -0,0 +1,375 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPAMaskWeight.h"
+#include "pmConceptsAverage.h"
+#include "pmReadoutStack.h"
+
+#include "pmReadoutCombine.h"
+
+//#define SHOW_BUSY 1                   // Show that the function is busy
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Allocator for pmCombineParams
+pmCombineParams *pmCombineParamsAlloc(psStatsOptions combine)
+{
+    pmCombineParams *params = psAlloc(sizeof(pmCombineParams));
+
+    params->combine = combine;
+    params->maskVal = 0;
+    params->blank = 0;
+    params->nKeep = 0;
+    params->fracHigh = 0.0;
+    params->fracHigh = 0.0;
+    params->iter = 1;
+    params->rej = INFINITY;
+    params->weights = false;
+
+    return params;
+}
+
+
+// XXX: Maybe add support for S16 and S32 types.  Currently, only F32 supported.
+bool pmReadoutCombine(pmReadout *output, const psArray *inputs, const psVector *zero, const psVector *scale,
+                      const pmCombineParams *params)
+{
+    // Check inputs
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_PTR_NON_NULL(params, false);
+    if (zero) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_SIZE(zero, inputs->n, false);
+    }
+    if (scale) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_SIZE(scale, inputs->n, false);
+    }
+    PS_ASSERT_FLOAT_WITHIN_RANGE(params->fracLow, 0.0, 1.0, false);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(params->fracHigh, 0.0, 1.0, false);
+    if (params->combine != PS_STAT_SAMPLE_MEAN && params->combine != PS_STAT_SAMPLE_MEDIAN &&
+            params->combine != PS_STAT_ROBUST_MEDIAN && params->combine != PS_STAT_FITTED_MEAN &&
+            params->combine != PS_STAT_CLIPPED_MEAN) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Combination method is not SAMPLE_MEAN, SAMPLE_MEDIAN, "
+                "ROBUST_MEDIAN, FITTED_MEAN or CLIPPED_MEAN.\n");
+        return false;
+    }
+    for (int i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        if (params->weights && !readout->weight) {
+            psError(PS_ERR_UNEXPECTED_NULL, true,
+                    "Rejection based on weights requested, but no weights supplied for image %d.\n", i);
+            return false;
+        }
+    }
+
+    bool first = !output->image;        // First pass through?
+
+    pmHDU *hdu = pmHDUFromReadout(output); // Output HDU
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find HDU for readout.\n");
+        return false;
+    }
+
+    if (first) {
+        psString comment = NULL;        // Comment to add to header
+        psStringAppend(&comment, "Combining using statistic: %x", params->combine);
+        if (!hdu->header) {
+            hdu->header = psMetadataAlloc();
+        }
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+    }
+
+    psStatsOptions combineStdev = 0; // Statistics option for weights
+    switch (params->combine) {
+      case PS_STAT_SAMPLE_MEAN:
+      case PS_STAT_SAMPLE_MEDIAN:
+        combineStdev = PS_STAT_SAMPLE_STDEV;
+        break;
+      case PS_STAT_ROBUST_MEDIAN:
+        combineStdev = PS_STAT_ROBUST_STDEV;
+        break;
+      case PS_STAT_FITTED_MEAN:
+        combineStdev = PS_STAT_FITTED_STDEV;
+        break;
+      case PS_STAT_CLIPPED_MEAN:
+        combineStdev = PS_STAT_CLIPPED_STDEV;
+        break;
+      default:
+        psAbort("Should never get here --- checked params->combine before.\n");
+    }
+
+    psStats *stats = psStatsAlloc(params->combine | combineStdev); // The statistics to use in the combination
+    if (params->combine == PS_STAT_CLIPPED_MEAN) {
+        stats->clipSigma = params->rej;
+        stats->clipIter = params->iter;
+
+        if (first) {
+            psString comment = NULL;    // Comment to add to header
+            psStringAppend(&comment, "Combination clipping: %d iterations, rejection at %f sigma",
+                           params->iter, params->rej);
+            psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+            psFree(comment);
+        }
+    }
+    if (params->weights && first) {
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                         "Using input weights to combine images", "");
+    }
+
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                inputs)) {
+        psError(PS_ERR_UNKNOWN, false, "No valid input readouts.");
+        return false;
+    }
+
+    pmReadoutUpdateSize(output, minInputCols, minInputRows, xSize, ySize, true, params->weights,
+                        params->blank);
+    psTrace("psModules.imcombine", 7, "Output minimum: %d,%d\n", output->col0, output->row0);
+
+    psImage *counts = pmReadoutAnalysisImage(output, PM_READOUT_STACK_ANALYSIS_COUNT, xSize, ySize,
+                                             PS_TYPE_U16, 0);
+    if (!counts) {
+        return false;
+    }
+    psImage *sigma = pmReadoutAnalysisImage(output, PM_READOUT_STACK_ANALYSIS_SIGMA, xSize, ySize,
+                                            PS_TYPE_F32, NAN);
+    if (!sigma) {
+        psFree(counts);
+        return false;
+    }
+
+    stats->options |= combineStdev;
+
+    // We loop through each pixel in the output image.  We loop through each input readout.  We determine if
+    // that output pixel is contained in the image from that readout.  If so, we save it in psVector pixels.
+    // If not, we set a mask for that element in pixels.  Then, we mask off pixels not between fracLow and
+    // fracHigh.  Then we call the vector stats routine on those pixels/mask.  Then we set the output pixel
+    // value to the result of the stats call.
+
+    psVector *pixels = psVectorAlloc(inputs->n, PS_TYPE_F32); // Stack of pixels
+    psF32 *pixelsData = pixels->data.F32; // Dereference pixels
+
+    psVector *mask   = psVectorAlloc(inputs->n, PS_TYPE_U8); // Mask for stack
+    psU8 *maskData = mask->data.U8;     // Dereference mask
+
+    psVector *weights = NULL;           // Stack of weights
+    psVector *errors = NULL;            // Stack of errors (sqrt of variance/weights), for psVectorStats
+    psF32 *weightsData = NULL;          // Dereference weights
+    if (params->weights) {
+        weights = psVectorAlloc(inputs->n, PS_TYPE_F32); // Stack of weights
+        weightsData = weights->data.F32;
+    }
+    psVector *index = NULL;             // The indices to sort the pixels
+
+    float keepFrac = 1.0 - params->fracLow - params->fracHigh; // Fraction of pixels to keep
+    if (keepFrac != 1.0 && first) {
+        psString comment = NULL;        // Comment to add to header
+        psStringAppend(&comment, "Min/max rejection: %f high, %f low, keep %d",
+                       params->fracHigh, params->fracLow, params->nKeep);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+    }
+
+    psMaskType maskVal = params->maskVal; // The mask value
+    if (maskVal && first) {
+        psString comment = NULL;        // Comment to add to header
+        psStringAppend(&comment, "Mask for combination: %x", maskVal);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+    }
+
+    #ifndef PS_NO_TRACE
+    psTrace("psModules.imcombine", 3, "Iterating output: %d --> %d, %d --> %d\n",
+            minInputCols - output->col0, maxInputCols - output->col0,
+            minInputRows - output->row0, maxInputRows - output->row0);
+    if (psTraceGetLevel("psModules.imcombine") >= 3) {
+        for (int r = 0; r < inputs->n; r++) {
+            pmReadout *readout = inputs->data[r]; // Input readout
+            psTrace("psModules.imcombine", 3, "Iterating input %d: %d --> %d, %d --> %d\n", r,
+                    minInputCols - readout->col0, maxInputCols - readout->col0,
+                    minInputRows - readout->row0, maxInputRows - readout->row0);
+        }
+    }
+    #endif
+
+    // Dereference output products
+    psF32 **outputImage  = output->image->data.F32; // Output image
+    psU8  **outputMask   = output->mask->data.U8; // Output mask
+    psF32 **outputWeight = NULL; // Output weight map
+    if (output->weight) {
+        outputWeight = output->weight->data.F32;
+    }
+
+    psVector *invScale = NULL;          // Inverse scale; pre-calculated for efficiency
+    if (scale) {
+        invScale = (psVector*)psBinaryOp(NULL, psScalarAlloc(1.0, PS_TYPE_F32), "/", (const psPtr)scale);
+    }
+
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - output->row0; // y position on output readout
+        #ifdef SHOW_BUSY
+
+        if (psTraceGetLevel("psModules.imcombine") > 9) {
+            printf("Processing row %d\r", i);
+            fflush(stdout);
+        }
+        #endif
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - output->col0; // x position on output readout
+
+            int numValid = 0;           // Number of valid pixels in the stack
+            memset(maskData, 0, mask->n * sizeof(psU8)); // Reset the mask
+            for (int r = 0; r < inputs->n; r++) {
+                pmReadout *readout = inputs->data[r]; // Input readout
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+                psImage *image = readout->image; // The readout image
+
+                #if 0 // This should have been taken care of already:
+                // Check bounds
+                if (xIn < 0 || xIn >= image->numCols || yIn < 0 || yIn >= image->numRows) {
+                    continue;
+                }
+                #endif
+
+                pixelsData[r] = image->data.F32[yIn][xIn];
+                if (!isfinite(pixelsData[r])) {
+                    maskData[r] = 1;
+                    continue;
+                }
+
+                // Check mask
+                psImage *roMask = readout->mask; // The mask image
+                if (roMask && roMask->data.U8[yIn][xIn] & maskVal) {
+                    maskData[r] = 1;
+                    continue;
+                }
+
+                if (params->weights) {
+                    weightsData[r] = readout->weight->data.F32[yIn][xIn];
+                }
+
+                if (zero) {
+                    pixelsData[r] -= zero->data.F32[r];
+                }
+                if (scale) {
+                    pixelsData[r] *= invScale->data.F32[r];
+                    if (params->weights) {
+                        weightsData[r] *= invScale->data.F32[r] * invScale->data.F32[r];
+                    }
+                }
+
+                numValid++;
+            }
+
+            if (numValid == 0) {
+                outputMask[yOut][xOut] = params->blank;
+                outputImage[yOut][xOut] = NAN;
+                counts->data.U16[yOut][xOut] = 0;
+                sigma->data.F32[yOut][xOut] = NAN;
+                continue;
+            }
+
+            // Apply fracLow,fracHigh if there are enough pixels
+            if (numValid * keepFrac >= params->nKeep && keepFrac != 1.0) {
+                index = psVectorSortIndex(index, pixels);
+                int numLow = numValid * params->fracLow; // Number of low pixels to clip
+                int numHigh = numValid * params->fracHigh; // Number of high pixels to clip
+                // Low pixels
+                psS32 *indexData = index->data.S32; // Dereference index
+                for (int k = 0, numMasked = 0; numMasked < numLow && k < index->n; k++) {
+                    // Don't count the ones that are already masked
+                    if (!maskData[indexData[k]]) {
+                        maskData[indexData[k]] = 1;
+                        numMasked++;
+                        numValid--;
+                    }
+                }
+                // High pixels
+                for (int k = pixels->n - 1, numMasked = 0; numMasked < numHigh && k >= 0; k--) {
+                    // Don't count the ones that are already masked
+                    if (!maskData[indexData[k]]) {
+                        maskData[indexData[k]] = 1;
+                        numMasked++;
+                        numValid--;
+                    }
+                }
+            }
+            counts->data.U16[yOut][xOut] = numValid;
+
+            // XXXXX this step probably is very expensive : convert errors to variance everywhere?
+            if (params->weights) {
+                errors = (psVector*)psUnaryOp(errors, weights, "sqrt");
+            }
+
+            // Combination
+            if (!psVectorStats(stats, pixels, errors, mask, 1)) {
+                // Can't do much about it, but it's not worth worrying about
+                psErrorClear();
+                outputImage[yOut][xOut] = NAN;
+                outputMask[yOut][xOut] = params->blank;
+                if (params->weights) {
+                    outputWeight[yOut][xOut] = NAN;
+                }
+                sigma->data.F32[yOut][xOut] = NAN;
+            } else {
+                outputImage[yOut][xOut] = psStatsGetValue(stats, params->combine);
+                outputMask[yOut][xOut] = isfinite(outputImage[yOut][xOut]) ? 0 : params->blank;
+                if (params->weights) {
+                    float stdev = psStatsGetValue(stats, combineStdev);
+                    outputWeight[yOut][xOut] = PS_SQR(stdev); // Variance
+                    // XXXX this is not the correct formal error.
+                    // also, the weighted mean is not obviously the correct thing here
+                }
+                sigma->data.F32[yOut][xOut] = psStatsGetValue(stats, combineStdev);
+            }
+        }
+    }
+    #ifdef SHOW_BUSY
+    if (psTraceGetLevel("psModules.imcombine") > 9) {
+        printf("\n");
+    }
+    #endif
+    psFree(index);
+    psFree(pixels);
+    psFree(mask);
+    psFree(weights);
+    psFree(errors);
+    psFree(stats);
+    psFree(invScale);
+    psFree(counts);
+    psFree(sigma);
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    bool success = pmConceptsAverageCells(output->parent, inputCells, NULL, NULL, true);
+    psFree(inputCells);
+
+    output->data_exists = true;
+    output->parent->data_exists = true;
+    output->parent->parent->data_exists = true;
+
+    return success;
+}
+
Index: /branches/eam_branch_20080324/psModules/src/imcombine/pmReadoutCombine.h
===================================================================
--- /branches/eam_branch_20080324/psModules/src/imcombine/pmReadoutCombine.h	(revision 17278)
+++ /branches/eam_branch_20080324/psModules/src/imcombine/pmReadoutCombine.h	(revision 17278)
@@ -0,0 +1,47 @@
+/* @file  pmReadoutCombine.h
+ * @brief Combine multiple readouts
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-29 03:10:17 $
+ * Copyright 2004-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_READOUT_COMBINE_H
+#define PM_READOUT_COMBINE_H
+
+/// @addtogroup imcombine Image Combinations
+/// @{
+
+/// Combination parameters for pmReadoutCombine.
+///
+/// These values define how the combination is performed, and should not vary by detector, so that it can be
+/// re-used for multiple combinations.
+typedef struct {
+    psStatsOptions combine;             ///< Statistic to use when performing the combination
+    psMaskType maskVal;                 ///< Mask value
+    psMaskType blank;                   ///< Mask value to give blank (i.e., no data) pixels
+    int nKeep;                          ///< Mimimum number of pixels to keep
+    float fracHigh;                     ///< Fraction of high pixels to immediately throw
+    float fracLow;                      ///< Fraction of low pixels to immediately throw
+    int iter;                           ///< Number of iterations for clipping (for CLIPPED_MEAN only)
+    float rej;                          ///< Rejection threshould for clipping (for CLIPPED_MEAN only)
+    bool weights;                       ///< Use the supplied weights (instead of calculated stdev)?
+} pmCombineParams;
+
+// Allocator for pmCombineParams
+pmCombineParams *pmCombineParamsAlloc(psStatsOptions statsOptions ///< Statistic to use for combination
+                                     );
+
+/// Combine multiple readouts, applying zero and scale, with optional minmax clipping
+bool pmReadoutCombine(pmReadout *output,///< Output readout; altered and returned
+                      const psArray *inputs,  ///< Array of input readouts (F32 image and weight, U8 mask)
+                      const psVector *zero, ///< Zero corrections to subtract from input, or NULL
+                      const psVector *scale, ///< Scale corrections to divide into input, or NULL
+                      const pmCombineParams *params ///< Combination parameters
+                     );
+
+/// @}
+#endif
