/*****************************************************************************
******************************************************************************
**<AUTO>
**
** FILE:	merge.cc
**
**<HTML>
**	This file contains functions to merge, unmerge, and generate
**	matching statistics.
**</HTML>
**</AUTO>
**
**
** ENVIRONMENT:
**	C++.
**
** REQUIRED PRODUCTS:
**	photoSch
**
******************************************************************************
******************************************************************************
*/

#include <arProgram.h>
#include <arCCDProgram.h>
#include <arDetectionFlag.h>
#include <arObjectStatus.h>
#include <arObjectType.h>
#include <arObject.h>
#include <arObjectList.h>
#include <arObjectCont.h>
#include <arFramesPipeRun.h>
#include <arAstromCalib.h>
#include <arFinalPhotoCalib.h>
#include <arRun.h>
#include "tsMatchStats.h"
#include "tsMerge.h"
#include <dervish.h>
extern "C" {
#include <slalib.h>
}
#include <astrotools.h>
#include <taCalibObj.h>
#include <taCosmic.h>
#include "tsTarget.h"

// Forward declarations
static int cmpRow (const void *d1, const void *d2);
int tsObjArrayBinomialSearch(double row, TSOBJ *array, int nObjects);
static int cmpMu (const void *d1, const void *d2);
int tsCalibObjArrayBinomialSearch(double mu, TSCALIBOBJ *array, int nObjects);
int tsCalibObjArrayCorrelatedSearch(double mu, TSCALIBOBJ *array, int nObjects,
				    int guess);

// Number of filters
const int nFilters = 5;
const int nColors = 4;

