/*****************************************************************************
******************************************************************************
**<AUTO>
**
** FILE:	targets.cc
**
**<HTML>
**	This file contains C++ functions to select spectroscopic targets.
**</HTML>
**</AUTO>
**
**
** ENVIRONMENT:
**	C++.
**
** REQUIRED PRODUCTS:
**	photoSch
**
******************************************************************************
******************************************************************************
*/
#include <sdb.h>
#include <genericEnum.h>
#include <arFrameStat.h>
#include <arRun.h>
#include <arTrans.h>
#include <arPTrans.h>
#include <arPSCalib.h>
#include <arAstromCalib.h>
#include <arObject.h>
#include <arObjectData.h>
#include <arObjectCont.h>
#include <arObjectList.h>
#include <arFramesPipeRun.h>
#include <arFinalPhotoCalib.h>
#include <arSegment.h>
#include <arChunk.h>
#include <arTarget.h>
#include <dervish.h>
#include <taCalibObj.h>
#include <taGalaxies.h>
#include <taQuasars.h>
#include <taStars.h>
#include <taSerendipity.h>
#include <taRosat.h>
#include <taStandards.h>
#include <taCatalogs.h>
#include <astrotools.h>
#include <arObjectStatus.h>
#include <arFirst.h>
#include <arRosat.h>
#include <arTiledTarget.h>
#include <taCosmic.h>
#include "tsTarget.h"

static const int nFilters = 5;  // Number of SDSS photometirc filters

RET_CODE tsObjectTargetUnselect(ooHandle(arObject)& obj);

// Return 1 if this object or any of its daughters is a primary (or secondary
// if the "secondaries" argument is 1) object in the segment.
int
tsGoodFamily(const ooHandle(arObject)& obj, /* IN: handle to object */
	     int secondaries, /* IN: accept secondaries */
	     int firstField,  /* IN: 1=obj in first field in segment, 0=aint;
			       *      -1=frames pipeline run (ignore firstField
			       *      status, accept all OK_RUNs) */
	     int family       /* IN: 0=don't test family members,
			       *     1=test daughters only,
			       *     2=test entire family tree */)
{
  // Is this a good object in the segment?
  if ((obj->status() & AR_OBJECT_STATUS_PRIMARY) ||
      (obj->status() & AR_OBJECT_STATUS_SECONDARY && secondaries) ||
      (obj->status() & AR_OBJECT_STATUS_OK_RUN && firstField == -1))
    {
      if (firstField == -1)
	{
	  return 1;
	}
      else if (firstField == 1)
	{
	  if (obj->status() & AR_OBJECT_STATUS_FIRST_FIELD)
	    return 1;
	}
      else if (! (obj->status() & AR_OBJECT_STATUS_FIRST_FIELD))
	{
	  return 1;
	}
    }

  // Test its family
  if (family == 0)
    return 0;
  else if (family == 1)
    {
      // Test children only
      if (obj->nChild() > 0)
	{
	  ooItr(arObject) daughters;
	  if (obj->daughters(daughters, oocRead) != oocSuccess)
	    shError("tsIsGood: unable to initialize iterator over daughters");
	  while (daughters.next())
	    if (tsGoodFamily(daughters, secondaries, firstField, 1)) return 1;
	}
    }
  else
    {
      // Test entire tree
      int hasParent = 0;
      ooHandle(arObject) current;
      ooHandle(arObject) parent;
      obj->parent(parent);
      while (parent.isValid())
	{
	  hasParent = 1;
	  current = parent;
	  current->parent(parent);
	}
      if (hasParent == 1)
	if (tsGoodFamily(current, secondaries, firstField, 1)) return 1;
    }
  return 0;
}

/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	tsSegmentCalibrate
**
**<HTML>
**	Loop through all primary objects in a segment and calibrate them.
**	<em>export</em> controls whether a calibrated object FITS
**	"tsObj" file is written out: 0=no, 1=yes, but selected targets only,
**	2=yes, but primaries only, 3=primaries and secondaries,4=everything.
**	If <em>select</em> is TRUE, then select spectroscopic targets, both
**	in the database, and in the exported file (targets are selected
**	from primaries only).  If "parents" is 1, then export parent objects
**	also.  The "current" astrometric and photometric calibrations are used.
**	When exporting, if "split" is 1, then there is one file per field; if
**	0 then there is one file with one HDU per field.
**<p>
**      If the "noUpdate" option is set to 1, then the OPDB is not updated.
**	This is used to generate a set of "tsObj" files without modifying the
**	OPDB.
**<p>
**	If the handle to the segment is NULL, then a handle to a frames
**	pipeline run may be passed in.  This allows exporting of tsObj files
**	for unresolved frames pipeline runs.  The PRIMARY/SECONDARY flags are
**	ignored.  OK_RUN objects are treated as if they were primary objects;
**	that is, all OK_RUN objects are exported, and if target selection is
**	run, then target selection is performed on all OK_RUN objects.
**	If target seletion is performed, then the "noUpdate" argument must
**	be set to 1, so that the OPDB is not updated.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
RET_CODE
tsSegmentCalibrate(ooHandle(arSegment)& segment, /* IN: segment to export */
		   ooHandle(arFramesPipeRun)& fpr, /* IN: frames pipeline run
						    *     to export, if
						    *     "segment" is a NULL
						    *     handle */
		   int field0Proc,               /* IN: first field to process
						  *     (-1 defaults to first
						  *     field in segment/run) */
		   int nFieldsProc,              /* IN: number of fields to
						  *     process, starting with
						  *     first field in the
						  *     segment --- 0=all;
						  *     used for debugging */
		   int export,                   /* IN:-1=don't export,
						  *     0=export field info,
						  *     1=export targets,
						  *     2=export primaries,
						  *     3=export prim and sec
						  *     4=everything */
		   int parents,                  /* IN:export parents(1=y,0=n)*/
		   int split,			 /* IN: one HDU per field */
		   int select,                   /* IN: select targets */
		   int force,                    /* IN:1=force re-select,0=no*/
		   int noUpdate,		 /* IN: 1=don't update the
						  *     OPDB, 0=do */
		   const char *outputDir,
		   /* IN: output directory if exporting */
		   const ooHandle(ooContObj) *targetCluster,
		   /* IN: clustering directive when creating spectro targets*/
		   const ooHandle(ooContObj) *firstCluster,
		   /* IN: clustering directive when creating FIRST objects */
		   const ooHandle(ooContObj) *rosatCluster,
		   /* IN: container for ROSAT catalog */
		   const TA_GALAXIES_PARAMS *galaxiesParams,
		   /* IN: Galaxies WG tunable parameters */
		   const TA_QUASARS_PARAMS *quasarsParams,
		   /* IN: Quasars WG tunable parameters */
		   const TA_SERENDIPITY_PARAMS *serendipityParams,
		   /* IN: Serendipity WG tunable parameters */
		   const TA_ROSAT_PARAMS *rosatParams,
		   /* IN: ROSAT tunable parameters */
		   const TA_STARS_PARAMS *starsParams,
		   /* IN: Stars WG tunable parameters */
		   const TA_STANDARDS_PARAMS *standardsParams,
		   /* IN: Standards tunable parameters */
		   TA_FIRST_CATALOG *first,
		   /* IN: FIRST catalog */
		   TA_ROSAT_CATALOG *rosat,
		   /* IN: ROSAT catalog */
		   TA_USNO_CATALOG *usno
		   /* IN: USNO catalog */)
{
  // Check for illegal argument combinations
  if (export == -1 && ! select)
    {
      shErrStackPush("tsSegmentCalibrate: export or select must be TRUE");
      return SH_GENERIC_ERROR;
    }
  if (force && noUpdate)
    {
      shErrStackPush("tsSegmentCalibrate: can't specify both force and noUpdate");
      return SH_GENERIC_ERROR;
    }

  // Determine whether a segment or frames pipeline run has been passed in
  ooHandle(arFramesPipeRun) run;
  int gotSegment;
  uint16 field0, nFields, segField0;
  if (segment.isValid())
    {
      // Get handle to Frames Pipeline Run for this segment
      run = segment->framesPipelineRun(oocRead);
      field0 = segment->field0();
      nFields = segment->nFields();
      segField0 = field0;
      gotSegment = 1;
    }
  else
    {
      // Frames pipeline runs must be handled in noUpdate mode.
      if (!noUpdate)
	{
	  shErrStackPush("tsSegmentCalibrate: can't process frames pipeline runs directly in update mode");
	  return SH_GENERIC_ERROR;
	}
      run = fpr;
      field0 = run->field0();
      nFields = run->nFields();
      segField0 = field0;
      gotSegment = 0;
    }
  uint32 runId = run->run();
  uint8 camCol = run->camCol();
  uint16 rerun= run->rerun();
  if (field0Proc > field0)
    {
      nFields -= (field0Proc - field0);
      field0 = field0Proc;
    }
  if (nFieldsProc > 0 && nFieldsProc < nFields) nFields = nFieldsProc;

  // Get current best astrometric calibration
  ooHandle(arAstromCalib) astrom =
    run->currentAstrometricCalibration(oocRead);
  if (! astrom.isValid())
    {
      shErrStackPush("tsSegmentCalibrate: no current astrometric calibration");
      return SH_GENERIC_ERROR;
    }
  if (fabs(astrom->equinox() - 2000.) > 0.000001)
    {
      shErrStackPush("tsSegmentCalibrate: equinox not 2000");
      return SH_GENERIC_ERROR;
    }
  int astromField0 = astrom->field0();
  if (astromField0 > field0 || astromField0+astrom->nFields() < field0+nFields)
    {
      shErrStackPush("tsSegmentCalibrate: astrometric calibration doesn't cover field range of the frames pipeline run");
      return SH_GENERIC_ERROR;
    }
  double node = astrom->node();
  double incl = astrom->incl();

  // Get astrometric calibration used by photo
  ooHandle(arAstromCalib) photoAstrom =
    run->astrometricCalibration(oocRead);
  if (! photoAstrom.isValid())
    {
      shErrStackPush("tsSegmentCalibrate: no photo astrometric calibration");
      return SH_GENERIC_ERROR;
    }
  if (fabs(photoAstrom->equinox() - 2000.) > 0.000001)
    {
      shErrStackPush("tsSegmentCalibrate: equinox not 2000");
      return SH_GENERIC_ERROR;
    }
  if (fabs(photoAstrom->node() - node) > 0.000001)
    {
      shErrStackPush("tsSegmentCalibrate: current and photo astrometric calibrations have different node");
      return SH_GENERIC_ERROR;
    }
  if (fabs(photoAstrom->incl() - incl) > 0.000001)
    {
      shErrStackPush("tsSegmentCalibrate: current and photo astrometric calibrations have different incl");
      return SH_GENERIC_ERROR;
    }

  // Set up array of camRow versus filter, in same order as filter order in
  // array fields in TA_CALIB_OBJ (ugriz).  This is used when getting TRANS
  // structures for specific filters.
  const int camRow[5] = {3, 5, 1, 2, 4};

  // All Frames Pipeline Runs were required when stuffed to have r' as their
  // reference filter for the primary positions.
  char refFilter = 'r';

  // Get final photometric calibration
  ooHandle(arFinalPhotoCalib) photom =
    run->currentPhotometricCalibration(oocRead);
  if (! photom.isValid())
    {
      shErrStackPush("tsSegmentCalibrate: no current photometric calibration");
      return SH_GENERIC_ERROR;
    }
  int photomField0 = photom->field0();
  if (photomField0 > field0 || photomField0+photom->nFields() < field0+nFields)
    {
      shErrStackPush("tsSegmentCalibrate: photometric calibration doesn't cover field range of the frames pipeline run");
      return TCL_ERROR;
    }

  // Set links to astrometric and photometric calibrations used
  if (select && !noUpdate)
    {
      if (force)
	{
	  segment->delTargetAstrometricCalibration();
	  segment->delTargetPhotometricCalibration();
	}
      if (segment->setTargetAstrometricCalibration(astrom) != oocSuccess)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't set target astrometric calibration");
	  return SH_GENERIC_ERROR;
	}
      if (segment->setTargetPhotometricCalibration(photom) != oocSuccess)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't set target photometric calibration");
	  return SH_GENERIC_ERROR;
	}
    }

  // Get postage stamp calibration
  ooHandle(arPSCalib) ps = run->postageStampCalibration(oocRead);

  // Get the drift scan
  ooHandle(arRun) scan = run->imagingRun();

  // Get exposure time (seconds)
  // HARWIRE: all photometric CCDs are 2048 rows long
  int nRowsPhys = 2048;
  double expTime = scan->c_obs() * nRowsPhys * 1.e-6;

  // Create output PDUs
  char fitsFile[100];
  HDR *hdr, *fieldHdr;
  TBLCOL *fieldTblCol = NULL;
  if (export > -1)
    {
      // Stuff header
      hdr = shHdrNew();
      if (hdr == NULL)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't allocate header");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertLogical(hdr, "SIMPLE", 1, "") != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword SIMPLE");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "BITPIX", 8, "") != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword BITPIX");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "NAXIS", 0, "") != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword NAXIS");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertLogical(hdr, "EXTEND", 1, "") != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword EXTEND");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "RUN", runId, "Imaging run number.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword RUN");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "CAMCOL", camCol, "Column in imaging camera.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword CAMCOL");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "RERUN", rerun, "Rerun number.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword RERUN");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "FIELD0", field0, "First field reduced.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FIELD0");
	  return SH_GENERIC_ERROR;
	}
      if (split)
	{
	  if (shHdrInsertInt(hdr, "NFIELDS", 1,
			     "Number of fields reduced.")!= SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword NFIELDS");
	      return SH_GENERIC_ERROR;
	    }
	}
      else
	{
	  if (shHdrInsertInt(hdr, "NFIELDS", nFields,
			     "Number of fields reduced.")!= SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword NFIELDS");
	      return SH_GENERIC_ERROR;
	    }
	}
      if (shHdrInsertInt(hdr, "STRIPE", scan->stripe(), "Stripe number.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword STRIPE");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "STRIP",
			   scan->strip() == AR_STRIP_NORTH ? "N" :
			   (scan->strip() == AR_STRIP_SOUTH ? "S" : "O"),
			   "N - north, S - south, O - other.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword STRIP");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertDbl(hdr, "EQUINOX", astrom->equinox(),
			 "Equinox of great circle scanned (Julian years).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword EQUINOX");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertDbl(hdr, "NODE", astrom->node(),
			 "Ascending node of great circle scanned (deg).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword NODE");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertDbl(hdr, "INCL", astrom->incl(),
			   "Inclination of great circle scanned (deg).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword INCL");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertDbl(hdr, "C_OBS", scan->c_obs(),
			 "CCD clock rate (usec/unbinned-row).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword C_OBS");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertDbl(hdr, "TRACKING", scan->tracking(),
			 "Tracking rate (arcsec/TAI-sec).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword TRACKING");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "PHOTO_ID", run->id(),
			   "Photometric pipeline run id.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword PHOTO_ID");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "PHOT_VER", run->version(),
			   "Version of frames pipeline used.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword PHO_VER");
	  return SH_GENERIC_ERROR;
	}
      ooHandle(arAstromCalib) targetAstrom;
      if (select)
	targetAstrom = astrom;
      else if (gotSegment)
	targetAstrom = segment->targetAstrometricCalibration(oocRead);
      if (targetAstrom.isValid())
	{
	  if (shHdrInsertAscii(hdr, "TASTR_ID", targetAstrom->id(),
			       "Astrometric pipeline run id for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword ASTRO_ID");
	      return SH_GENERIC_ERROR;
	    }
	  if (shHdrInsertAscii(hdr, "TAST_VER", targetAstrom->version(),
			       "Version of astrometric pipeline for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword ASTR_VER");
	      return SH_GENERIC_ERROR;
	    }
	}
      else
	{
	  if (shHdrInsertAscii(hdr, "TASTR_ID", "",
			       "Astrometric pipeline run id for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword ASTRO_ID");
	      return SH_GENERIC_ERROR;
	    }
	  if (shHdrInsertAscii(hdr, "TAST_VER", "",
			       "Version of astrometric pipeline for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword ASTR_VER");
	      return SH_GENERIC_ERROR;
	    }
	}
      ooHandle(arFinalPhotoCalib) targetPhotom;
      if (select)
	targetPhotom = photom;
      else if (gotSegment)
	targetPhotom = segment->targetPhotometricCalibration(oocRead);
      if (targetPhotom.isValid())
	{
	  if (shHdrInsertAscii(hdr, "TFCAL_ID", targetPhotom->id(),
			       "Photometric calibration run id for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FCALI_ID");
	      return SH_GENERIC_ERROR;
	    }
	  if (shHdrInsertAscii(hdr, "TFCA_VER", targetPhotom->version(),
			       "Version of fcalib for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FCAL_VER");
	      return SH_GENERIC_ERROR;
	    }
	}
      else
	{
	  if (shHdrInsertAscii(hdr, "TFCAL_ID", "",
			       "Photometric calibration run id for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FCALI_ID");
	      return SH_GENERIC_ERROR;
	    }
	  if (shHdrInsertAscii(hdr, "TFCA_VER", "",
			       "Version of fcalib for target.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FCAL_VER");
	      return SH_GENERIC_ERROR;
	    }
	}
      if (shHdrInsertAscii(hdr, "EASTR_ID", astrom->id(),
			   "Astrometric pipeline run id for export.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword ASTRO_ID");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "EAST_VER", astrom->version(),
			   "Version of astrometric pipeline for export.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword ASTR_VER");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "EFCAL_ID", photom->id(),
			   "Photometric calibration run id for export.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FCALI_ID");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "EFCA_VER", photom->version(),
			   "Version of fcalib for export.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FCAL_VER");
	  return SH_GENERIC_ERROR;
	}
      char version[80];
      sprintf(version, "%s", "$Name: ts_v2_9 $");
      version[strlen(version)-2] = '\000';
      if (strlen(&version[7]) == 0) sprintf(&version[7], "NOCVS:ts");
      char *obj_src = NULL;
      char *targ_src = NULL;
      if (select)
	{
	  if (noUpdate)
	    {
	      obj_src = "OPDB_PRELIM";
	      targ_src = "OPDB_PRELIM";
	    }
	  else
	    {
	      obj_src = "OPDB_EXPORT";
	      targ_src = "OPDB_EXPORT";
	    }
	  if (shHdrInsertAscii(hdr, "TARG_VER", &version[7],
			       "Version of target pipeline used.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword TARG_VER");
	      return SH_GENERIC_ERROR;
	    }
	}
      else if (gotSegment)
	{
	  if (segment->chunk()->selectTargets().isValid())
	    {
	      obj_src = "OPDB_EXPORT";
	      targ_src = "OPDB_EXPORT";
	      if (shHdrInsertAscii(hdr, "TARG_VER",
				   segment->chunk()->selectTargets()->version(),
				   "Version of target pipeline used.")
		  != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword TARG_VER");
		  return SH_GENERIC_ERROR;
		}
	    }
	  else
	    {
	      obj_src = "OPDB_PRELIM";
	      targ_src = "OPDB_PRELIM";
	      if (shHdrInsertAscii(hdr, "TARG_VER", "",
				   "Version of target pipeline used.")
		  != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword TARG_VER");
		  return SH_GENERIC_ERROR;
		}
	    }
	}
      else
	{
	  obj_src = "OPDB_PRELIM";
	  targ_src = "OPDB_PRELIM";
	  if (shHdrInsertAscii(hdr, "TARG_VER", "",
			       "Version of target pipeline used.")
	      != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword TARG_VER");
	      return SH_GENERIC_ERROR;
	    }
	}
      if (shHdrInsertAscii(hdr, "EXPO_VER", &version[7],
			   "Version of export pipeline used.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword EXPO_VER");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "FILTERS", "u g r i z",
			   "Filter order in arrays.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FILTERS");
	  return SH_GENERIC_ERROR;
	}

      // Copy this much of the header to the field header
      fieldHdr = shHdrNew();
      if (shHdrCopy(hdr, fieldHdr) != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't copy headers");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(fieldHdr, "DSCALE", ps->dscale(),
			 "Scaling for bias drift (lbias and rbias).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword DSCALE");
	  return SH_GENERIC_ERROR;
	}
      int lineNo;
      if (shHdrGetLineno(fieldHdr, "DSCALE", &lineNo) != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't find header keyword DSCALE");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertLine(fieldHdr, ++lineNo, "END") != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword END");
	  return SH_GENERIC_ERROR;
	}

      // Finish object header
      if (shHdrInsertAscii(hdr, "OBJ_SRC", obj_src, "Source of objects.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword OBJ_SRC");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "TARG_SRC", targ_src, "Source of target info.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword TARG_SRC");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertLogical(hdr, "RESOLVED", gotSegment,
			     "Data from a resolved segment.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword RESOLVED");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "EXPORTED", export,
			 "1=targets,2=prim,3=prim+sec,4=all")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword EXPORTED");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertLogical(hdr, "FAMILIES", parents,
			   "Contains family members of included objects")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FAMILIES");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertInt(hdr, "REF_BAND", 2, "Band used for canonical centre.")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword REF_BAND");
	  return SH_GENERIC_ERROR;
	}
      arFieldPhotoTrans psTrans = ps->trans(field0);
      char darkVar[80];
      char gain[80];
      sprintf(darkVar, "%.2f %.2f %.2f %.2f %.2f",
	      psTrans.filter(AR_U).dark_variance(),
	      psTrans.filter(AR_G).dark_variance(),
	      psTrans.filter(AR_R).dark_variance(),
	      psTrans.filter(AR_I).dark_variance(),
	      psTrans.filter(AR_Z).dark_variance());
      sprintf(gain, "%.2f %.2f %.2f %.2f %.2f",
	      psTrans.filter(AR_U).gain(),
	      psTrans.filter(AR_G).gain(),
	      psTrans.filter(AR_R).gain(),
	      psTrans.filter(AR_I).gain(),
	      psTrans.filter(AR_Z).gain());
      if (shHdrInsertAscii(hdr, "DARK_VAR", darkVar,
			   "Per-pixel variances at zero DN (DN^2).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword DARK_VAR");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertAscii(hdr, "GAIN", gain,
			   "Mean gains of amplifiers (e/DN).")
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword GAIN");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrGetLineno(hdr, "GAIN", &lineNo) != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't find header keyword GAIN");
	  return SH_GENERIC_ERROR;
	}
      if (shHdrInsertLine(hdr, ++lineNo, "END") != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword END");
	  return SH_GENERIC_ERROR;
	}

      // Write out PDUs
      if (!split)
	{
	  if (export > 0)
	    {
	      if (outputDir == NULL)
		sprintf(fitsFile, "tsObj-%06d-%d-%d-%04d.fit", runId,
			camCol, rerun, field0);
	      else
		sprintf(fitsFile, "%s/tsObj-%06d-%d-%d-%04d.fit", outputDir,
			runId, camCol, rerun, field0);
	      if (shHdrWriteAsFits(hdr, fitsFile, STANDARD, 0, DEF_NONE,
				   NULL, NULL, NULL) != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't write object file PDU");
		  return SH_GENERIC_ERROR;
		}
	    }
	  shHdrDel(hdr);
	  if (outputDir == NULL)
	    sprintf(fitsFile, "tsField-%06d-%d-%d-%04d.fit", runId,
		    camCol, rerun, field0);
	  else
	    sprintf(fitsFile, "%s/tsField-%06d-%d-%d-%04d.fit", outputDir,
		    runId, camCol, rerun, field0);
	  if (shHdrWriteAsFits(fieldHdr, fitsFile, STANDARD, 0, DEF_NONE,
			       NULL, NULL, NULL) != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't write field file PDU");
	      return SH_GENERIC_ERROR;
	    }
	  shHdrDel(fieldHdr);
	}

      // Initialize field TBLCOL
      if ((fieldTblCol = tsFieldTblColInit((split ? 1 : nFields))) == NULL)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't initialize TBLCOL");
	  return SH_GENERIC_ERROR;
	}
    }
  
  // Initialize arrays of TRANS structures.
  TRANS astroTrans[nFilters];
  TRANS *astroTransPtr[nFilters];
  TRANS photoTrans[nFilters];
  TRANS *photoTransPtr[nFilters];
  for (int k = 0; k < nFilters; k++)
    {
      astroTransPtr[k] = &(astroTrans[k]);
      photoTransPtr[k] = &(photoTrans[k]);
    }

  // Get pointer to generic enums so we may decode integer to string
  GenericEnum *psfStatus;
  GenericEnum *quality;
  if (export > -1)
    {
      psfStatus = GenericEnum::Find("AR_PSF_STATUS");
      if (psfStatus == NULL)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't find generic enum 'AR_SF_STATUS'");
	  return SH_GENERIC_ERROR;
	}
      quality = GenericEnum::Find("arDataQuality");
      if (quality == NULL)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't find generic enum 'arDataQuality'");
	  return SH_GENERIC_ERROR;
	}
    }

  // Initialize C object structures
  TA_CALIB_OBJ cObj;
  TA_CALIB_OBJ *cObjPtr = &cObj;
  CHAIN *objChain;
  TA_FIELD_INFO fieldInfo;

  // Mode to open objects in
  ooMode mode;
  if (select && !noUpdate)
    mode = oocUpdate;
  else
    mode = oocRead;

  /*adjacent filter to use in colorterms*/
  const int adj[nFilters]  = {1, 2, 3, 4, 3};
  /* sign to use for color terms */
  const int sign[nFilters] = {1, 1, 1, 1, -1}; 

  // For each field ...
  printf("run %d, camCol %d, rerun %d\n", runId, camCol, rerun);
  int lastField = field0 + nFields - 1;
  for (int field = field0; field <= lastField; field++)
    {
      printf("%4d", field);
      fflush(stdout);

      // Get object list for this field
      ooHandle(arObjectList) objList = run->objectList(field);

      // Get the astrometric and photometric transformations for this field
      arFieldPTrans fieldPTrans = photom->ptrans(field);
      for (int k = 0; k < nFilters; k++)
	{
	  int ccd = camRow[k] * 10 + camCol;
	  astroTrans[k] = astrom->astrotoolsTrans(ccd, field);
	  photoTrans[k] = photoAstrom->astrotoolsTrans(ccd, field);
	  // Correct TRANS used by photo for rowOffset and colOffset
	  // values determined by photo
	  double rowOffset = objList->frameStat((arFilter)k).rowOffset();
	  double colOffset = objList->frameStat((arFilter)k).colOffset();
	  if (k == 2)
	    {
	      // r' is always the reference filter, and its rowOffset and
	      // colOffset should always be zero
	      shAssert(fabs(rowOffset) < 0.00001);
	      shAssert(fabs(colOffset) < 0.00001);
	    }
	  else
	    {
	      photoTrans[k].a +=
		(-photoTrans[k].b * rowOffset - photoTrans[k].c * colOffset);
	      photoTrans[k].d +=
		(-photoTrans[k].e * rowOffset - photoTrans[k].f * colOffset);
	    }
	}
      arFieldPhotoTrans psTrans = ps->trans(field);
      arFieldPhotoTrans2 psTrans2 = ps->trans2(field);

      if (select || export > 0)
	{
	  // Looking at the objects.
	  
	  // Convert photometric transformations to form callable by
	  // "target" routines.
	  TA_PTRANS ptrans;
	  ptrans.field = field;
	  for (k = 0; k < nFilters; k++)
	    {
	      arPTrans dbPTrans = fieldPTrans.filter((arFilter)k);
	      ptrans.a[k].val = dbPTrans.a().value();
	      ptrans.a[k].err = dbPTrans.a().err();
	      ptrans.b[k].val = dbPTrans.b().value();
	      ptrans.b[k].err = dbPTrans.b().err();
	      ptrans.c[k].val = dbPTrans.c().value();
	      ptrans.c[k].err = dbPTrans.c().err();
	      ptrans.k[k].val = dbPTrans.k().value();
	      ptrans.k[k].err = dbPTrans.k().err();
	      double calMag, calMagErr;
	      int caliStatus;
	      atLCaliApply(psTrans.filter((arFilter)k).flux20().value(), 0.,
			   -1000., -1000., expTime, expTime,
			   astroTrans[k].airmass, ptrans.a[k].val,
			   ptrans.k[k].val, 0., 0., sign[k], 0., 0.,
			   &calMag, &calMagErr, &caliStatus);
	      ptrans.dZero[k] = calMag - 20.;
	    }
      
	  // Initialize the field info structure for this field
	  fieldInfo.field = field;
	  fieldInfo.psp_status = psTrans2.psp_status();
	  for (k = 0; k < nFilters; k++)
	    {
	      fieldInfo.seeing[k] = psTrans.filter((arFilter)k).psf_width();
	      fieldInfo.mjd[k] = astroTrans[k].mjd;
	      fieldInfo.status[k] = psTrans2.status((arFilter)k);
	      fieldInfo.psf_ap_correctionErr[k] =
		psTrans2.psf_ap_correctionErr((arFilter)k);

	      // Must calibrate sky from DNs/pixel to mag/arcsec^2
	      double scale = at_deg2Asec * sqrt((double)(astroTrans[k].b *
							 astroTrans[k].f -
							 astroTrans[k].e *
							 astroTrans[k].c));
	      double scale2 = scale * scale;
	      double mag, magErr;
	      int status;
	      taLCaliApply(objList->frameStat((arFilter)k).sky_frames_sub() /
			   scale2, 0.,
			   objList->frameStat((arFilter)adj[k]).sky_frames_sub() /
			   scale2, 0.,
			   expTime, expTime, astroTrans[k].airmass,
			   ptrans.a[k].val, ptrans.k[k].val,
			   ptrans.b[k].val, ptrans.c[k].val, sign[k],
			   cosmicSkyColor[cosmicIdx[k]],
			   cosmicSkyScatter[cosmicIdx[k]], &mag, &magErr,
			   &status);
	      fieldInfo.sky_frames_sub[k] = mag;
	    }

	  // Create chain for calibrated objects for this field
	  if (export > 0) objChain = shChainNew("TA_CALIB_OBJ");
	  
	  // Initialize iterator over objects
	  ooItr(arObject) objItr;
	  if (objList->objects(objItr, mode) != oocSuccess)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't initialize iterator over objects");
	      if (export > 0) taGenericChainDestroy(objChain);
	      return SH_GENERIC_ERROR;
	    }

	  // For each object ...
	  int secondaries = (export == 3);
	  int firstField = -1;
	  if (gotSegment) firstField = (field == segField0);
	  int family = ((export > 1 && parents) ? 2 : 0);
	  while (objItr.next())
	    {
	      // Skip if this is not a primary object, or a secondary object if
	      // we're exporting secondary objects, or a parent if we're
	      // exporting parents.
	      if (export == 4)
		{
		  // Exporting everything, including objects outside segment
		  // limits (which isn't defined for BAD objects), so just pass
		  // all objects.
		}
	      else if (! tsGoodFamily(objItr, secondaries, firstField, family))
		{
		  continue;
		}

	      // If forcing a retargeting, then remove previous effects of
	      // target selection on primary objects.
	      int inSegment = 1;
	      if (gotSegment)
		{
		  inSegment = objItr->status() & AR_OBJECT_STATUS_FIRST_FIELD;
		  if (firstField == 0) inSegment = ! inSegment;
		}
	      if (gotSegment && select && inSegment && force &&
		  objItr->status() & AR_OBJECT_STATUS_PRIMARY)
		if (tsObjectTargetUnselect(objItr) != SH_SUCCESS)
		  return SH_GENERIC_ERROR;
	  
	      // If this is a MISSING or HOLE field, then skip the object
	      // now that we've potentially untargeted it
	      if (objList->quality() == AR_DQ_MISSING ||
		  objList->quality() == AR_DQ_HOLE)
		continue;

	      // Calibrate the object, transforming instrument quantities to
	      // calibrated quantities in the TA_CALIB_OBJ C structure.
	      if (tsObjCalibrate(objItr, cObjPtr, &ptrans,
				 (const TRANS **)astroTransPtr,
				 (const TRANS **)photoTransPtr,
				 node, incl, refFilter, expTime, 0)
		  != SH_SUCCESS)
		{
		  if (export > 0) taGenericChainDestroy(objChain);
		  return SH_GENERIC_ERROR;
		}
	      cObjPtr->field = &fieldInfo;
	      cObjPtr->run = runId;
	      cObjPtr->camCol = camCol;
	      cObjPtr->rerun = rerun;
	      cObjPtr->fieldId = field;
	  
	      // Select targets
	      if (select && inSegment &&
		  ((gotSegment && objItr->status() & AR_OBJECT_STATUS_PRIMARY)
		   || (!gotSegment &&
		       objItr->status() & AR_OBJECT_STATUS_OK_RUN)))
		{
		  // Set target selection flags on calibrated object
		  if (taTargetSelectFromObj(cObjPtr, galaxiesParams,
					    quasarsParams,
					    serendipityParams, rosatParams,
					    starsParams, standardsParams,
					    first, rosat, usno) != SH_SUCCESS)
		    {
		      if (export > 0) taGenericChainDestroy(objChain);
		      return SH_GENERIC_ERROR;
		    }

		  if (!noUpdate)
		    {
		      // Update the OPDB.
		      
		      // If matched a USNO source, set relevant fields.
		      if (cObjPtr->properMotionMatch != 0)
			{
			  uint16 dEpoch = (cObjPtr->properMotionDelta /
					   cObjPtr->properMotion)*36525. + 0.5;
			  objItr->setProperMotion(cObjPtr->properMotionDelta,
						  cObjPtr->properMotionAngle,
						  dEpoch,
						  cObjPtr->usnoBlue,
						  cObjPtr->usnoRed);
			}

		      // If matched a FIRST source, create the new composite
		      // FIRST source in the database and set link to it.
		      if (cObjPtr->firstMatch != 0)
			{
			  ooHandle(arFirst) firstObj = new(*firstCluster)
			    arFirst(objItr, cObjPtr->firstId,
				    cObjPtr->firstMatch,
				    cObjPtr->firstLambda, cObjPtr->firstEta,
				    cObjPtr->firstDelta, cObjPtr->firstPeak,
				    cObjPtr->firstInt, cObjPtr->firstrms,
				    cObjPtr->firstmajor, cObjPtr->firstminor,
				    cObjPtr->firstpa);
			  if (! firstObj.isValid())
			    {
			      shErrStackPush("tsSegmentCalibrate: couldn't create new composite FIRST object");
			      if (export > 0) taGenericChainDestroy(objChain);
			      return SH_GENERIC_ERROR;
			    }
			  firstObj.close();
			}

		      // If matched a ROSAT source, set link to it.
		      if (cObjPtr->rosatMatch != 0)
			{
			  // Set link to  matching ROSAT object.
			  ooItr(arRosat) rosatItr;
			  char predicate[80];
			  sprintf(predicate, "id_ == %d", cObjPtr->rosatMatch);
			  if (rosatItr.scan((*rosatCluster), oocNoOpen, oocAll,
					    predicate) != oocSuccess)
			    {
			      shErrStackPush("tsSegmentCalibrate: couldn't initialize iterator over ROSAT catalog");
			      if (export > 0) taGenericChainDestroy(objChain);
			      return SH_GENERIC_ERROR;
			    }
			  if (! rosatItr.next())
			    {
			      shErrStackPush("tsSegmentCalibrate: couldn't find ROSAT object");
			      if (export > 0) taGenericChainDestroy(objChain);
			      return SH_GENERIC_ERROR;
			    }
			  if (objItr->setRosat(rosatItr) != oocSuccess)
			    {
			      shErrStackPush("tsSegmentCalibrate: couldn't set link to ROSAT object");
			      if (export > 0) taGenericChainDestroy(objChain);
			      return SH_GENERIC_ERROR;
			    }
			  if (rosatItr.next())
			    {
			      shErrStackPush("tsSegmentCalibrate: more than one ROSAT object found");
			      if (export > 0) taGenericChainDestroy(objChain);
			      return SH_GENERIC_ERROR;
			    }
			}

		      // If any criteria matched, create a target.
		      if (cObjPtr->status & AR_OBJECT_STATUS_TARGET)
			{
			  objItr->setStatus(objItr->status() |
					    AR_OBJECT_STATUS_TARGET);
			  ooHandle(arTarget) target =
			    new(*targetCluster) arTarget(objItr,
							 cObjPtr->primTarget,
							 cObjPtr->secTarget,
							 cObjPtr->priority);
			  if (! target.isValid())
			    {
			      shErrStackPush("tsSegmentCalibrate: couldn't create new target");
			      if (export > 0) taGenericChainDestroy(objChain);
			      return SH_GENERIC_ERROR;
			    }
			  target.close();
			}
		    }
		}

	      // Append to exported chain of calibrated objects.
	      if (export >= 2 || 
		  (export == 1 && cObjPtr->status & AR_OBJECT_STATUS_TARGET))
		{
		  TA_CALIB_OBJ *cCopyPtr =
		    (TA_CALIB_OBJ*) shMalloc(sizeof(TA_CALIB_OBJ));
		  *cCopyPtr = *cObjPtr;
		  if (shChainElementAddByPos(objChain, cCopyPtr, "TA_CALIB_OBJ",
					     TAIL, AFTER)
		      != SH_SUCCESS)
		    {
		      shErrStackPush("tsSegmentCalibrate: couldn't append object chain");
		      taGenericChainDestroy(objChain);
		      shFree(cCopyPtr);
		      return SH_GENERIC_ERROR;
		    }
		}

	    } // end loop over objects
	}

      // Write out field info
      if (export > -1)
	{
	  // Copy field info to the field TBLCOL
	  int32 numStars[5];
	  numStars[0] = photom->numStar(field, AR_U);
	  numStars[1] = photom->numStar(field, AR_G);
	  numStars[2] = photom->numStar(field, AR_R);
	  numStars[3] = photom->numStar(field, AR_I);
	  numStars[4] = photom->numStar(field, AR_Z);
	  tsFieldTblColSet(fieldTblCol, (split ? 0 : field - field0),
			   field, expTime, fieldPTrans, numStars,
			   astroTrans[0], astroTrans[1],
			   astroTrans[2], astroTrans[3], astroTrans[4],
			   psTrans, psTrans2, objList);

	  // If one field file per field, write it out
	  if (split)
	    {
	      if (outputDir == NULL)
		sprintf(fitsFile, "tsField-%06d-%d-%d-%04d.fit", runId,
			camCol, rerun, field);
	      else
		sprintf(fitsFile, "%s/tsField-%06d-%d-%d-%04d.fit", outputDir,
			runId, camCol, rerun, field);
	      if (shHdrDelByKeyword(fieldHdr, "FIELD0") != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't delete header keyword");
		  return SH_GENERIC_ERROR;
		}
	      if (shHdrInsertInt(fieldHdr, "FIELD0", field,
				 "First field reduced.")
		  != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FIELD0");
		  return SH_GENERIC_ERROR;
		}
	      if (shHdrWriteAsFits(fieldHdr, fitsFile, STANDARD, 0, DEF_NONE,
				   NULL, NULL, NULL) != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't write object file PDU");
		  return SH_GENERIC_ERROR;
		}
	      HANDLE handle;
	      handle.ptr = (void *) fieldTblCol;
	      handle.type = shTypeGetFromName("TBLCOL");
	      if (shFitsWrite(&handle, (char *)fitsFile, DEF_NONE,
			      f_hduBINTABLE, SH_FITS_PDU_NONE, 1, STANDARD)
		  != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't write field file HDU");
		  return SH_GENERIC_ERROR;
		}
	    }
	}

      // Write the objects file
      if (export > 0)
	{
	  // Set file name
	  if (outputDir == NULL)
	    sprintf(fitsFile, "tsObj-%06d-%d-%d-%04d.fit", runId,
		    camCol, rerun, (split ? field : field0));
	  else
	    sprintf(fitsFile, "%s/tsObj-%06d-%d-%d-%04d.fit", outputDir,
		    runId, camCol, rerun, (split ? field : field0));

	  if (split)
	    {
	      // One file per field.  Write the PDU.
	      if (shHdrDelByKeyword(hdr, "FIELD0") != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't delete header keyword");
		  return SH_GENERIC_ERROR;
		}
	      if (shHdrInsertInt(hdr, "FIELD0", field, "First field reduced.")
		  != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't insert header keyword FIELD0");
		  return SH_GENERIC_ERROR;
		}
	      if (shHdrWriteAsFits(hdr, fitsFile, STANDARD, 0, DEF_NONE,
				   NULL, NULL, NULL) != SH_SUCCESS)
		{
		  shErrStackPush("tsSegmentCalibrate: couldn't write object file PDU");
		  return SH_GENERIC_ERROR;
		}
	    }

	  // Write the objects HDU
	  TBLCOL *tblCol;
	  if ((tblCol = taCalibObjsWriteToTbl(objChain)) == NULL)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't append object HDU");
	      return SH_GENERIC_ERROR;
	    }
	  taGenericChainDestroy(objChain);
	  shHdrInsertInt(&(tblCol->hdr), "FIELD", field,
			 "Field sequence number within the run");
	  shHdrInsertAscii(&(tblCol->hdr), "QUALITY",
			   &(quality->asString(objList->quality())[6]),
			   "Field quality");
	  const char *filterUp = "UGRIZ";
	  for (int k = 0; k < nFilters; k++)
	    {
	      char keyword[10];
	      sprintf(keyword, "SEEING_%c", filterUp[k]);
	      shHdrInsertDbl(&(tblCol->hdr), keyword, fieldInfo.seeing[k],
			     "FWHM (arcsecs)");
	      sprintf(keyword, "MJD_%c", filterUp[k]);
	      shHdrInsertDbl(&(tblCol->hdr), keyword, fieldInfo.mjd[k],
			     "MJD(TAI)");
	      sprintf(keyword, "PSFERR_%c", filterUp[k]);
	      shHdrInsertDbl(&(tblCol->hdr), keyword,
			     fieldInfo.psf_ap_correctionErr[k], "Photometric err due to imperfect PSF model(mag)");
	      sprintf(keyword, "STATUS_%c", filterUp[k]);
	      shHdrInsertAscii(&(tblCol->hdr), keyword,
			       &(psfStatus->asString(fieldInfo.status[k])[14]),
			       "PSF fit status on this frame");
	      sprintf(keyword, "SKY_%c", filterUp[k]);
	      shHdrInsertDbl(&(tblCol->hdr), keyword,
			     fieldInfo.sky_frames_sub[k], "Global sky value after obj sub (mag/arcsec^2)");
	    }
	  shHdrInsertAscii(&(tblCol->hdr), "PSP_STAT",
			   &(psfStatus->asString(fieldInfo.psp_status)[14]),
			   "Maximum of STATUS value on all 5 frames");
	  HANDLE handle;
	  handle.ptr = (void *) tblCol;
	  handle.type = shTypeGetFromName("TBLCOL");
	  if (shFitsWrite(&handle, (char *)fitsFile, DEF_NONE, f_hduBINTABLE,
			  SH_FITS_PDU_NONE, 1, STANDARD) != SH_SUCCESS)
	    {
	      shErrStackPush("tsSegmentCalibrate: couldn't append object HDU");
	      return SH_GENERIC_ERROR;
	    }
	  shTblcolDel(tblCol);
	}
    } // end loop over fields
  printf("\n");

  // Write out field info HDU
  if (export > -1 && ! split)
    {
      if (outputDir == NULL)
	sprintf(fitsFile, "tsField-%06d-%d-%d-%04d.fit", runId,
		camCol, rerun, field0);
      else
	sprintf(fitsFile, "%s/tsField-%06d-%d-%d-%04d.fit", outputDir,
		runId, camCol, rerun, field0);
      HANDLE handle;
      handle.ptr = (void *) fieldTblCol;
      handle.type = shTypeGetFromName("TBLCOL");
      if (shFitsWrite(&handle, (char *)fitsFile, DEF_NONE,
		      f_hduBINTABLE, SH_FITS_PDU_NONE, 1, STANDARD)
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsSegmentCalibrate: couldn't write field file HDU");
	  return SH_GENERIC_ERROR;
	}
    }

  // Clean up
  if (export > -1)
    {
      shTblcolDel(fieldTblCol);
      if (split)
	{
	  if (export > 0) shHdrDel(hdr);
	  shHdrDel(fieldHdr);
	}
    }
  
  return SH_SUCCESS;
}