// Luptitude
const double luptitudeA = -2.5 / log(10.0);
const double luptitudeB = log(2.0 / LUPTITUDE_SCALE);
double lasinh(double x) {
  return log(x + sqrt(1 + x * x));
}
double luptitude(double x)
{
  return luptitudeA * (lasinh(x / LUPTITUDE_SCALE) - luptitudeB);
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsFieldUnsetObjectStatus
**
**<HTML>
**	Unset all object status bits for all objects in a single
**	field of a Frames Pipeline Run.  This also removes all links for all
**	objects in the field to other objects belonging to the run.  It does
**	not remove links to objects in other Frames Pipeline Runs.  Returns
**	SH_SUCCESS if successful, SH_GENERIC_ERROR if an error occurred.
**</HTML>
**</AUTO>
**============================================================================
*/
RET_CODE
tsFieldUnsetObjectStatus(ooHandle(arObjectList)& field
			 /* MODIFIED: the object list for a field */)
{
  // Initialize the iterator over objects
  ooItr(arObject) itr;
  if (field->objects(itr, oocUpdate) == oocError)
    return SH_GENERIC_ERROR;

  // For each object ...
  while (itr.next())
    {
      // Object has duplicates only if DUPLICATE flag is set.  If it is, then
      // remove the links to the duplicates.
      if (itr->status() & AR_OBJECT_STATUS_DUPLICATE)
	if (itr->delDuplicates() == oocError)
	  return SH_GENERIC_ERROR;
      
      // Set the object status to undetermined.
      itr->setStatus(0);
    }

  // Done
  return SH_SUCCESS;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsFieldUnmerge
**
**<HTML>
**	Remove links from all objects in a single field of a Frames Pipeline
**	Run to objects in other Frames Pipeline Runs.  It does not remove links
**	to objects in the same Frames Pipeline Run.  Returns SH_SUCCESS if
**	successful, SH_GENERIC_ERROR if an error occurs.
**</HTML>
**
**</AUTO>
**============================================================================
*/
RET_CODE
tsFieldUnmerge(ooHandle(arObjectList)& field  /* MODIFIED: the object list for a field */)
{
  // Initialize the iterator over objects
  ooItr(arObject) itr;
  if (field->objects(itr, oocUpdate) == oocError)
    return SH_GENERIC_ERROR;

  // For each object ...
  while (itr.next())
    {
      // Only OK_RUN objects were matched, so skip if not OK_RUN.
      // Remove all matches.
      if (itr->status() & AR_OBJECT_STATUS_OK_RUN)
	if (itr->delMatches() == oocError)
	  return SH_GENERIC_ERROR;
    }
  
  // Done
  return SH_SUCCESS;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsMatchStatsInit
**
**<HTML>
**	Initialize a TSMATCHSTATS structure, setting all fields to 0.
**</HTML>
**
**</AUTO>
**============================================================================
*/
void
tsMatchStatsInit(TSMATCHSTATS *stats /* MODIFIED: matching statistics */)
{
  // Initialize the statistics
  stats->nOverlap = 0;
  stats->nOverlapBright = 0;
  stats->nMismatches = 0;
  stats->nMismatchesBright = 0;
  stats->nUnknown = 0;
  stats->nUnknownBright = 0;
  stats->stars.nObjects = 0;
  stats->stars.nObjectsBright = 0;
  stats->stars.nMatches = 0;
  stats->stars.nMatchesBright = 0;
  stats->stars.row.av = 0.;
  stats->stars.row.rms = 0.;
  stats->stars.row.nPairs = 0.;
  stats->stars.col.av = 0.;
  stats->stars.col.rms = 0.;
  stats->stars.col.nPairs = 0.;
  for (int i = 0; i < nFilters; i++)
    {
      stats->stars.mag[i].av = 0;
      stats->stars.mag[i].rms = 0;
      stats->stars.mag[i].nPairs = 0;
    }
  for (i = 0; i < nColors; i++)
    {
      stats->stars.color[i].av = 0;
      stats->stars.color[i].rms = 0;
      stats->stars.color[i].nPairs = 0;
    }
  stats->galaxies.nObjects = 0;
  stats->galaxies.nObjectsBright = 0;
  stats->galaxies.nMatches = 0;
  stats->galaxies.nMatchesBright = 0;
  stats->galaxies.row.av = 0.;
  stats->galaxies.row.rms = 0.;
  stats->galaxies.row.nPairs = 0.;
  stats->galaxies.col.av = 0.;
  stats->galaxies.col.rms = 0.;
  stats->galaxies.col.nPairs = 0.;
  for (i = 0; i < nFilters; i++)
    {
      stats->galaxies.mag[i].av = 0;
      stats->galaxies.mag[i].rms = 0;
      stats->galaxies.mag[i].nPairs = 0.;
    }
  for (i = 0; i < nColors; i++)
    {
      stats->galaxies.color[i].av = 0;
      stats->galaxies.color[i].rms = 0;
      stats->galaxies.color[i].nPairs = 0;
    }
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsFieldMatchStatsByPixels
**
**<HTML>
**	Calculate matching statistics between two adjacent fields in the same
**	run.  Detailed statistics are returned
**	in an argument.  Objects in the first "field" are assumed to be
**      located at lower row number, by exactly "rowsPerField" rows, compared
**	to objects in the "other" field.  This is typically used to compare
**	adjacent fields within a run, where the field number of "field" is
**	always one larger than the field number for "other".
**	The returned statistics give the row and
**	column statistics in pixels, and the uncalibrated PSF magnitude
**	statistics as magnitudes.
**</HTML>
**
**</AUTO>
**============================================================================
*/
RET_CODE
tsFieldMatchStatsByPixel(const ooHandle(arObjectList)& field
			 /* IN: the object list for a field */,
			 const ooHandle(arObjectList)& other
			 /* IN: object list for the field to match against */,
			 int rowsPerField,
			 /* number of non-overlap rows per field */
			 double nSigma   /* IN: Compile matching statistics for
					  *     nSigma detections (PSF, r') */,
			 TSMATCHSTATS *stats
			 /* MODIFIED: matching statistics */,
			 int noBlends /* IN: don't included blended object */)
{
  // Allocate handles for an iterator for, and the containing container
  // for, matching objects.
  ooItr(arObject) matches;
  ooHandle(ooContObj) contH;
  ooHandle(arObjectCont) otherContH;
  otherContH = other->objectCont(otherContH);

  // Initialize the statistics
  tsMatchStatsInit(stats);

  // If both fields are the same, then there will be no matches (the
  // calling procedure does this to compile the non-matching stats for the
  // first field in the run).
  int noMatches = (field == other);

  // Initialize the iterator over objects
  ooItr(arObject) itr;
  if (field->objects(itr, oocReadOnly) == oocError)
    return SH_GENERIC_ERROR;

  // For each object ...
  while (itr.next())
    {
      // Count only GOOD objects
      int status = itr->status();
      if (! (status & AR_OBJECT_STATUS_GOOD))
	continue;

      // Count only stars, galaxies, and unknown
      arObjectType objType = itr->objc_type();
      TSSTATS *matchStats;
      arFloatErr counts;
      if (objType == AR_OBJECT_TYPE_STAR)
	{
	  matchStats = &stats->stars;
	  matchStats->nObjects++;
	  counts = itr->psfCounts(AR_R);
	}
      else if (objType == AR_OBJECT_TYPE_GALAXY)
	{
	  matchStats = &stats->galaxies;
	  matchStats->nObjects++;
	  counts = itr->petroCounts(AR_R);
	}
      else if (objType == AR_OBJECT_TYPE_UNK)
	{
	  matchStats = NULL;
	  stats->nUnknown++;
	  counts = itr->psfCounts(AR_R);
	}
      else
	continue;

      // Is this a "bright" object?
      int bright = (counts.value() > nSigma * counts.err() &&
		    counts.err() > 0.);
      if (bright)
	{
	  if (objType == AR_OBJECT_TYPE_UNK)
	    stats->nUnknownBright++;
	  else
	    matchStats->nObjectsBright++;
	}

      // If not a DUPLICATE object then there are no matching objects so
      // skip the matching test.
      if (! (status & AR_OBJECT_STATUS_DUPLICATE))
	{
	  if (! (status & AR_OBJECT_STATUS_OK_RUN))
	    {
	      stats->nOverlap++;
	      if (bright) stats->nOverlapBright++;
	    }
	  continue;
	}

      // Skip remaining if unknown, as we don't compile matching stats for
      // type unknown.
      if (objType == AR_OBJECT_TYPE_UNK) continue;

      // If no matches possible skip to next object.
      if (noMatches) continue;

      // Skip object if blended and we're skipping blends
      if (noBlends)
	if (itr->objc_flags() & AR_DFLAG_BLENDED ||
	    itr->objc_flags() & AR_DFLAG_CHILD)
	  continue;

      // Initialize iterator over all matching objects in adjacent fields.
      if (itr->duplicates(matches, oocNoOpen) == oocError)
	return SH_GENERIC_ERROR;

      // For each matching object ...
      while (matches.next())
	{
	  // Fetch the container for this object.
	  matches.containedIn(contH);

	  // If the matching object is in the other field, increment
	  // the matching statistics.
	  if (contH == otherContH)
	    {
	      matchStats->nMatches++;
	      if (objType != matches->objc_type())
		{
		  stats->nMismatches++;
		  if (bright) stats->nMismatchesBright++;
		}

	      // Add to statistics only if object is "bright" (at least an
	      // nSigma detection).
	      if (bright)
		{
		  matchStats->nMatchesBright++;
		  double rowc = itr->objc_rowc().value() + rowsPerField -
		    matches->objc_rowc().value();
		  double colc = itr->objc_colc().value() -
		    matches->objc_colc().value();
		  matchStats->row.av += rowc;
		  matchStats->row.rms += rowc * rowc;
		  matchStats->row.nPairs++;
		  matchStats->col.av += colc;
		  matchStats->col.rms += colc * colc;
		  matchStats->col.nPairs++;
		  arFloatErr bModel1, bModel2;
		  for (int i = 0; i < nFilters; i++)
		    {
		      arFloatErr counts1, counts2, model1, model2;
		      if (objType == AR_OBJECT_TYPE_STAR)
			{
			  // Use PSF magnitudes for stars
			  counts1 = itr->psfCounts((arFilter)i);
			  counts2 = matches->psfCounts((arFilter)i);
			}
		      else
			{
			  // Use petrosian magnitudes for galaxies
			  counts1 = itr->petroCounts((arFilter)i);
			  counts2 = matches->petroCounts((arFilter)i);
			}
		      if (counts1.err() > 0 && counts2.err() > 0)
			{
			  double magDiff = luptitude(counts1.value()) -
			    luptitude(counts2.value());
			  matchStats->mag[i].av += magDiff;
			  matchStats->mag[i].rms += magDiff * magDiff;
			  matchStats->mag[i].nPairs++;
			}

		      // Update color stats
		      model1 = itr->counts_model((arFilter)i);
		      model2 = matches->counts_model((arFilter)i);
		      if (i > 0)
			if (model1.err() > 0 && model2.err() > 0 &&
			    bModel1.err() > 0 && bModel2.err() > 0)
			  {
			    double magDiff =
			      (luptitude(bModel1.value()) -
			       luptitude(model1.value())) -
			      (luptitude(bModel2.value()) -
			       luptitude(model2.value()));
			    matchStats->color[i-1].av += magDiff;
			    matchStats->color[i-1].rms += magDiff * magDiff;
			    matchStats->color[i-1].nPairs++;
			  }
		      bModel1 = model1;
		      bModel2 = model2;
		    }
		}
	    }
	}
    }

  // Final star statistics
  if (stats->stars.row.nPairs > 0)
    {
      stats->stars.row.av /= stats->stars.row.nPairs;
      double meanSq = stats->stars.row.rms / stats->stars.row.nPairs -
	stats->stars.row.av * stats->stars.row.av;
      stats->stars.row.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  if (stats->stars.col.nPairs > 0)
    {
      stats->stars.col.av /= stats->stars.col.nPairs;
      double meanSq = stats->stars.col.rms / stats->stars.col.nPairs -
	stats->stars.col.av * stats->stars.col.av;
      stats->stars.col.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  for (int i = 0; i < nFilters; i++)
    if (stats->stars.mag[i].nPairs > 0)
      {
	stats->stars.mag[i].av /= stats->stars.mag[i].nPairs;
	double meanSq = stats->stars.mag[i].rms / stats->stars.mag[i].nPairs -
	  stats->stars.mag[i].av * stats->stars.mag[i].av;
	stats->stars.mag[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }
  for (i = 0; i < nColors; i++)
    if (stats->stars.color[i].nPairs > 0)
      {
	stats->stars.color[i].av /= stats->stars.color[i].nPairs;
	double meanSq =
	  stats->stars.color[i].rms / stats->stars.color[i].nPairs -
	  stats->stars.color[i].av * stats->stars.color[i].av;
	stats->stars.color[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }

  // Final galaxy statistics
  if (stats->galaxies.row.nPairs > 0)
    {
      stats->galaxies.row.av /= stats->galaxies.row.nPairs;
      double meanSq = stats->galaxies.row.rms / stats->galaxies.row.nPairs -
	stats->galaxies.row.av * stats->galaxies.row.av;
      stats->galaxies.row.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  if (stats->galaxies.col.nPairs > 0)
    {
      stats->galaxies.col.av /= stats->galaxies.col.nPairs;
      double meanSq = stats->galaxies.col.rms / stats->galaxies.col.nPairs -
	stats->galaxies.col.av * stats->galaxies.col.av;
      stats->galaxies.col.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  for (i = 0; i < nFilters; i++)
    if (stats->galaxies.mag[i].nPairs > 0)
      {
	stats->galaxies.mag[i].av /= stats->galaxies.mag[i].nPairs;
	double meanSq =
	  stats->galaxies.mag[i].rms / stats->galaxies.mag[i].nPairs -
	  stats->galaxies.mag[i].av * stats->galaxies.mag[i].av;
	stats->galaxies.mag[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }
  for (i = 0; i < nColors; i++)
    if (stats->galaxies.color[i].nPairs > 0)
      {
	stats->galaxies.color[i].av /= stats->galaxies.color[i].nPairs;
	double meanSq =
	  stats->galaxies.color[i].rms / stats->galaxies.color[i].nPairs -
	  stats->galaxies.color[i].av * stats->galaxies.color[i].av;
	stats->galaxies.color[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }

  // Return the number of good matches.
  return SH_SUCCESS;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsFieldMatchStatsByArcsec
**
**<HTML>
**	Calculate matching statistics between a field and another Frames
**	Pipeline run (specified as a handle to the database containing its
**	objects).  
**	Detailed statistics are returned in an argument.  The astrometric
**	and photometric calibrations to use for both the field and the other
**	run are specified as arguments.  The row and column differences (in
**	arcsecs) are in calibrated great circle coordinates, where the
**	coordinate system of the input field is used.  Only GOOD objects
**	of class STAR or GALAXY are used, with separate statistics
**	calculated for stars and galaxies.  PSF magnitudes are used for
**	stars, petrosian magnitudes for galaxies.  If "noBlends" is set to 1,
**	then blended object (DFLAG_BLEND || DFLAG_CHILD) are not included in
**	the matching statistics or chains.
**</HTML>
**
**</AUTO>
**============================================================================
*/
RET_CODE
tsFieldMatchStatsByArcsec(const ooHandle(arObjectList)& field
			  /* IN: the object list for a field */,
			  const ooHandle(arAstromCalib)& fieldCalib
			  /* IN: the astrometric calibration to use for the
			     field */,
			  const ooHandle(arFinalPhotoCalib)& fieldFinal
			  /* IN: the final photometric calibration to use to
			     the field */,
			  double fieldExpTime
			  /* IN: exposure time for the field (seconds) */,
			  const ooHandle(ooDBObj)& other
			  /* IN: database with objects from other run */,
			  uint8 otherCamCol
			  /* IN: camera column for the other run */,
			  const ooHandle(arAstromCalib)& otherCalib
			  /* IN: the astrometric calibration to use for the
			     other run */,
			  const ooHandle(arFinalPhotoCalib)& otherFinal
			  /* IN: the final photometric calibration to use to
			     the other run */,
			  double otherExpTime
			  /* IN: exposure time for the other run (seconds) */,
			  double minMag /* IN: minimum mag for "BRIGHT" objs*/,
			  double maxMag /* IN: maximum mag for "BRIGHT" objs*/,
			  char magFilter /* IN: filter for min/maxMag(ugriz)*/,
			  TSMATCHSTATS *stats
			  /* MODIFIED: matching statistics */,
			  CHAIN *starChain /* MOD: matched stars chain (NULL to
					    *      ignore) */,
			  CHAIN *galaxyChain /* MOD: matched galaxies chain
					      *      (NULL to ignore) */,
			  int full /* IN: 0=mag and position only for object
				    *     chains, 1=full objects */,
			  int noBlends /* IN: don't included blended object */
			  )
{
  /*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}; 

  // Get mag filter index
  int fil;
  switch (magFilter)
    {
    case 'u':
      fil = 0;
      break;
    case 'g':
      fil = 1;
      break;
    case 'r':
      fil = 2;
      break;
    case 'i':
      fil = 3;
      break;
    case 'z':
      fil = 4;
      break;
    default:
      shErrStackPush("tsFieldMatchStatsByArcsec: illegal magFilter");
      return (SH_GENERIC_ERROR);
    }

  // Check that magnitude range specified correctly.
  if (minMag > maxMag)
    {
      double tmp = minMag;
      minMag = maxMag;
      maxMag = tmp;
    }

  // Allocate handles for an iterator for, and the containing container
  // for, matching objects.
  ooItr(arObject) matches;
  ooHandle(ooContObj) contH;
  ooHandle(ooDBObj) dbH;

  // Initialize the statistics
  tsMatchStatsInit(stats);

  // All Frames Pipeline Runs are required when stuffed to have used r' as
  // their reference filter.  Thus, we always want the TRANS structure for
  // the r' CCD.
  int refCamRow = 1;
  int camCol = field->framesPipelineRun()->camCol();
  int ccd = 10 * refCamRow + camCol;
  int runId = field->framesPipelineRun()->run();
  int rerun = field->framesPipelineRun()->rerun();
  int otherCcd = 10 * refCamRow + otherCamCol;

  // 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};

  // Fetch the calibrations for this field.
  TRANS trans = fieldCalib->astrotoolsTrans(ccd, field->field());
  arFieldPTrans ptrans = fieldFinal->ptrans(field->field());
  double node = fieldCalib->node();
  double incl = fieldCalib->incl();
  double otherNode = otherCalib->node();
  double otherIncl = otherCalib->incl();
  TRANS trans1[5];
  TRANS *transPtr1[5];
  TRANS photoTrans1[5];
  TRANS *photoTransPtr1[5];
  TA_PTRANS ptrans1;
  if (full)
    {
      // Get astrometric calibrations used when photo ran
      ooHandle(arAstromCalib) photoAstrom=
	field->framesPipelineRun()->astrometricCalibration();
      shAssert(fabs(photoAstrom->node() - node) < 0.0001);
      shAssert(fabs(photoAstrom->incl() - incl) < 0.0001);

      // Set up astrometric transformations in each filter.
      for (int kk = 0; kk < 5; kk++)
	{
	  trans1[kk] =
	    fieldCalib->astrotoolsTrans(camRow[kk]*10+camCol, field->field());
	  transPtr1[kk] = &(trans1[kk]);
	  photoTrans1[kk] =
	    photoAstrom->astrotoolsTrans(camRow[kk]*10+camCol, field->field());
	  photoTransPtr1[kk] = &(photoTrans1[kk]);

	  // Correct TRANS used by photo for rowOffset and
	  // colOffset values determined by photo
	  double rowOffset = field->frameStat((arFilter)kk).rowOffset();
	  double colOffset = field->frameStat((arFilter)kk).colOffset();
	  if (kk == 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
	    {
	      photoTrans1[kk].a += (-photoTrans1[kk].b * rowOffset -
				    photoTrans1[kk].c * colOffset);
	      photoTrans1[kk].d += (-photoTrans1[kk].e * rowOffset -
				    photoTrans1[kk].f * colOffset);
	    }

	  // Get photometric transformations in each filter
	  arPTrans dbPTrans1 = ptrans.filter((arFilter)kk);
	  ptrans1.a[kk].val = dbPTrans1.a().value();
	  ptrans1.a[kk].err = dbPTrans1.a().err();
	  ptrans1.b[kk].val = dbPTrans1.b().value();
	  ptrans1.b[kk].err = dbPTrans1.b().err();
	  ptrans1.c[kk].val = dbPTrans1.c().value();
	  ptrans1.c[kk].err = dbPTrans1.c().err();
	  ptrans1.k[kk].val = dbPTrans1.k().value();
	  ptrans1.k[kk].err = dbPTrans1.k().err();
	}
    }

  // Initialize the iterator over objects
  ooItr(arObject) itr;
  if (field->objects(itr, oocReadOnly) == oocError)
    return SH_GENERIC_ERROR;

  // For each object ...
  while (itr.next())
    {
      // Count only GOOD objects
      int status = itr->status();
      if (! (status & AR_OBJECT_STATUS_GOOD))
	continue;

      // Skip edge objects
      if (itr->objc_flags() & AR_DFLAG_EDGE)
	continue;

      // Count only stars, galaxies, and unknown
      arObjectType objType = itr->objc_type();
      TSSTATS *matchStats;
      arFloatErr counts, adjCounts;
      CHAIN *pairChain = NULL;
      if (objType == AR_OBJECT_TYPE_STAR)
	{
	  matchStats = &stats->stars;
	  matchStats->nObjects++;
	  // Use PSF magnitudes for stars
	  counts = itr->psfCounts((arFilter)fil);
	  adjCounts = itr->psfCounts((arFilter)adj[fil]);
	  pairChain = starChain;
	}
      else if (objType == AR_OBJECT_TYPE_GALAXY)
	{
	  matchStats = &stats->galaxies;
	  matchStats->nObjects++;
	  // Use petrosian magnitudes for galaxies
	  counts = itr->petroCounts((arFilter)fil);
	  adjCounts = itr->petroCounts((arFilter)adj[fil]);
	  pairChain = galaxyChain;
	}
      else if (objType == AR_OBJECT_TYPE_UNK)
	{
	  stats->nUnknown++;
	  // Use PSF magnitudes for unknown
	  counts = itr->psfCounts((arFilter)fil);
	  adjCounts = itr->psfCounts((arFilter)adj[fil]);
	}
      else
	continue;

      // Is this a "bright" object?
      int bright = 0;
      if (counts.value() > 0 && counts.err() > 0)
	{
	  double mag, magErr;
	  int tStatus;
	  arPTrans p = ptrans.filter((arFilter)fil);
	  taLCaliApply(counts.value(), counts.err(), adjCounts.value(),
		       adjCounts.err(),
		       fieldExpTime, fieldExpTime, trans.airmass,
		       p.a().value(), p.k().value(),
		       p.b().value(), p.c().value(), sign[fil],
		       cosmicColor[cosmicIdx[fil]],
		       cosmicScatter[cosmicIdx[fil]],
		       &mag, &magErr, &tStatus);
	  if (mag >= minMag && mag <= maxMag) bright = 1;
	}
      if (bright)
	{
	  if (objType == AR_OBJECT_TYPE_UNK)
	    stats->nUnknownBright++;
	  else
	    matchStats->nObjectsBright++;
	}

      // Only OK_RUN objects were matched, so skip if this object isn't OK_RUN.
      if (! (status & AR_OBJECT_STATUS_OK_RUN))
	{
	  if (! (status & AR_OBJECT_STATUS_DUPLICATE))
	    {
	      stats->nOverlap++;
	      if (bright) stats->nOverlapBright++;
	    }
	  continue;
	}

      // Skip remaining if unknown, as we don't compile matching stats for
      // type unknown.
      if (objType == AR_OBJECT_TYPE_UNK) continue;

      // Skip object if blended and we're skipping blends
      if (noBlends)
	if (itr->objc_flags() & AR_DFLAG_BLENDED ||
	    itr->objc_flags() & AR_DFLAG_CHILD)
	  continue;

      // Initialize iterator over all matching objects.
      if (itr->matches(matches, oocNoOpen) == oocError)
	return SH_GENERIC_ERROR;
      int first = 1;

      // For each matching object ...
      while (matches.next())
	{
	  double mu1;
	  double nu1;

	  // Fetch the container and database for this object.
	  matches.containedIn(contH);
	  contH.containedIn(dbH);

	  // If the matching object is in the other database, increment
	  // the matching statistics.
	  if (dbH == other)
	    {
	      // Skip edge objects
	      if (matches->objc_flags() & AR_DFLAG_EDGE)
		continue;
	      
	      matchStats->nMatches++;
	      if (objType != matches->objc_type())
		{
		  stats->nMismatches++;
		  if (bright) stats->nMismatchesBright++;
		}

	      // Add to statistics only if object is "bright".
	      if (bright)
		{
		  matchStats->nMatchesBright++;

		  // Fetch calibrated great circle coordinates for target
		  // object, if we haven't already.
		  if (first)
		    {
		      first = 0;
		      arSphericalCoord coord1 = itr->gcCoord(trans, &ptrans);
		      mu1 = coord1.longitude();
		      nu1 = coord1.latitude();
		      atBound(&mu1, 0., 360.);
		    }
		      
		  // Fetch calibrated great circle coordinates for matching
		  // object.
		  ooHandle(arObjectList) otherFieldH =
		    ((ooHandle(arObjectCont)&)contH)->objectList();
		  int otherField = otherFieldH->field();
		  ooHandle(arFramesPipeRun) otherFPR =
		    otherFieldH->framesPipelineRun();
		  int otherRun = otherFPR->run();
		  int otherRerun = otherFPR->rerun();
		  TRANS otherTrans = otherCalib->astrotoolsTrans(otherCcd,
								 otherField);
		  arFieldPTrans otherPTrans = otherFinal->ptrans(otherField);
		  arSphericalCoord coord2 =
		    matches->gcCoord(otherTrans, &otherPTrans);
		  double mu2 = coord2.longitude();
		  atBound(&mu2, 0., 360.);
		  double nu2 = coord2.latitude();

		  // Convert other coordinates to target run great circle
		  // coordinates.
		  double ra, dec;
		  atGCToEq(mu2, nu2, &ra, &dec, otherNode, otherIncl);
		  atEqToGC(ra, dec, &mu2, &nu2, node, incl);
		  atBound(&mu2, 0., 360.);

		  // Increment position statistics
		  double rowDiff = mu1 - mu2;
		  if (rowDiff > 180.)
		    rowDiff -= 360.;
		  else if (rowDiff < -180)
		    rowDiff += 360.;
		  rowDiff *= at_deg2Asec;
		  double colDiff = (nu1 - nu2) * at_deg2Asec;
		  matchStats->row.av += rowDiff;
		  matchStats->row.rms += rowDiff * rowDiff;
		  matchStats->row.nPairs++;
		  matchStats->col.av += colDiff;
		  matchStats->col.rms += colDiff * colDiff;
		  matchStats->col.nPairs++;

		  // Add individual pair to output pair chain
		  TSMATCHEDPAIR *pair;
		  if (pairChain != NULL)
		    {
		      pair = (TSMATCHEDPAIR*) shMalloc(sizeof(TSMATCHEDPAIR));
		      pair->objc_flags[0] = itr->objc_flags();
		      pair->objc_flags[1] = matches->objc_flags();
		      pair->objc_flags2[0] = itr->objc_flags2();
		      pair->objc_flags2[1] = matches->objc_flags2();
		      if (shChainElementAddByPos(pairChain, pair,
						 "TSMATCHEDPAIR",
						 TAIL, AFTER)
			  != SH_SUCCESS)
			{
			  shErrStackPush("tsFieldMatchStatsByArcsec: couldn't append object chain");
			  shFree(pair);
			  return SH_GENERIC_ERROR;
			}
		      if (full)
			{
			  pair->obj1 =
			    (TA_CALIB_OBJ*) shMalloc(sizeof(TA_CALIB_OBJ));
			  pair->obj2 =
			    (TA_CALIB_OBJ*) shMalloc(sizeof(TA_CALIB_OBJ));

			  // Get astrometric calibration used when photo ran
			  // in the other field
			  ooHandle(arAstromCalib) otherPhotoAstrom =
			    otherFPR->astrometricCalibration();
			  shAssert(fabs(otherPhotoAstrom->node() - otherNode) < 0.0001);
			  shAssert(fabs(otherPhotoAstrom->incl() - otherIncl) < 0.0001);
			  TRANS trans2[5];
			  TRANS *transPtr2[5];
			  TRANS photoTrans2[5];
			  TRANS *photoTransPtr2[5];
			  TA_PTRANS ptrans2;
			  for (int kk = 0; kk < 5; kk++)
			    {
			      // Get astrometric calibrations in other field
			      trans2[kk] =
				otherCalib->astrotoolsTrans(camRow[kk]*10 +
							    otherCamCol,
							    otherField);
			      transPtr2[kk] = &(trans2[kk]);
			      photoTrans2[kk] =
				otherPhotoAstrom->astrotoolsTrans(camRow[kk]*10
								  +otherCamCol,
								  otherField);
			      photoTransPtr2[kk] = &(photoTrans2[kk]);
			      // Correct TRANS used by photo for rowOffset and
			      // colOffset values determined by photo
			      double rowOffset =
				otherFieldH->frameStat((arFilter)kk).rowOffset();
			      double colOffset =
				otherFieldH->frameStat((arFilter)kk).colOffset();
			      if (kk == 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
				{
				  photoTrans2[kk].a +=
				    (-photoTrans2[kk].b * rowOffset -
				     photoTrans2[kk].c * colOffset);
				  photoTrans2[kk].d +=
				    (-photoTrans2[kk].e * rowOffset -
				     photoTrans2[kk].f * colOffset);
				}
			      arPTrans dbPTrans2 =
				otherPTrans.filter((arFilter)kk);
			      ptrans2.a[kk].val = dbPTrans2.a().value();
			      ptrans2.a[kk].err = dbPTrans2.a().err();
			      ptrans2.b[kk].val = dbPTrans2.b().value();
			      ptrans2.b[kk].err = dbPTrans2.b().err();
			      ptrans2.c[kk].val = dbPTrans2.c().value();
			      ptrans2.c[kk].err = dbPTrans2.c().err();
			      ptrans2.k[kk].val = dbPTrans2.k().value();
			      ptrans2.k[kk].err = dbPTrans2.k().err();
			    }
			  if (tsObjCalibrate(itr, pair->obj1, &ptrans1,
					     (const TRANS **)transPtr1,
					     (const TRANS **)photoTransPtr1,
					     node, incl, 'r', fieldExpTime)
			      != SH_SUCCESS)
			    {
			      shErrStackPush("tsFieldMatchStatsByArcsec: couldn't calibrate object");
			      return SH_GENERIC_ERROR;
			    }
			  pair->obj1->field = NULL;
			  pair->obj1->run = runId;
			  pair->obj1->camCol = camCol;
			  pair->obj1->rerun = rerun;
			  pair->obj1->fieldId = field->field();
			  if (tsObjCalibrate(matches, pair->obj2, &ptrans2,
					     (const TRANS **)transPtr2,
					     (const TRANS **)photoTransPtr2,
					     otherNode, otherIncl,
					     'r', otherExpTime)
			      != SH_SUCCESS)
			    {
			      shErrStackPush("tsFieldMatchStatsByArcsec: couldn't calibrate object");
			      return SH_GENERIC_ERROR;
			    }
			  pair->obj2->field = NULL;
			  pair->obj2->run = otherRun;
			  pair->obj2->camCol = otherCamCol;
			  pair->obj2->rerun = otherRerun;
			  pair->obj2->fieldId = otherField;
			}
		      else
			{
			  pair->obj1 = NULL;
			  pair->obj2 = NULL;
			}
		      pair->mu = mu1;
		      pair->nu = nu1;
		      pair->dmu = rowDiff;
		      pair->dnu = colDiff;
		    }

		  // Increment mag statistics
		  float bMag1, bMag1Err, bMag2, bMag2Err;
		  for (int i = 0; i < nFilters; i++)
		    {
		      // Calibrate counts to magnitudes
		      arFloatErr counts1, adj1, counts2, adj2;
		      if (objType == AR_OBJECT_TYPE_STAR)
			{
			  // Use PSF magnitudes for stars
			  counts1 = itr->psfCounts((arFilter)i);
			  adj1 = itr->psfCounts((arFilter)adj[i]);
			  counts2 = matches->psfCounts((arFilter)i);
			  adj2 = matches->psfCounts((arFilter)adj[i]);
			}
		      else
			{
			  // Use petrosian magnitudes for galaxies
			  counts1 = itr->petroCounts((arFilter)i);
			  adj1 = itr->petroCounts((arFilter)adj[i]);
			  counts2 = matches->petroCounts((arFilter)i);
			  adj2 = matches->petroCounts((arFilter)adj[i]);
			}

		      // Update mag stats if all counts are good
		      if (counts1.err() >= 0 && counts2.err() >= 0 &&
			  adj1.err() >= 0 && adj2.err() >= 0)
			{
			  double mag1, mag1Err, mag2, mag2Err;
			  int tStatus;
			  arPTrans p1 = ptrans.filter((arFilter)i);
			  arPTrans p2 = otherPTrans.filter((arFilter)i);
			  taLCaliApply(counts1.value(), counts1.err(),
				       adj1.value(), adj1.err(),
				       fieldExpTime, fieldExpTime,
				       trans.airmass,
				       p1.a().value(), p1.k().value(),
				       p1.b().value(), p1.c().value(), sign[i],
				       cosmicColor[cosmicIdx[i]],
				       cosmicScatter[cosmicIdx[i]],
				       &mag1, &mag1Err, &tStatus);
			  taLCaliApply(counts2.value(), counts2.err(),
				       adj2.value(), adj2.err(),
				       otherExpTime, otherExpTime,
				       otherTrans.airmass,
				       p2.a().value(), p2.k().value(),
				       p2.b().value(), p2.c().value(), sign[i],
				       cosmicColor[cosmicIdx[i]],
				       cosmicScatter[cosmicIdx[i]],
				       &mag2, &mag2Err, &tStatus);
		      
			  // Increment statistics
			  double magDiff = mag1 - mag2;
			  matchStats->mag[i].av += magDiff;
			  matchStats->mag[i].rms += magDiff * magDiff;
			  matchStats->mag[i].nPairs++;
			  if (pairChain != NULL)
			    {
			      pair->mag[i] = mag1;
			      pair->dmag[i] = magDiff;
			    }
			}

		      // Get models mags for colors
		      counts1 = itr->counts_model((arFilter)i);
		      adj1 = itr->counts_model((arFilter)adj[i]);
		      counts2 = matches->counts_model((arFilter)i);
		      adj2 = matches->counts_model((arFilter)adj[i]);

		      // If model counts are good, convert to mags
		      if (counts1.err() >= 0 && counts2.err() >= 0 &&
			  adj1.err() >= 0 && adj2.err() >= 0)
			{
			  double mag1, mag1Err, mag2, mag2Err;
			  int tStatus;
			  arPTrans p1 = ptrans.filter((arFilter)i);
			  arPTrans p2 = otherPTrans.filter((arFilter)i);
			  taLCaliApply(counts1.value(), counts1.err(),
				       adj1.value(), adj1.err(),
				       fieldExpTime, fieldExpTime,
				       trans.airmass,
				       p1.a().value(), p1.k().value(),
				       p1.b().value(), p1.c().value(), sign[i],
				       cosmicColor[cosmicIdx[i]],
				       cosmicScatter[cosmicIdx[i]],
				       &mag1, &mag1Err, &tStatus);
			  taLCaliApply(counts2.value(), counts2.err(),
				       adj2.value(), adj2.err(),
				       otherExpTime, otherExpTime,
				       otherTrans.airmass,
				       p2.a().value(), p2.k().value(),
				       p2.b().value(), p2.c().value(), sign[i],
				       cosmicColor[cosmicIdx[i]],
				       cosmicScatter[cosmicIdx[i]],
				       &mag2, &mag2Err, &tStatus);

			  // If possible, update color stats
			  if (i > 0)
			    if (bMag1Err >= 0 && bMag2Err >= 0)
			      {
				// Increment color statistics
				double magDiff =
				  (bMag1 - mag1) - (bMag2 - mag2);
				matchStats->color[i-1].av += magDiff;
				matchStats->color[i-1].rms +=
				  magDiff * magDiff;
				matchStats->color[i-1].nPairs++;
				if (pairChain != NULL)
				  {
				    pair->color[i-1] = bMag1 - mag1;
				    pair->dcolor[i-1] = magDiff;
				  }
			      }

			  // Save blue mags for next band
			  bMag1 = mag1;
			  bMag1Err = mag1Err;
			  bMag2 = mag2;
			  bMag2Err = mag2Err;
			} else {
			  // Undefined model mag.  Save as undefined blue mags
			  // for next band.
			  bMag1Err = -1;
			  bMag2Err = -1;
			}
		    }
		}
	    }
	}
    }

  // Final star statistics
  if (stats->stars.row.nPairs > 0)
    {
      stats->stars.row.av /= stats->stars.row.nPairs;
      double meanSq = stats->stars.row.rms / stats->stars.row.nPairs -
	stats->stars.row.av * stats->stars.row.av;
      stats->stars.row.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  if (stats->stars.col.nPairs > 0)
    {
      stats->stars.col.av /= stats->stars.col.nPairs;
      double meanSq = stats->stars.col.rms / stats->stars.col.nPairs -
	stats->stars.col.av * stats->stars.col.av;
      stats->stars.col.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  for (int i = 0; i < nFilters; i++)
    if (stats->stars.mag[i].nPairs > 0)
      {
	stats->stars.mag[i].av /= stats->stars.mag[i].nPairs;
	double meanSq = stats->stars.mag[i].rms / stats->stars.mag[i].nPairs -
	  stats->stars.mag[i].av * stats->stars.mag[i].av;
	stats->stars.mag[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }
  for (i = 0; i < nColors; i++)
    if (stats->stars.color[i].nPairs > 0)
      {
	stats->stars.color[i].av /= stats->stars.color[i].nPairs;
	double meanSq =
	  stats->stars.color[i].rms / stats->stars.color[i].nPairs -
	  stats->stars.color[i].av * stats->stars.color[i].av;
	stats->stars.color[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }

  // Final galaxy statistics
  if (stats->galaxies.row.nPairs > 0)
    {
      stats->galaxies.row.av /= stats->galaxies.row.nPairs;
      double meanSq = stats->galaxies.row.rms / stats->galaxies.row.nPairs -
	stats->galaxies.row.av * stats->galaxies.row.av;
      stats->galaxies.row.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  if (stats->galaxies.col.nPairs > 0)
    {
      stats->galaxies.col.av /= stats->galaxies.col.nPairs;
      double meanSq = stats->galaxies.col.rms / stats->galaxies.col.nPairs -
	stats->galaxies.col.av * stats->galaxies.col.av;
      stats->galaxies.col.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  for (i = 0; i < nFilters; i++)
    if (stats->galaxies.mag[i].nPairs > 0)
      {
	stats->galaxies.mag[i].av /= stats->galaxies.mag[i].nPairs;
	double meanSq =
	  stats->galaxies.mag[i].rms / stats->galaxies.mag[i].nPairs -
	  stats->galaxies.mag[i].av * stats->galaxies.mag[i].av;
	stats->galaxies.mag[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }
  for (i = 0; i < nColors; i++)
    if (stats->galaxies.color[i].nPairs > 0)
      {
	stats->galaxies.color[i].av /= stats->galaxies.color[i].nPairs;
	double meanSq =
	  stats->galaxies.color[i].rms / stats->galaxies.color[i].nPairs -
	  stats->galaxies.color[i].av * stats->galaxies.color[i].av;
	stats->galaxies.color[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }

  // Return the number of matches.
  return SH_SUCCESS;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsObjectStatusSet
**
**<HTML>
**	Set the SET, GOOD, and OK_RUN flags as appropriate for each
**	object in a field object list.  Two arrays are returned containing
**	those GOOD objects (STARS, GALAXIES, and UNK only) which can
**	potentially match objects in an adjacent
**	field (i.e., they lie in the overlap region or a buffer around the
**	overlap region of width equal to the matching radius).  One array is
**	an ooTVArray of object handles (ooTVArray(ooHandle(arObject))), and
**	the other is an array of structures containing each object's row and
**	column position along with the index of the object in the first array.
**	This second array is sorted by increasing row to facilitate matching
**	by the calling routine.  Returns the number of potentially matchable
**	objects (i.e., the size of the output arrays), or -1 on error.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
tsObjectStatusSet(const ooHandle(arObjectList)& field /* IN: field obj list */,
		  int rowsPerField /* IN:  non-overlap rows per field */,
		  int overlapRows  /* IN:  overlap rows per field */,
		  double matchRadius /* IN: match radius (pixels) */,
		  double nSigma   /* IN: Compile matching statistics for
				   *     nSigma detections (PSF, r') only */,
		  ooTVArray(ooHandle(arObject)) **handles
		  /* OUT: pointer to array of object handles */,
		  TSOBJ **array  /* OUT: pointer to array of objects,
				  *      sorted in order of increasing row */,
		  TSMATCHSTATS *stats  /* MODIFIED: matching statistics */)
{
  // Initialize the output arrays of matchable objects.
  int arrayIncr = 500;
  int arraySize = arrayIncr;
  if ((*handles = new ooTVArray(ooHandle(arObject))(arraySize)) == 0)
    {
      shErrStackPush("tsObjectStatusSet: couldn't allocate memory");
      return -1;
    }
  if ((*array = (TSOBJ*) shMalloc(arraySize * sizeof(TSOBJ))) == NULL)
    {
      shErrStackPush("tsObjectStatusSet: couldn't allocate memory");
      return -1;
    }
  int nMatchable = 0;

  // Offset to convert row from field coordinate to scan coordinate
  int offset = field->field() * rowsPerField;

  // Define ranges of rows in which objects could not possibly match objects
  // in adjacent fields.  Any objects outside this region will be returned
  // in the arrays.
  double unmatchableMin = overlapRows + matchRadius;
  double unmatchableMax = rowsPerField - matchRadius;

  // We divide the overlap regions into two, and assign
  // half of each overlap region to the adjacent fields, and half to the
  // primary region of this field.  We then consider an object to belong to a
  // given field (OK_RUN) if it falls between those limits.
  double okMin = overlapRows / 2.;
  double okMax = rowsPerField + overlapRows / 2.;
  
  // Get iterator to objects for this field
  ooItr(arObject) itr;
  if (field->objects(itr, oocUpdate) != oocSuccess)
    {
      // Error.
      delete *handles;
      shFree(*array);
      shErrStackPush("tsObjectStatusSet: error getting objects for field %d",
		     field);
      return -1;
    }

  // For each object ...
  while (itr.next())
    {
      // Set the SET flag for all objects, indicating its been through this
      // step.
      int status = AR_OBJECT_STATUS_SET;

      // Determine if object is GOOD.
      int good = taGood(itr->objc_flags());

      // If object is not GOOD, then just set its flags and skip to the
      // next object --- processing for BAD objects stops here.
      if (! good)
	{
	  itr->setStatus(status);
	  continue;
	}
      status |= AR_OBJECT_STATUS_GOOD;

      // Determine whether object lies in the primary or overlap regions for
      // this field and set all flags.
      double row = itr->objc_rowc().value();
      if (row >= okMin && row < okMax)
	status |= AR_OBJECT_STATUS_OK_RUN;
      itr->setStatus(status);

      // Count only stars, galaxies, and unknown
      arObjectType objType = itr->objc_type();
      TSSTATS *matchStats;
      arFloatErr counts;
      if (objType == AR_OBJECT_TYPE_STAR)
	{
	  matchStats = &stats->stars;
	  matchStats->nObjects++;
	  counts = itr->psfCounts(AR_R);
	}
      else if (objType == AR_OBJECT_TYPE_GALAXY)
	{
	  matchStats = &stats->galaxies;
	  matchStats->nObjects++;
	  counts = itr->petroCounts(AR_R);
	}
      else if (objType == AR_OBJECT_TYPE_UNK)
	{
	  stats->nUnknown++;
	  counts = itr->psfCounts(AR_R);
	}
      else
	continue;

      // Is this a "bright" object?
      int bright = (counts.value() > nSigma * counts.err() &&
		    counts.err() > 0.);
      if (bright)
	{
	  if (objType == AR_OBJECT_TYPE_UNK)
	    stats->nUnknownBright++;
	  else
	    matchStats->nObjectsBright++;
	}

      // If the object can't possibly match another object in an adjacent field
      // (i.e., its outside the overlap regions, including a buffer zone
      // 'matchRadius' wide), then skip to the next object.
      if (row > unmatchableMin && row < unmatchableMax)
	continue;

      // This object can potentially match an object in an adjacent field.
      // Extend the array if necessary.
      if (nMatchable == arraySize)
	{
	  arraySize += arrayIncr;
	  if ((*handles)->resize(arraySize) != oocSuccess)
	    {
	      delete *handles;
	      shFree(*array);
	      shErrStackPush("tsObjectStatusSet: couldn't reallocate memory");
	      return -1;
	    }
	  if ((*array = (TSOBJ*) shRealloc(*array, arraySize * sizeof(TSOBJ)))
	      == NULL)
	    {
	      delete *handles;
	      shErrStackPush("tsObjectStatusSet: couldn't reallocate memory");
	      return -1;
	    }
	}

      // Add object to the array of potentially matching objects.
      (*handles)->set(nMatchable, itr);
      (*array)[nMatchable].index = nMatchable;
      (*array)[nMatchable].bright = bright;
      (*array)[nMatchable].row = row + offset;
      (*array)[nMatchable].col = itr->objc_colc().value();
      nMatchable++;
    }

  // Resize arrays to final size
  if (nMatchable < arraySize)
    {
      if ((*handles)->resize(nMatchable) != oocSuccess)
	{
	  delete *handles;
	  shFree(*array);
	  shErrStackPush("tsObjectStatusSet: couldn't reallocate memory");
	  return -1;
	}
      if ((*array = (TSOBJ*) shRealloc(*array, nMatchable * sizeof(TSOBJ)))
	  == NULL)
	{
	  delete *handles;
	  shErrStackPush("tsObjectStatusSet: couldn't reallocate memory");
	  return -1;
	}
    }

  // Sort it by increasing row
  qsort((void *)(*array), nMatchable, sizeof(TSOBJ), cmpRow);

  // Return number of potentially matchable objects
  return nMatchable;
}

/********************************************************************/
/* qsort helper function. Final list is ordered from smallest to largest */
static int cmpRow (const void *d1, const void *d2)
{
  TSOBJ *f1 = (TSOBJ *)d1;
  TSOBJ *f2 = (TSOBJ *)d2;
  if (f1->row < f2->row) return -1;
  if (f1->row > f2->row) return 1;
  return 0;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsSelfMerge
**
**<HTML>
**	Merge objects in overlap region of adjacent fields in a Frames
**	Pipeline Run.  Objects match if their separation
**	is less than or equal to the match radius.  This resolves any matched
**	pairs such that, for each pair, one object is set OK_RUN and the other
**	aint.  All objects in a pair have their DUPLICATE flags set.  Matching
**	statistics are compiled and returned.  The input arrays ("array" and
**	"previousArray") must be sorted by increasing row.
**</HTML>
**
**</AUTO>
**============================================================================
*/
void
tsSelfMerge(ooTVArray(ooHandle(arObject)) *handles
	    /* MODIFIED: array of object handles for the field */,
	    TSOBJ *array
	    /* IN: Array of objects for the field */,
	    ooTVArray(ooHandle(arObject)) *previousHandles
	    /* MODIFIED: array of object handles for the previous field */,
	    TSOBJ *previousArray
	    /* IN: Array of objects for the previous field */,
	    double matchRadius   /* IN: Match radius (pixels) */,
	    double rowMin        /* IN: Minimum row to match */,
	    double rowMax        /* IN: Maximum row to match */,
	    TSMATCHSTATS *stats  /* MODIFIED: matching statistics */)
{
  // Set the lower end of the matching range
  double negMatchRadius = -matchRadius;
  double matchRadiusSq = matchRadius * matchRadius;

  // Find index to start searches in previous field (just searching in upper
  // overlap region).  Limit array to just that portion.  Since current field
  // is being searched in the lower overlap region, we start at the beginning
  // of that array.
  int previousSize = previousHandles->size();
  int previousStart = tsObjArrayBinomialSearch(rowMin, previousArray,
					       previousSize) + 1;
  if (previousStart == previousSize) return;  // no objects in overlap region

  // For each object in this field ...
  int size = handles->size();
  for (int i = 0; i < size; i++)
    {
      // Both arrays are sorted by increasing row, so we search on row.
      double row = array[i].row;

      // If beyond max row then we have left the lower overlap area and are
      // done.
      if (row > rowMax) break;

      // Increment low index from previous search until we hit mininum row
      // for this search.
      while (previousStart < previousSize)
	{
	  if (previousArray[previousStart].row >= row - matchRadius)
	    break;
	  previousStart++;
	}

      // Skip if all objects in the previous array are at a smaller row.
      // In fact, since this array is ordered by increasing row, we can
      // break out of the loop since there can be no more matches.
      if (previousStart == previousSize) break;

      // Now check each object until we're beyond the match radius or we
      // run out of the array.
      int closestIndex = -1;
      double closestDistance = 1000. * matchRadiusSq;
      double rowHi = row + matchRadius;
      double col = array[i].col;
      for (int index = previousStart; index < previousSize; index++)
	{
	  // Break out if we're beyond the match radius
	  if (previousArray[index].row > rowHi) break;

	  double colDiff = col - previousArray[index].col;
	  if (colDiff > matchRadius || colDiff < negMatchRadius) continue;
	  double rowDiff = row - previousArray[index].row;
	  double distance = rowDiff * rowDiff + colDiff * colDiff;
	  if (distance <= matchRadiusSq)
	    {
	      // This matches.  Save it if its the closest matching one
	      // thus far.
	      if (distance < closestDistance)
		{
		  closestIndex = index;
		  closestDistance = distance;
		}
	    }
	}
      
      // Do we have a match?
      if (closestIndex != -1)
	{
	  // Yes.  Link the two objects in the database.
	  (*handles)[array[i].index]->
	    addDuplicates((*previousHandles)[previousArray[closestIndex].index]);

	  // Resolve the pair, so that only one object in the pair is set to
	  // OK_RUN.  Since an object in this field can have at most one match,
	  // while objects in the previous field can have more than one match,
	  // we keep the OK_RUNedness of the object in the previous field and
	  // adjust the object in this field accordingly.
	  int status = (*handles)[array[i].index]->status();
	  int previousStatus =
	    (*previousHandles)[previousArray[closestIndex].index]->status();
	  if ((status & AR_OBJECT_STATUS_OK_RUN) ==
	      (previousStatus & AR_OBJECT_STATUS_OK_RUN))
	    {
	      if (previousStatus & AR_OBJECT_STATUS_OK_RUN)
		status &= ~AR_OBJECT_STATUS_OK_RUN;
	      else
		status |= AR_OBJECT_STATUS_OK_RUN;
	    }
	  (*handles)[array[i].index]->
	    setStatus(status | AR_OBJECT_STATUS_DUPLICATE);
	  (*previousHandles)[previousArray[closestIndex].index]->
	    setStatus(previousStatus | AR_OBJECT_STATUS_DUPLICATE);

	  // Update matching statistics
	  arObjectType objType = (*handles)[array[i].index]->objc_type();
	  TSSTATS *matchStats;
	  if (objType == AR_OBJECT_TYPE_STAR)
	    matchStats = &stats->stars;
	  else if (objType == AR_OBJECT_TYPE_GALAXY)
	    matchStats = &stats->galaxies;
	  else
	    continue;
	  matchStats->nMatches++;
	  if (objType != (*previousHandles)[previousArray[closestIndex].index]
	      ->objc_type())
	    {
	      stats->nMismatches++;
	      if (array[i].bright) stats->nMismatchesBright++;
	    }

	  // Add to statistics only if object is "bright" (at least an
	  // nSigma detection).
	  if (array[i].bright)
	    {
	      matchStats->nMatchesBright++;
	      double rowDiff = row - previousArray[closestIndex].row;
	      double colDiff = col - previousArray[closestIndex].col;
	      matchStats->row.av += rowDiff;
	      matchStats->row.rms += rowDiff * rowDiff;
	      matchStats->row.nPairs++;
	      matchStats->col.av += colDiff;
	      matchStats->col.rms += colDiff * colDiff;
	      matchStats->col.nPairs++;
	      arFloatErr model1, model2, bModel1, bModel2;
	      for (int k = 0; k < nFilters; k++)
		{
		  arFloatErr counts1, counts2;
		  if (objType == AR_OBJECT_TYPE_STAR)
		    {
		      // Use PSF magnitudes for stars
		      counts1 =
			(*handles)[array[i].index]->psfCounts((arFilter)k);
		      counts2 =
			(*previousHandles)[previousArray[closestIndex].index]
			->psfCounts((arFilter)k);
		    }
		  else
		    {
		      // Use petrosian magnitudes for galaxies
		      counts1 =
			(*handles)[array[i].index]->petroCounts((arFilter)k);
		      counts2 =
			(*previousHandles)[previousArray[closestIndex].index]
			->petroCounts((arFilter)k);
		    }
		  if (counts1.err() >= 0 && counts2.err() >= 0)
		    {
		      double magDiff = luptitude(counts1.value()) -
			luptitude(counts2.value());
		      matchStats->mag[k].av += magDiff;
		      matchStats->mag[k].rms += magDiff * magDiff;
		      matchStats->mag[k].nPairs++;
		    }

		  // Update color stats
		  model1 =
		    (*handles)[array[i].index]->counts_model((arFilter)k);
		  model2 =
		    (*previousHandles)[previousArray[closestIndex].index]
		    ->counts_model((arFilter)k);
		  if (k > 0)
		    if (model1.err() > 0 && model2.err() > 0 &&
			bModel1.err() > 0 && bModel2.err() > 0)
		      {
			double magDiff =
			  (luptitude(bModel1.value()) -
			   luptitude(model1.value())) -
			  (luptitude(bModel2.value()) -
			   luptitude(model2.value()));
			matchStats->color[k-1].av += magDiff;
			matchStats->color[k-1].rms += magDiff * magDiff;
			matchStats->color[k-1].nPairs++;
		      }
		  bModel1 = model1;
		  bModel2 = model2;
		}
	    }
	}
    }

  // Final star statistics
  if (stats->stars.row.nPairs > 0)
    {
      stats->stars.row.av /= stats->stars.row.nPairs;
      double meanSq = stats->stars.row.rms / stats->stars.row.nPairs -
	stats->stars.row.av * stats->stars.row.av;
      stats->stars.row.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  if (stats->stars.col.nPairs > 0)
    {
      stats->stars.col.av /= stats->stars.col.nPairs;
      double meanSq = stats->stars.col.rms / stats->stars.col.nPairs -
	stats->stars.col.av * stats->stars.col.av;
      stats->stars.col.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  for (i = 0; i < nFilters; i++)
    if (stats->stars.mag[i].nPairs > 0)
      {
	stats->stars.mag[i].av /= stats->stars.mag[i].nPairs;
	double meanSq = stats->stars.mag[i].rms / stats->stars.mag[i].nPairs -
	  stats->stars.mag[i].av * stats->stars.mag[i].av;
	stats->stars.mag[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }
  for (i = 0; i < nColors; i++)
    if (stats->stars.color[i].nPairs > 0)
      {
	stats->stars.color[i].av /= stats->stars.color[i].nPairs;
	double meanSq =
	  stats->stars.color[i].rms / stats->stars.color[i].nPairs -
	  stats->stars.color[i].av * stats->stars.color[i].av;
	stats->stars.color[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }

  // Final galaxy statistics
  if (stats->galaxies.row.nPairs > 0)
    {
      stats->galaxies.row.av /= stats->galaxies.row.nPairs;
      double meanSq = stats->galaxies.row.rms / stats->galaxies.row.nPairs -
	stats->galaxies.row.av * stats->galaxies.row.av;
      stats->galaxies.row.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  if (stats->galaxies.col.nPairs > 0)
    {
      stats->galaxies.col.av /= stats->galaxies.col.nPairs;
      double meanSq = stats->galaxies.col.rms / stats->galaxies.col.nPairs -
	stats->galaxies.col.av * stats->galaxies.col.av;
      stats->galaxies.col.rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
    }
  for (i = 0; i < nFilters; i++)
    if (stats->galaxies.mag[i].nPairs > 0)
      {
	stats->galaxies.mag[i].av /= stats->galaxies.mag[i].nPairs;
	double meanSq =
	  stats->galaxies.mag[i].rms / stats->galaxies.mag[i].nPairs -
	  stats->galaxies.mag[i].av * stats->galaxies.mag[i].av;
	stats->galaxies.mag[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }
  for (i = 0; i < nColors; i++)
    if (stats->galaxies.color[i].nPairs > 0)
      {
	stats->galaxies.color[i].av /= stats->galaxies.color[i].nPairs;
	double meanSq =
	  stats->galaxies.color[i].rms / stats->galaxies.color[i].nPairs -
	  stats->galaxies.color[i].av * stats->galaxies.color[i].av;
	stats->galaxies.color[i].rms = (meanSq > 0. ? sqrt(meanSq) : 0.);
      }

  // Return.
  return;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsRunToCalibArray
**
**<HTML>
**	Create an array of calibrated objects (TSCALIBOBJ) for all OK_RUN
**	objects	(STARS, GALAXIES, and UNK only) in a Frames Pipeline Run.  The
**	array will be sorted in increasing mu order.
**	DCR corrections are applied using cosmic colors, since
**	a final photometric calibration can not be guaranteed to exist for
**	the run at this time.
**	Returns the number of objects read, or -1 on error.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
tsRunToCalibArray(const ooHandle(arFramesPipeRun)& run
		  /* IN: Frames Pipeline run */,
		  const ooHandle(arAstromCalib)& astrom
		  /* IN: astrometric calibration */,
		  ooTVArray(ooHandle(arObject)) **handles
		  /* OUT: pointer to array of object handles */,
		  TSCALIBOBJ **array
		  /* OUT: pointer to array of calibrated objects, sorted in
		   * order of increasing mu */,
		  double *minNu  /* OUT: minimum nu in array (radians) */,
		  double *maxNu  /* OUT: maximum nu in array (radians) */)
{
  // Initialize the array of calibrated objects.
  int arrayIncr = 50000;
  int arraySize = arrayIncr;
  if ((*handles = new ooTVArray(ooHandle(arObject))(arraySize)) == 0)
    {
      shErrStackPush("tsRunToCalibArray: couldn't allocate memory");
      return NULL;
    }
  if ((*array = (TSCALIBOBJ*) shMalloc(arraySize * sizeof(TSCALIBOBJ)))
      == NULL)
    {
      shErrStackPush("tsRunToCalibArray: couldn't allocate memory");
      return NULL;
    }
  int nRead = 0;

  // Fetch the range of fields
  uint16 field0 = run->field0();
  uint16 nFields = run->nFields();
  uint8 camCol = run->camCol();
  int first = 1;

  // All Frames Pipeline Runs are required when stuffed to have used r' as
  // their reference filter.  Thus, we always want the TRANS structure for
  // the r' CCD.
  int camRow = 1;
  int ccd = 10 * camRow + camCol;

  // For each field ...
  for (int32 field = field0; field < field0 + nFields; field++)
    {
      printf("%4d", field);
      fflush(stdout);

      // Get the astrometric transformation for this field
      TRANS trans = astrom->astrotoolsTrans(ccd, field);
      
      // Get iterator to objects for this field
      ooHandle(arObjectList) objectList = run->objectList(field);
      ooItr(arObject) itr;
      if (objectList->objects(itr, oocUpdate) != oocSuccess)
	{
	  // Error.
	  delete *handles;
	  shFree(*array);
	  shErrStackPush("tsRunToCalibArray: error getting objects for field %d", field);
	  return -1;
	}

      // For each object ...
      while (itr.next())
	{
	  // We will match only OK_RUN objects.
	  if (! (itr->status() & AR_OBJECT_STATUS_OK_RUN)) continue;
	      
	  // Only match stars, galaxies, and unknowns
	  arObjectType objType = itr->objc_type();
	  if (objType != AR_OBJECT_TYPE_STAR &&
	      objType != AR_OBJECT_TYPE_GALAXY &&
	      objType != AR_OBJECT_TYPE_UNK)
	    continue;

	  // Extend the array if necessary
	  if (nRead == arraySize)
	    {
	      arraySize += arrayIncr;
	      if ((*handles)->resize(arraySize) != oocSuccess)
		{
		  delete *handles;
		  shFree(*array);
		  shErrStackPush("tsRunToCalibArray: couldn't reallocate memory");
		  return -1;
		}
	      if ((*array = (TSCALIBOBJ*) shRealloc(*array, arraySize *
						    sizeof(TSCALIBOBJ)))
		  == NULL)
		{
		  delete *handles;
		  shErrStackPush("tsRunToCalibArray: couldn't reallocate memory");
		  return -1;
		}
	    }

	  // Append calibrated object to the array
	  (*handles)->set(nRead, itr);
	  arSphericalCoord coord = itr->gcCoord(trans);
	  (*array)[nRead].index = nRead;
	  (*array)[nRead].mu = coord.longitude();
	  (*array)[nRead].nu = coord.latitude();

	  // Update minimum and maximum nu in the array
	  if (first)
	    {
	      *minNu = (*array)[nRead].nu;
	      *maxNu = (*array)[nRead].nu;
	      first = 0;
	    }
	  else
	    {
	      if ((*array)[nRead].nu < *minNu) *minNu = (*array)[nRead].nu;
	      if ((*array)[nRead].nu > *maxNu) *maxNu = (*array)[nRead].nu;
	    }

	  // Increment the total number of objects
	  nRead++;
	}
    }
  printf("\n");

  // Resize the array
  if (nRead < arraySize)
    {
      if ((*handles)->resize(nRead) != oocSuccess)
	{
	  delete *handles;
	  shFree(*array);
	  shErrStackPush("tsRunToCalibArray: couldn't reallocate memory");
	  return -1;
	}
      if ((*array = (TSCALIBOBJ*) shRealloc(*array, nRead * sizeof(TSCALIBOBJ)))
	  == NULL)
	{
	  delete *handles;
	  shErrStackPush("tsRunToCalibArray: couldn't reallocate memory");
	  return -1;
	}
    }

  // Sort it by increasing mu
  qsort((void *)(*array), nRead, sizeof(TSCALIBOBJ), cmpMu);

  // Return number of objects read
  return nRead;
}

/********************************************************************/
/* qsort helper function. Final list is ordered from smallest to largest*/
static int cmpMu (const void *d1, const void *d2)
{
  TSCALIBOBJ *f1 = (TSCALIBOBJ *)d1;
  TSCALIBOBJ *f2 = (TSCALIBOBJ *)d2;
  if (f1->mu < f2->mu) return -1;
  if (f1->mu > f2->mu) return 1;
  return 0;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: tsRunMergeOne
**
**<HTML>
**	Merge one Frames Pipeline Run against another.  Return SH_SUCCESS
**	if successful, else an error code.  Only OK_RUN objects of type
**	STAR, GALAXY, or UNK are matched.
**<P>
**	Assumes that nu is sufficiently small that cos(nu) ~ 1 and can thus
**	be ignored.  DCR corrections are applied using cosmic colors, since
**	a final photometric calibration can not be guaranteed to exist for
**	the run at this time.
**</HTML>
**
**</AUTO>
**============================================================================
*/
RET_CODE
tsRunMergeOne(ooTVArray(ooHandle(arObject)) *handles
	      /* MODIFIED: array of object handles */,
	      TSCALIBOBJ *array    /* MODIFIED: Array of calibrated objects */,
	      int nObjects         /* IN: Number of objects to merge */,
	      double minNu         /* IN: minimum nu in array (degrees) */,
	      double maxNu         /* IN: maximum nu in array (degrees) */,
	      double node          /* IN: ascending node of GC (degrees) */,
	      double incl          /* IN: inclination of GC (degrees) */,
	      double equinox       /* IN: equinox of GC coords (years) */,
	      ooHandle(arFramesPipeRun)& other
	      /* IN: Frames Pipe Run to merge against */,
	      double matchRadius   /* IN: Match radius (arcsecs) */)
{
  // Fetch the range of fields and calibrations for the other run
  uint16 field0 = other->field0();
  uint16 nFields = other->nFields();
  uint8 camCol = other->camCol();
  ooHandle(arAstromCalib) astromH = other->mergeAstrometricCalibration();
  if (fabs(astromH->equinox() - equinox) > 0.000001)
    {
      shErrStackPush("tsRunMergeOne: mismatched equinoxes");
      return SH_GENERIC_ERROR;
    }
  double node2 = astromH->node();
  double incl2 = astromH->incl();

  // All Frames Pipeline Runs are required when stuffed to have used r' as
  // their reference filter.  Thus, we always want the TRANS structure for
  // the r' CCD.
  int camRow = 1;
  int ccd = 10 * camRow + camCol;

  // Get number of non-overlap rows and columns on a frame.
  int nRows =
    other->imagingRun()->program()->ccd(camRow,other->camCol())->nRows();
  // HARDWIRED: number of columns per frame.
  int nCols = 2048;
  // HARDWIRED: number of overlap rows per frame.
  int overlapRows = 128;

  // Want full size of the frame
  nRows += overlapRows;

  // Convert match radius to degrees
  matchRadius *= at_asec2Deg;
  double negMatchRadius = -matchRadius;
  double matchRadiusSq = matchRadius * matchRadius;

  // Range in great circle longitude covered by the array (degrees)
  double minMu = array[0].mu;
  double maxMu = array[nObjects-1].mu;

  // Some parked scans have oddly defined great circles in which the scans
  // can go through 0/360.  Thus, to match-up, we'll need to force the "other"
  // scan into the same range of mu as "this" scan.  Thus, lets determine a
  // good range of mu for "this" scan (just subtract 10 degrees from the
  // minimum mu for the low mu in the range).
  double muRangeLo = minMu - 10.;
  double muRangeHi = muRangeLo + 360.;

  // For each field ...
  printf("run %d, camCol %d, rerun %d, fields %d to %d\n", other->run(),
	 other->camCol(), other->rerun(), field0, field0+nFields-1);
  fflush(stdout);
  for (int32 field = field0; field < field0 + nFields; field++)
    {
      // Get astrometric transformation for this field
      TRANS trans = astromH->astrotoolsTrans(ccd, field);
      
      // Get great circle coordinates of the field center (degrees).
      double centerMu = 0, centerNu = 0;
      atTransApply(&trans, 'r', nRows / 2., 0., nCols / 2., 0., NULL, NULL,
		   &centerMu, NULL, &centerNu,NULL);

      // If first field, measure the field radius (degrees).
      double fieldRadius;
      if (field == field0)
	{
	  double cornerMu = 0, cornerNu = 0;
	  atTransApply(&trans, 'r', 0., 0., 0., 0., NULL, NULL,
		       &cornerMu, NULL, &cornerNu, NULL);
	  fieldRadius = arSphericalCoord(centerMu, centerNu).
	    distance(arSphericalCoord(cornerMu, cornerNu)) * at_asec2Deg;
	}

      // Convert center coordinates to target run great circle coordinates
      // (degrees).
      double ra, dec;
      atGCToEq(centerMu, centerNu, &ra, &dec, node2, incl2);
      atEqToGC(ra, dec, &centerMu, &centerNu, node, incl);
      atBound(&centerMu, muRangeLo, muRangeHi);

      // Skip this field if it lies outside range of mu or nu spanned by the
      // target run.
      if (centerMu + fieldRadius < minMu || centerMu - fieldRadius > maxMu)
	continue;
      if (centerNu + fieldRadius < minNu || centerNu - fieldRadius > maxNu)
	continue;
      printf("%4d", field);
      fflush(stdout);

      // Get iterator to objects for this field
      ooHandle(arObjectList) objectList = other->objectList(field);
      ooItr(arObject) itr;
      if (objectList->objects(itr, oocUpdate) != oocSuccess)
	{
	  shErrStackPush("tsRunMergeOne: error getting objects for field %d",
			 field);
	  return SH_GENERIC_ERROR;
	}

      // For each object ...
      int first = 1;
      while (itr.next())
	{
	  // Only match OK_RUN objects.
	  if (! (itr->status() & AR_OBJECT_STATUS_OK_RUN)) continue;

	  // Only match stars, galaxies, and unknowns
	  arObjectType objType = itr->objc_type();
	  if (objType != AR_OBJECT_TYPE_STAR &&
	      objType != AR_OBJECT_TYPE_GALAXY &&
	      objType != AR_OBJECT_TYPE_UNK)
	    continue;

	  // Fetch calibrated great circle coordinates (degrees)
	  arSphericalCoord coord = itr->gcCoord(trans);
	  double mu = coord.longitude();
	  double nu = coord.latitude();

	  // Convert to target run great circle coordinates (degrees)
	  atGCToEq(mu, nu, &ra, &dec, node2, incl2);
	  atEqToGC(ra, dec, &mu, &nu, node, incl);
	  atBound(&mu, muRangeLo, muRangeHi);

	  // Look for match in calibrated array.  We'll search based on the
	  // object position minus the match radius.
	  int index;
	  if (first)
	    {
	      // Do a binomial search for the first object.  The binomial
	      // search returns an array index which is set to one below the
	      // lowest matching or greater than array element.
	      index = tsCalibObjArrayBinomialSearch(mu-matchRadius, array,
						    nObjects) + 1;
	      first = 0;
	    }
	  else
	    {
	      // Search with correlated values around the last index.
	      index = tsCalibObjArrayCorrelatedSearch(mu-matchRadius, array,
						      nObjects, index-1) + 1;
	    }

	  // Skip if all objects in the array are at a smaller mu. 
	  if (index == nObjects) continue;

	  // Now check each object until we're beyond the match radius or we
	  // run out of the array.
	  int closestIndex = -1;
	  double closestDistance = 1000. * matchRadiusSq;
	  double muHi = mu + matchRadius;
	  for (; index < nObjects; index++)
	    {
	      // Break out if we're beyond the match radius
	      if (array[index].mu > muHi) break;

	      double nuDiff = nu - array[index].nu;
	      if (nuDiff > matchRadius || nuDiff < negMatchRadius) continue;
	      double muDiff = mu - array[index].mu;
	      double distance = muDiff * muDiff + nuDiff * nuDiff;
	      if (distance <= matchRadiusSq)
		{
		  // This matches.  Save it if its the closest matching one
		  // thus far.
		  if (distance < closestDistance)
		    {
		      closestIndex = index;
		      closestDistance = distance;
		    }
		}
	    }
	  
	  // If we had a match, link the two objects.
	  if (closestIndex != -1)
	    (*handles)[array[closestIndex].index]->addMatches(itr);
	}
    }
  printf("\n");
  fflush(stdout);

  // Return.
  return SH_SUCCESS;
}

// Binomial search by mu on an array of TSCALIBOBJ.  Returns the index such
// that the input mu is in the range mu(index) to mu(index+1);
// the input mu is definitely greater than mu(index), but could be
// equal to mu(index+1).
int tsCalibObjArrayBinomialSearch(double mu, TSCALIBOBJ *array, int nObjects)
{
  int lo = -1;
  int hi = nObjects;
  while (hi - lo > 1)
    {
      int mid = (hi + lo) / 2;
      if (mu > array[mid].mu)
	lo = mid;
      else
	hi = mid;
    }
  return lo;
}
  
// Binomial search by row on an array of TSOBJ.  Returns the index such
// that the input row is in the range row(index) to row(index+1);
// the input row is definitely greater than row(index), but could be
// equal to row(index+1).
int tsObjArrayBinomialSearch(double row, TSOBJ *array, int nObjects)
{
  int lo = -1;
  int hi = nObjects;
  while (hi - lo > 1)
    {
      int mid = (hi + lo) / 2;
      if (row > array[mid].row)
	lo = mid;
      else
	hi = mid;
    }
  return lo;
}

// Correlated search by mu on an array of TSCALIBOBJ.  Returns the index such
// that the input mu is in the range mu(index) to mu(index+1);
// the input mu is definitely greater than mu(index), but could be
// equal to mu(index+1).
int tsCalibObjArrayCorrelatedSearch(double mu, TSCALIBOBJ *array, int nObjects,
				    int guess)
{
  if (guess <= -1)
    {
      if (mu <= array[0].mu)
	return -1;
      else
	guess = 0;
    }
  else if (guess >= nObjects)
    {
      if (mu > array[nObjects - 1].mu)
	return nObjects - 1;
      else
	guess = nObjects - 1;
    }
  int lo, hi;
  int incr = 1;
  if (mu > array[guess].mu)
    {
      if (guess == nObjects - 1) return guess;
      lo = guess;
      hi = guess + incr;
      while (mu > array[hi].mu)
	{
	  lo = hi;
	  incr += incr;
	  hi += incr;
	  if (hi >= nObjects)
	    {
	      hi = nObjects;
	      break;
	    }
	}
    }
  else
    {
      if (guess == 0) return -1;
      hi = guess;
      lo = guess - incr;
      while (mu <= array[lo].mu)
	{
	  hi = lo;
	  incr += incr;
	  lo -= incr;
	  if (lo < 0)
	    {
	      lo = -1;
	      break;
	    }
	}
    }
  while (hi - lo > 1)
    {
      int mid = (hi + lo) / 2;
      if (mu > array[mid].mu)
	lo = mid;
      else
	hi = mid;
    }
  return lo;
}