/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	tsObjCalibrate
**
**<HTML>
**	Calibrate a database object, returning the calibrated object as a
**	TA_CALIB_OBJ.  
**	The argument "trans" is a pointer to a 5-element array of pointers to
**	TRANS structures, in the order u, g, r, i, z.  These are the current
**	'best' astrometric transformations.  The argument "transPhoto" is
**	the TRANS structures used when photo was run, corrected using
**	photo's "rowOffset" and "colOffset", and are required to
**	reproduce the position offsets in each filter.
**	For DCR corrections in astrometric transformations, PSF magnitudes are
**	used for stars and model magnitudes for galaxies.
**	<p>
**	If "raw" is set to 1, then lengths, magnitudes, surface brightnesses
**	and angles are not calibrated.  Celestial coordinates and extinction
**	values are still calculated, however.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
RET_CODE
tsObjCalibrate(const ooHandle(arObject)& in, /* IN: database object */
	       TA_CALIB_OBJ *out,      /* MOD: Calibrate C structure copy */
	       const TA_PTRANS *ptrans,/* IN:  Photometric calibration */
	       const TRANS *trans[5],  /* IN:  Astrometric calibration(ugriz)*/
	       const TRANS *transPhoto[5], /* IN:  astrometric calibration
					    *      used by photo, corrected
					    *      for photo's determined
					    *      rowOffset and colOffset
					    *      (ugriz)*/
	       double node,	       /* IN:  GC ascending node (degrees) */
	       double incl,	       /* IN:  GC inclination (degrees) */
	       char refFilter,	       /* IN:  Ref. filter (primary position)*/
	       double expTime,	       /* IN:  Exposure time (seconds) */
	       int raw                 /* IN:  1=don't calibrate lengths,
					*      magnitudes, surface brightesses,
					*      and angles; 0=do */)
{ 
  // Set all fields with instrumental quantities

  // Not setting field.
  ooHandle(arObject) parent;
  parent = in->parent(parent);
  if (parent.isValid())
    out->parent = parent->id();
  else
    out->parent = -1;
  out->id = in->id();
  out->nchild = in->nChild();
  out->objc_type = in->objc_type();
  out->catId = in->catID();
  out->objc_flags = in->objc_flags();
  out->objc_flags2 = in->objc_flags2();
  out->objc_rowc.val = in->objc_rowc().value();
  out->objc_rowc.err = in->objc_rowc().err();
  out->objc_colc.val = in->objc_colc().value();
  out->objc_colc.err = in->objc_colc().err();
  out->rowv.val = in->rowv().value();
  out->rowv.err = in->rowv().err();
  out->colv.val = in->colv().value();
  out->colv.err = in->colv().err();
  out->status = in->status();
  ooHandle(arObjectData) data;
  data = in->data(data);

  // Loop though each detection/filter.
  for (int i = 0; i < 5; i++)
    {
      arDetection d = data->detection((arFilter)i);
      out->detection[i].rowc.val = d.rowc().value();
      out->detection[i].rowc.err = d.rowc().err();
      out->detection[i].colc.val = d.colc().value();
      out->detection[i].colc.err = d.colc().err();
      out->detection[i].sky.val = d.sky().value();
      out->detection[i].sky.err = d.sky().err();
      out->detection[i].psfCounts.val = in->psfCounts((arFilter)i).value();
      out->detection[i].psfCounts.err = in->psfCounts((arFilter)i).err();
      out->detection[i].fiberCounts.val = d.fiberCounts().value();
      out->detection[i].fiberCounts.err = d.fiberCounts().err();
      out->detection[i].petroCounts.val = in->petroCounts((arFilter)i).value();
      out->detection[i].petroCounts.err = in->petroCounts((arFilter)i).err();
      out->detection[i].petroRad.val = d.petroRad().value();
      out->detection[i].petroRad.err = d.petroRad().err();
      out->detection[i].petroR50.val = d.petroR50().value();
      out->detection[i].petroR50.err = d.petroR50().err();
      out->detection[i].petroR90.val = d.petroR90().value();
      out->detection[i].petroR90.err = d.petroR90().err();
      out->detection[i].q.val = d.q().value();
      out->detection[i].q.err = d.q().err();
      out->detection[i].u.val = d.u().value();
      out->detection[i].u.err = d.u().err();
      out->detection[i].iso_rowc.val = d.iso_rowc().value();
      out->detection[i].iso_rowc.err = d.iso_rowc().err();
      out->detection[i].iso_rowc.grad = d.iso_rowcGrad();
      out->detection[i].iso_colc.val = d.iso_colc().value();
      out->detection[i].iso_colc.err = d.iso_colc().err();
      out->detection[i].iso_colc.grad = d.iso_colcGrad();
      out->detection[i].iso_a.val = d.iso_a().value();
      out->detection[i].iso_a.err = d.iso_a().err();
      out->detection[i].iso_a.grad = d.iso_aGrad();
      out->detection[i].iso_b.val = d.iso_b().value();
      out->detection[i].iso_b.err = d.iso_b().err();
      out->detection[i].iso_b.grad = d.iso_bGrad();
      out->detection[i].iso_phi.val = d.iso_phi().value();
      out->detection[i].iso_phi.err = d.iso_phi().err();
      out->detection[i].iso_phi.grad = d.iso_phiGrad();
      out->detection[i].r_deV.val = d.r_deV().value();
      out->detection[i].r_deV.err = d.r_deV().err();
      out->detection[i].ab_deV.val = d.ab_deV().value();
      out->detection[i].ab_deV.err = d.ab_deV().err();
      out->detection[i].phi_deV.val = d.phi_deV().value();
      out->detection[i].phi_deV.err = d.phi_deV().err();
      out->detection[i].counts_deV.val = d.counts_deV().value();
      out->detection[i].counts_deV.err = d.counts_deV().err();
      out->detection[i].r_exp.val = d.r_exp().value();
      out->detection[i].r_exp.err = d.r_exp().err();
      out->detection[i].ab_exp.val = d.ab_exp().value();
      out->detection[i].ab_exp.err = d.ab_exp().err();
      out->detection[i].phi_exp.val = d.phi_exp().value();
      out->detection[i].phi_exp.err = d.phi_exp().err();
      out->detection[i].counts_exp.val = d.counts_exp().value();
      out->detection[i].counts_exp.err = d.counts_exp().err();
      out->detection[i].counts_model.val =
	in->counts_model((arFilter)i).value();
      out->detection[i].counts_model.err =
	in->counts_model((arFilter)i).err();
      out->detection[i].texture = d.texture();
      out->detection[i].star_L = d.star_L();
      out->detection[i].exp_L = d.exp_L();
      out->detection[i].deV_L = d.deV_L();
      out->detection[i].fracPSF = d.fracPSF();
      out->detection[i].flags = d.flags();
      out->detection[i].flags2 = d.flags2();
      out->detection[i].type = d.type();
      out->detection[i].nProf = d.nRadialBins();
      for (int j = 0; j < out->detection[i].nProf; j++)
	{
	  arFloatErr bin = d.radialBin(j);
	  out->detection[i].profile[j].val = bin.value();
	  out->detection[i].profile[j].err = bin.err();
	}
    }

  // Calibrate the C copy.
  if (taObjCalibrate(out, ptrans, trans, transPhoto, node, incl, refFilter,
		     expTime, raw)
      != SH_SUCCESS)
    return SH_GENERIC_ERROR;

  // Is this a target?
  ooHandle(arTarget) target;
  target = in->spectroscopicTarget(target);
  if (target.isValid())
    {
      out->primTarget = target->primaryType();
      out->secTarget = target->secondaryType();
      out->priority = target->priority();
    }
  else
    {
      out->primTarget = 0;
      out->secTarget = 0;
      out->priority = 0;
    }

  // Did it match a USNO object?
  if (in->dEpoch() > 0)
    {
      out->properMotionMatch = 1;
      out->properMotionDelta = in->pm();
      out->properMotion = in->pm() / (in->dEpoch() / 36525.);
      out->properMotionAngle = in->pmAngle();
      out->usnoBlue = in->blue();
      out->usnoRed = in->red();
    }
  else
    {
      out->properMotionMatch = 0;
      out->properMotionDelta = 0.0;
      out->properMotion = 0.0;
      out->properMotionAngle = 0.0;
      out->usnoBlue = 0.0;
      out->usnoRed = 0.0;
    }

  // Does this match a FIRST object?
  ooHandle(arFirst) first;
  first = in->first(first);
  if (first.isValid())
    {
      out->firstMatch = first->nSources();
      out->firstId = first->id();
      out->firstLambda = first->coord().longitude();
      out->firstEta = first->coord().latitude();
      out->firstDelta = first->delta();
      out->firstPeak = first->peak();
      out->firstInt = first->integrated();
      out->firstrms = first->rms();
      out->firstmajor = first->major();
      out->firstminor = first->minor();
      out->firstpa = first->pa();
    }
  else
    {
      out->firstMatch = 0;
      out->firstId = 0;
      out->firstLambda = 0;
      out->firstEta = 0;
      out->firstDelta = 0;
      out->firstPeak = 0;
      out->firstInt = 0;
      out->firstrms = 0;
      out->firstmajor = 0;
      out->firstminor = 0;
      out->firstpa = 0;
    }

  // Does this match a ROSAT object?
  ooHandle(arRosat) rosat;
  rosat = in->rosat(rosat);
  if (rosat.isValid())
    {
      out->rosatMatch = rosat->id();
      out->rosatDelta =
	rosat->coord().distance(arSphericalCoord(out->lambda, out->eta));
      out->rosatPosErr = rosat->posErr();
      out->rosatCPS.val = rosat->cps().value();
      out->rosatCPS.err = rosat->cps().err();
      out->rosatHR1.val = rosat->hr1().value();
      out->rosatHR1.err = rosat->hr1().err();
      out->rosatHR2.val = rosat->hr2().value();
      out->rosatHR2.err = rosat->hr2().err();
      out->rosatExt = rosat->extent();
      out->rosatExtLike = rosat->extentLike();
      out->rosatDetectLike = rosat->detectLike();
      out->rosatExposure = rosat->exptime();
    }
  else
    {
      out->rosatMatch = 0;
      out->rosatDelta = 0;
      out->rosatPosErr = 0;
      out->rosatCPS.val = 0;
      out->rosatCPS.err = 0;
      out->rosatHR1.val = 0;
      out->rosatHR1.err = 0;
      out->rosatHR2.val = 0;
      out->rosatHR2.err = 0;
      out->rosatExt = 0;
      out->rosatExtLike = 0;
      out->rosatDetectLike = 0;
      out->rosatExposure = 0;
    }

  // Array of matching object IDs (for primaries and secondaries only).
  if (in->status() & AR_OBJECT_STATUS_PRIMARY ||
      in->status() & AR_OBJECT_STATUS_SECONDARY)
    {
      int j = 0;
      ooItr(arObject) matches;
      if (in->matches(matches) != oocSuccess) return SH_GENERIC_ERROR;
      while (matches.next())
	{
	  if (j > 9) return SH_GENERIC_ERROR;
	  ooHandle(arObjectCont) objectCont;
	  matches->cont(objectCont);
	  ooHandle(arObjectList) objectList;
	  objectCont->objectList(objectList);
	  ooHandle(arFramesPipeRun) fpr;
	  objectList->framesPipelineRun(fpr);
	  out->matchId[j][0] = fpr->run();
	  out->matchId[j][1] = fpr->camCol();
	  out->matchId[j][2] = fpr->rerun();
	  out->matchId[j][3] = objectList->field();
	  out->matchId[j][4] = matches->id();
	  j++;
	}
      while (j < 10)
	{
	  out->matchId[j][0] = 0;
	  out->matchId[j][1] = 0;
	  out->matchId[j][2] = 0;
	  out->matchId[j][3] = 0;
	  out->matchId[j][4] = 0;
	  j++;
	}
    }
  else
    {
      for (int j = 0; j < 10; j++)
	for (int k = 0; k < 5; k++)
	  out->matchId[j][k] = 0;
    }

  return SH_SUCCESS;
}

/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	tsSegmentTargetUnselect
**
**<HTML>
**	Delete targets attached to all primary objects in a segment.  Remove
**	links to all associated ROSAT, FIRST, and USNO objects (and delete
**	the FIRST composite object).  Thus, this undoes target selection
**	on a segment.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
RET_CODE
tsSegmentTargetUnselect(ooHandle(arSegment)& segment /* IN: segment */)
{
  // Get handle to Frames Pipeline Run for this segment
  ooHandle(arFramesPipeRun) run = segment->framesPipelineRun(oocRead);
  uint32 runId = run->run();
  uint8 camCol = run->camCol();
  uint16 rerun= run->rerun();

  // Get first field and number of fields in the segment
  uint16 field0 = segment->field0();
  uint16 nFields = segment->nFields();

  // Remove links to astrometric and photometric calibrations used
  segment->delTargetAstrometricCalibration();
  segment->delTargetPhotometricCalibration();

  // For each field ...
  printf("run %d, camCol %d, rerun %d\n", runId, camCol, rerun);
  int lastField = field0 + nFields - 1;
  for (int field = field0; field <= lastField; field++)
    {
      printf("%4d", field);
      fflush(stdout);

      // Get object list for this field
      ooHandle(arObjectList) objList = run->objectList(field);

      // Initialize iterator over objects
      ooItr(arObject) objItr;
      if (objList->objects(objItr, oocUpdate) != oocSuccess)
	{
	  shErrStackPush("tsSegmentTargetUnselect: couldn't initialize iterator over objects");
	  return SH_GENERIC_ERROR;
	}

      // For each object ...
      while (objItr.next())
	{
	  // Skip if this is not a primary object (only primaries are
	  // targetted).
	  if (! (objItr->status() & AR_OBJECT_STATUS_PRIMARY))
	    continue;

	  // Skip if object doesn't belong to the segment
	  if (field == field0)
	    {
	      if (! (objItr->status() & AR_OBJECT_STATUS_FIRST_FIELD))
		continue;
	    }
	  else if (field == lastField)
	    {
	      if (objItr->status() & AR_OBJECT_STATUS_FIRST_FIELD)
		continue;
	    }

	  // Object is a primary belonging to the segment.  Untarget it.
	  if (tsObjectTargetUnselect(objItr) != SH_SUCCESS)
	    return SH_GENERIC_ERROR;
	} // end loop over objects
    } // end loop over fields
  printf("\n");

  return SH_SUCCESS;
}

/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	tsObjectTargetUnselect
**
**<HTML>
**	Unset everything on an object that may have been set by target
**	selection.  That is, make the object appear as it did before
**	target selection, including removing
**	links to all associated ROSAT, FIRST, and USNO objects (and delete
**	the FIRST composite object).
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
RET_CODE
tsObjectTargetUnselect(ooHandle(arObject)& obj /* MOD: object */)
{
  // Unset USNO match
  if (obj->dEpoch() > 0) obj->setProperMotion(0., 0., 0, 0., 0.);

  // Remove FIRST match (and FIRST composite object)
  ooHandle(arFirst) firstSource;
  obj->first(firstSource);
  if (firstSource.isValid()) ooDelete(firstSource);

  // Remove ROSAT match
  ooHandle(arRosat) rosatSource;
  obj->rosat(rosatSource);
  if (rosatSource.isValid()) obj->delRosat();

  // Remove target flags and target object
  if (obj->status() & AR_OBJECT_STATUS_TARGET)
    {
      ooHandle(arTarget) target;
      obj->spectroscopicTarget(target, oocUpdate);
      if (! target.isValid())
	{
	  shErrStackPush("tsSegmentTargetUnselect: invalid old target");
	  return SH_GENERIC_ERROR;
	}
      ooItr(arTiledTarget) tiles;
      if (target->tiledTargets(tiles) != oocSuccess)
	{
	  shErrStackPush("tsSegmentTargetUnselect: couldn't initialize iterator over tiledTargets");
	  return SH_GENERIC_ERROR;
	}
      if (tiles.next())
	{
	  shErrStackPush("tsSegmentTargetUnselect: can't reforce target selection, as some old targets are assigned to tiles");
	  return SH_GENERIC_ERROR;
	}
      tiles.close();
      ooDelete(target);
      obj->setStatus(obj->status() & (~ AR_OBJECT_STATUS_TARGET));
    }
  return SH_SUCCESS;
}

// Define all fields in the tsField TBLCOL
static const int nTblFields = 89;
static const int nTblFilters = 5;
static const int nMaxProfiles = 15;
typedef struct tsField
{
  char *name;
  int dimCnt;
  int dim[2];
  char *type;
  char *units;
} TS_FIELD;
static TS_FIELD fields[nTblFields] = {
  {"field", 0, {0, 0}, "INT", ""},
  {"quality", 0, {0, 0}, "INT", ""},
  {"a", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"b", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"c", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"d", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"e", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"f", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dRow0", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dRow1", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dRow2", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dRow3", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dCol0", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dCol1", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dCol2", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"dCol3", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"csRow", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"csCol", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"ccRow", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"ccCol", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"riCut", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"mjd", 1, {nTblFilters, 0}, "DOUBLE", "MJD(TAI)"},
  {"airmass", 1, {nTblFilters, 0}, "DOUBLE", ""},
  {"seqId", 0, {0, 0}, "INT", ""},
  {"numStars", 1, {nTblFilters, 0}, "INT", ""},
  {"aa", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"aaErr", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"bb", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"bbErr", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"cc", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"ccErr", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"kk", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"kkErr", 1, {nTblFilters, 0}, "FLOAT", "mag"},
  {"frames_status", 0, {0, 0}, "INT", ""},
  {"rowOffset", 1, {nTblFilters, 0}, "FLOAT", "pixels"},
  {"colOffset", 1, {nTblFilters, 0}, "FLOAT", "pixels"},
  {"nObjects", 0, {0, 0}, "INT", ""},
  {"nChild", 0, {0, 0}, "INT", ""},
  {"nStars", 0, {0, 0}, "INT", ""},
  {"nGals", 0, {0, 0}, "INT", ""},
  {"saturation_level", 1, {nTblFilters, 0}, "INT", ""},
  {"neff_psf", 1, {nTblFilters, 0}, "FLOAT", "pixels"},
  {"sky_psp", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"sky_frames", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"sky_frames_sub", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"sigPix", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"deV_ap_correction", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"deV_ap_correctionErr", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"exp_ap_correction", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"exp_ap_correctionErr", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"deV_model_ap_correction", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"deV_model_ap_correctionErr", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"exp_model_ap_correction", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"exp_model_ap_correctionErr", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"nCR", 1, {nTblFilters, 0}, "INT", ""},
  {"nBrightObj", 1, {nTblFilters, 0}, "INT", ""},
  {"nFaintObj", 1, {nTblFilters, 0}, "INT", ""},
  {"median_fiberColor", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"median_psfColor", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"q", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"u", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psp_status", 0, {0, 0}, "INT", ""},
  {"status", 1, {nTblFilters, 0}, "INT", ""},
  {"sky", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"skySig", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"skyErr", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2"},
  {"skySlope", 1, {nTblFilters, 0}, "FLOAT", "luptitudes/arcsec^2/field"},
  {"lbias", 1, {nTblFilters, 0}, "FLOAT", "ADUs x DSCALE"},
  {"rbias", 1, {nTblFilters, 0}, "FLOAT", "ADUs x DSCALE"},
  {"psf_nstar", 1, {nTblFilters, 0}, "INT", ""},
  {"psf_ap_correctionErr", 1, {nTblFilters, 0}, "FLOAT", "mags"},
  {"psf_sigma1", 1, {nTblFilters, 0}, "FLOAT", "arcsec"},
  {"psf_sigma2", 1, {nTblFilters, 0}, "FLOAT", "arcsec"},
  {"psf_b", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psf_p0", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psf_beta", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psf_sigmap", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psf_width", 1, {nTblFilters, 0}, "FLOAT", "arcsec"},
  {"psf_psfCounts", 1, {nTblFilters, 0}, "FLOAT", "counts"},
  {"psf_sigma1_2G", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psf_sigma2_2G", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psf_b_2G", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"psfCounts", 1, {nTblFilters, 0}, "FLOAT", ""},
  {"prof_nprof", 1, {nTblFilters, 0}, "INT", ""},
  {"prof_mean", 2, {nTblFilters, nMaxProfiles}, "FLOAT", ""},
  {"prof_med", 2, {nTblFilters, nMaxProfiles}, "FLOAT", ""},
  {"prof_sig", 2, {nTblFilters, nMaxProfiles}, "FLOAT", ""},
  {"gain", 1, {nTblFilters, 0}, "FLOAT", "electrons/DN"},
  {"dark_variance", 1, {nTblFilters, 0}, "FLOAT", ""},
};

enum fieldNames
{
  FIELD,
  QUALITY,
  A,
  B,
  C,
  D,
  E,
  F,
  DROW0,
  DROW1,
  DROW2,
  DROW3,
  DCOL0,
  DCOL1,
  DCOL2,
  DCOL3,
  CSROW,
  CSCOL,
  CCROW,
  CCCOL,
  RICUT,
  MJD,
  AIRMASS,
  SEQID,
  NUMSTARS,
  AA,
  AAERR,
  BB,
  BBERR,
  CC,
  CCERR,
  KK,
  KKERR,
  FRAMES_STATUS,
  ROWOFFSET,
  COLOFFSET,
  NOBJECTS,
  NCHILD,
  NSTARS,
  NGALS,
  SATURATION_LEVEL,
  NEFF_PSF,
  SKY_PSP,
  SKY_FRAMES,
  SKY_FRAMES_SUB,
  SIGPIX,
  DEV_AP_CORRECTION,
  DEV_AP_CORRECTIONERR,
  EXP_AP_CORRECTION,
  EXP_AP_CORRECTIONERR,
  DEV_MODEL_AP_CORRECTION,
  DEV_MODEL_AP_CORRECTIONERR,
  EXP_MODEL_AP_CORRECTION,
  EXP_MODEL_AP_CORRECTIONERR,
  NCR,
  NBRIGHTOBJ,
  NFAINTOBJ,
  MEDIAN_FIBERCOLOR,
  MEDIAN_PSFCOLOR,
  Q,
  U,
  PSP_STATUS,
  STATUS,
  SKY,
  SKYSIG,
  SKYERR,
  SKYSLOPE,
  LBIAS,
  RBIAS,
  PSF_NSTAR,
  PSF_AP_CORRECTIONERR,
  PSF_SIGMA1,
  PSF_SIGMA2,
  PSF_B,
  PSF_P0,
  PSF_BETA,
  PSF_SIGMAP,
  PSF_WIDTH,
  PSF_PSFCOUNTS,
  PSF_SIGMA1_2G,
  PSF_SIGMA2_2G,
  PSF_B_2G,
  PSFCOUNTS,
  PROF_NPROF,
  PROF_MEAN,
  PROF_MED,
  PROF_SIG,
  GAIN,
  DARK_VARIANCE
};
#define ioff(field,idx) (((int*)(array[(field)]->arrayPtr))[(idx)])
#define i2off(field,idx1,idx2) (((int**)(array[(field)]->arrayPtr))[(idx1)][(idx2)])
#define f2off(field,idx1,idx2) (((float**)(array[(field)]->arrayPtr))[(idx1)][(idx2)])
#define f3off(field,idx1,idx2,idx3) (((float***)(array[(field)]->arrayPtr))[(idx1)][(idx2)][(idx3)])
#define d2off(field,idx1,idx2) (((double**)(array[(field)]->arrayPtr))[(idx1)][(idx2)])

/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	tsFieldTblColInit
**
**<HTML>
**	Create a TBLCOL for a "tsField" file, containing all information
**	for a single field that is to be export to the SX.  The number of
**	rows in the TBLCOL is specified by the argument "nRows".
**	Returns a pointer to the newly created TBLCOL, or NULL on error.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
TBLCOL*
tsFieldTblColInit(int nRows  /* number of rows to allocate */)
{
  // Create the TBLCOL
  TBLCOL *tblCol = NULL;
  if (shTblColCreate(nRows, nTblFields, 0, &tblCol) != SH_SUCCESS)
    {
      shErrStackPush("tsFieldTblColInit: couldn't create TBLCOL");
      return NULL;
    }

  // Set up each field in the TBLCOL, saving its array pointer.
  ARRAY* array[nTblFields];
  for (int fldIdx = 0; fldIdx < nTblFields; fldIdx++)
    {
      // Locate the field
      if (shTblFldLoc(tblCol, fldIdx, NULL, 0, SH_CASE_INSENSITIVE,
		      &(array[fldIdx]), NULL, NULL) != SH_SUCCESS)
	{
	  shErrStackPush("tsFieldTblColInit: couldn't create TBLFLD");
	  shTblcolDel(tblCol);
	  return NULL;
	}

      // Set the array dimensions
      for (int dimIdx = 0; dimIdx < fields[fldIdx].dimCnt; dimIdx++)
	{
	  array[fldIdx]->dim[array[fldIdx]->dimCnt++] =
	    fields[fldIdx].dim[dimIdx];
	}

      // Allocate storage for the array
      if (shArrayDataAlloc(array[fldIdx],fields[fldIdx].type) != SH_SUCCESS)
	{
	  shErrStackPush("tsFieldTblColInit: couldn't allocate storage");
	  shTblcolDel(tblCol);
	  return NULL;
	}
      if (shTblTBLFLDsetWithAscii(array[fldIdx], SH_TBL_TTYPE,
				  fields[fldIdx].name, (TBLFLD**) 0)
	  != SH_SUCCESS)
	{
	  shErrStackPush("tsFieldTblColInit: couldn't set field name");
	  shTblcolDel(tblCol);
	  return NULL;
	}
      if (strlen(fields[fldIdx].units) > 0)
	if (shTblTBLFLDsetWithAscii(array[fldIdx], SH_TBL_TUNIT,
				    fields[fldIdx].units, (TBLFLD**) 0)
	    != SH_SUCCESS)
	  {
	    shErrStackPush("tsFieldTblColInit: couldn't set units");
	    shTblcolDel(tblCol);
	    return NULL;
	  }
    }

  // Return pointer to the TBLCOL
  return tblCol;
}

/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	tsFieldTblColSet
**
**<HTML>
**	Set a row in a "tsField" TBLCOL from the database data for that field.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
RET_CODE
tsFieldTblColSet(TBLCOL *tblCol,    /* MOD: TBLCOL to set */
		 int row,           /* IN:  row in TBLCOL to set */
		 int field,         /* IN:  field number */
		 double expTime,    /* IN:  exposure time (sec) */
		 const arFieldPTrans& fieldPTrans,
		 /* IN: photometric calibration for the field */
		 const int32 *numStars,/* IN: number of stars matched by fcalib,
					*     (order ugriz).  KLUDGE: these
					*     values should be contained in the
					*     previous argument, but schema
					*     evolution bug forces this kludge*/
		 const TRANS& uTrans,
		 /* IN: u astrometric transformation for the field */
		 const TRANS& gTrans,
		 /* IN: g astrometric transformation for the field */
		 const TRANS& rTrans,
		 /* IN: r astrometric transformation for the field */
		 const TRANS& iTrans,
		 /* IN: i astrometric transformation for the field */
		 const TRANS& zTrans,
		 /* IN: z astrometric transformation for the field */
		 const arFieldPhotoTrans psTrans,
		 /* IN: PSP output for the field */
		 const arFieldPhotoTrans2 psTrans2,
		 /* IN: extra PSP output for the field */
		 const ooHandle(arObjectList)& objList
		 /* IN: object list for the field */
		 )
{
  // Fetch pointers to each field in the TBLCOL.
  ARRAY* array[nTblFields];
  for (int fldIdx = 0; fldIdx < nTblFields; fldIdx++)
    {
      if (shTblFldLoc(tblCol, fldIdx, NULL, 0, SH_CASE_INSENSITIVE,
		      &(array[fldIdx]), NULL, NULL) != SH_SUCCESS)
	{
	  shErrStackPush("tsFieldTblColInit: couldn't create TBLFLD");
	  shTblcolDel(tblCol);
	  return SH_GENERIC_ERROR;
	}
    }

  // Create array of pointers to trans structures, stricly for convenience.
  TRANS *transPtr[5];
  transPtr[0] = (TRANS *) &uTrans;
  transPtr[1] = (TRANS *) &gTrans;
  transPtr[2] = (TRANS *) &rTrans;
  transPtr[3] = (TRANS *) &iTrans;
  transPtr[4] = (TRANS *) &zTrans;

  /*adjacent filter to use in colorterms*/
  const int adj[TA_NFILTERS]  = {1, 2, 3, 4, 3};
  /* sign to use for color terms */
  const int sign[TA_NFILTERS] = {1, 1, 1, 1, -1}; 

  // Fill in the TBLCOL row
  ioff(FIELD,row) = field;
  ioff(QUALITY,row) = objList->quality();
  ioff(SEQID,row) = fieldPTrans.seqId();
  ioff(FRAMES_STATUS,row) = objList->status();
  ioff(NOBJECTS,row) = objList->nObjects();
  ioff(NCHILD,row) = objList->nChild();
  ioff(NSTARS,row) = objList->nStars();
  ioff(NGALS,row) = objList->nGals();
  ioff(PSP_STATUS,row) = psTrans2.psp_status();

  // For each filter ...
  for (int k = 0; k < nTblFilters; k++)
    {
      // Astrometric transformations
      d2off(A,row,k) = transPtr[k]->a;
      d2off(B,row,k) = transPtr[k]->b;
      d2off(C,row,k) = transPtr[k]->c;
      d2off(D,row,k) = transPtr[k]->d;
      d2off(E,row,k) = transPtr[k]->e;
      d2off(F,row,k) = transPtr[k]->f;
      d2off(DROW0,row,k) = transPtr[k]->dRow0;
      d2off(DROW1,row,k) = transPtr[k]->dRow1;
      d2off(DROW2,row,k) = transPtr[k]->dRow2;
      d2off(DROW3,row,k) = transPtr[k]->dRow3;
      d2off(DCOL0,row,k) = transPtr[k]->dCol0;
      d2off(DCOL1,row,k) = transPtr[k]->dCol1;
      d2off(DCOL2,row,k) = transPtr[k]->dCol2;
      d2off(DCOL3,row,k) = transPtr[k]->dCol3;
      d2off(CSROW,row,k) = transPtr[k]->csRow;
      d2off(CSCOL,row,k) = transPtr[k]->csCol;
      d2off(CCROW,row,k) = transPtr[k]->ccRow;
      d2off(CCCOL,row,k) = transPtr[k]->ccCol;
      d2off(RICUT,row,k) = transPtr[k]->riCut;
      d2off(MJD,row,k) = transPtr[k]->mjd;
      d2off(AIRMASS,row,k) = transPtr[k]->airmass;

      // Photometric calibrations
      i2off(NUMSTARS,row,k) = numStars[k];
      arPTrans ptrans = fieldPTrans.filter((arFilter)k);
      f2off(AA,row,k) = ptrans.a().value();
      f2off(AAERR,row,k) = ptrans.a().err();
      f2off(BB,row,k) = ptrans.b().value();
      f2off(BBERR,row,k) = ptrans.b().err();
      f2off(CC,row,k) = ptrans.c().value();
      f2off(CCERR,row,k) = ptrans.c().err();
      f2off(KK,row,k) = ptrans.k().value();
      f2off(KKERR,row,k) = ptrans.k().err();

      /* Scale calibrations (pixels -> arcsecs) */
      double scale =
	at_deg2Asec * sqrt((double)(transPtr[k]->b * transPtr[k]->f -
				    transPtr[k]->e * transPtr[k]->c));
      double scale2 = scale * scale;

      // fpFieldStat info
      arFrameStat frameStat = objList->frameStat((arFilter)k);
      arFrameStat adjFrameStat = objList->frameStat((arFilter)adj[k]);
      f2off(ROWOFFSET,row,k) = frameStat.rowOffset();
      f2off(COLOFFSET,row,k) = frameStat.colOffset();
      i2off(SATURATION_LEVEL,row,k) = frameStat.saturation_level();
      f2off(NEFF_PSF,row,k) = frameStat.neff_psf();
      int c = cosmicIdx[k];
      int status;
      double mag, magErr;
      double aa = ptrans.a().value();
      double bb = ptrans.b().value();
      double cc = ptrans.c().value();
      double kk = ptrans.k().value();
      taLCaliApply(frameStat.sky_psp() / scale2, 0.,
		   adjFrameStat.sky_psp() / scale2, 0.,
		   expTime, expTime, transPtr[k]->airmass,
		   aa, kk, bb, cc, sign[k],
		   cosmicSkyColor[c], cosmicSkyScatter[c],
		   &mag, &magErr, &status);
      f2off(SKY_PSP,row,k) = mag;
      taLCaliApply(frameStat.sky_frames() / scale2, 0.,
		   adjFrameStat.sky_frames() / scale2, 0.,
		   expTime, expTime, transPtr[k]->airmass,
		   aa, kk, bb, cc, sign[k],
		   cosmicSkyColor[c], cosmicSkyScatter[c],
		   &mag, &magErr, &status);
      f2off(SKY_FRAMES,row,k) = mag;
      taLCaliApply(frameStat.sky_frames_sub() / scale2, 0.,
		   adjFrameStat.sky_frames_sub() / scale2, 0.,
		   expTime, expTime, transPtr[k]->airmass,
		   aa, kk, bb, cc, sign[k],
		   cosmicSkyColor[c], cosmicSkyScatter[c],
		   &mag, &magErr, &status);
      f2off(SKY_FRAMES_SUB,row,k) = mag;
      f2off(SIGPIX,row,k) =
	(2.5/log(10.0) ) * (frameStat.sigPix() / frameStat.sky_frames());
      f2off(DEV_AP_CORRECTION,row,k) =
	frameStat.deV_ap_correction().value();
      f2off(DEV_AP_CORRECTIONERR,row,k) =
	frameStat.deV_ap_correction().err();
      f2off(EXP_AP_CORRECTION,row,k) =
	frameStat.exp_ap_correction().value();
      f2off(EXP_AP_CORRECTIONERR,row,k) =
	frameStat.exp_ap_correction().err();
      f2off(DEV_MODEL_AP_CORRECTION,row,k) =
	frameStat.deV_model_ap_correction().value();
      f2off(DEV_MODEL_AP_CORRECTIONERR,row,k) =
	frameStat.deV_model_ap_correction().err();
      f2off(EXP_MODEL_AP_CORRECTION,row,k) =
	frameStat.exp_model_ap_correction().value();
      f2off(EXP_MODEL_AP_CORRECTIONERR,row,k) =
	frameStat.exp_model_ap_correction().err();
      i2off(NCR,row,k) = frameStat.nCR();
      i2off(NBRIGHTOBJ,row,k) = frameStat.nBrightObj();
      i2off(NFAINTOBJ,row,k) = frameStat.nFaintObj();
      f2off(MEDIAN_FIBERCOLOR,row,k) = frameStat.median_fiberColor();
      f2off(MEDIAN_PSFCOLOR,row,k) = frameStat.median_psfColor();
      f2off(Q,row,k) = frameStat.q();
      f2off(U,row,k) = frameStat.u();

      // psField info
      arPhotoTrans pt = psTrans.filter((arFilter)k);
      arPhotoTrans adjPt = psTrans.filter((arFilter)adj[k]);
      i2off(STATUS,row,k) = psTrans2.status((arFilter)k);
      taLCaliApply(pt.sky().value() / scale2, pt.sky().err() / scale2,
		   adjPt.sky().value() / scale2, adjPt.sky().err() / scale2,
		   expTime, expTime, transPtr[k]->airmass,
		   aa, kk, bb, cc, sign[k],
		   cosmicSkyColor[c], cosmicSkyScatter[c],
		   &mag, &magErr, &status);
      f2off(SKY,row,k) = mag;
      f2off(SKYERR,row,k) = magErr;
      f2off(SKYSIG,row,k) =
	(2.5/log(10.0) ) * (pt.skySig() / pt.sky().value());
      taLCaliApply(pt.skySlope() / scale2, 0.,
		   adjPt.skySlope() / scale2, 0.,
		   expTime, expTime, transPtr[k]->airmass,
		   aa, kk, bb, cc, sign[k],
		   cosmicSkyColor[c], cosmicSkyScatter[c],
		   &mag, &magErr, &status);
      f2off(SKYSLOPE,row,k) = mag;
      f2off(LBIAS,row,k) = pt.lbias();
      f2off(RBIAS,row,k) = pt.rbias();
      i2off(PSF_NSTAR,row,k) = pt.psf_nstar();
      f2off(PSF_AP_CORRECTIONERR,row,k) =
	psTrans2.psf_ap_correctionErr((arFilter)k);
      f2off(PSF_SIGMA1,row,k) = pt.psf_sigma1() * scale;
      f2off(PSF_SIGMA2,row,k) = pt.psf_sigma2() * scale;
      f2off(PSF_B,row,k) = pt.psf_b();
      f2off(PSF_P0,row,k) = pt.psf_p0();
      f2off(PSF_BETA,row,k) = pt.psf_beta();
      f2off(PSF_SIGMAP,row,k) = pt.psf_sigmap();
      f2off(PSF_WIDTH,row,k) = pt.psf_width();
      f2off(PSF_PSFCOUNTS,row,k) = pt.psf_psfCounts();
      f2off(PSF_SIGMA1_2G,row,k) = pt.psf_sigma1_2G();
      f2off(PSF_SIGMA2_2G,row,k) = pt.psf_sigma2_2G();
      f2off(PSF_B_2G,row,k) = pt.psf_b_2G();
      f2off(PSFCOUNTS,row,k) = pt.psfCounts();
      i2off(PROF_NPROF,row,k) = pt.prof_nprof();
      for (int j = 0; j < pt.prof_nprof(); j++)
	{
	  f3off(PROF_MEAN,row,k,j) = pt.psfBin(j).value();
	  f3off(PROF_SIG,row,k,j) = pt.psfBin(j).err();
	  f3off(PROF_MED,row,k,j) = pt.prof_med(j);
	}
      f2off(GAIN,row,k) = pt.gain();
      f2off(DARK_VARIANCE,row,k) = pt.dark_variance();
    }
  return SH_SUCCESS;
}
