/*****************************************************************************
******************************************************************************
**<AUTO>
**
** FILE:	io.c
**
**<HTML>
**	This file contains C functions to read/write chains of TA_CALIB_OBJ
**	from/to TBLCOLs.
**</HTML>
**</AUTO>
**
**
** ENVIRONMENT:
**	ANSI C.
**
******************************************************************************
******************************************************************************
*/
#include <string.h>
#include <dervish.h>
#include "taCalibObj.h"

typedef struct taField
{
  char *name;
  int dimCnt;
  int dim[2];
  char *type;
  char *units;
  int raw;    /* 1=in fpObj files, 0=not */
} TA_FIELD;

/*
 * Define the FITS file format for calibrated objects
 */

#define N_OBJ_FIELDS 129     /* Number of FITS fields in tsObj files */
/* Maximum number of profile bins for each object */
#define MAX_PROFILES 15

static TA_FIELD objFields[N_OBJ_FIELDS] = {
  {"run", 0, {0, 0}, "INT", "", 0},
  {"camCol", 0, {0, 0}, "INT", "", 0},
  {"rerun", 0, {0, 0}, "INT", "", 0},
  {"field", 0, {0, 0}, "INT", "", 0},
  {"parent", 0, {0, 0}, "INT", "", 1},
  {"id", 0, {0, 0}, "INT", "", 1},
  {"nchild", 0, {0, 0}, "INT", "", 1},
  {"objc_type", 0, {0, 0}, "INT", "", 1},
  {"catID", 0, {0, 0}, "INT", "", 1},
  {"objc_flags", 0, {0, 0}, "INT", "", 1},
  {"objc_flags2", 0, {0, 0}, "INT", "", 1},
  {"objc_rowc", 0, {0, 0}, "FLOAT", "pixels", 1},
  {"objc_rowcErr", 0, {0, 0}, "FLOAT", "pixels", 1},
  {"objc_colc", 0, {0, 0}, "FLOAT", "pixels", 1},
  {"objc_colcErr", 0, {0, 0}, "FLOAT", "pixels", 1},
  {"rowv", 0, {0, 0}, "FLOAT", "deg/day", 1},
  {"rowvErr", 0, {0, 0}, "FLOAT", "deg/day", 1},
  {"colv", 0, {0, 0}, "FLOAT", "deg/day", 1},
  {"colvErr", 0, {0, 0}, "FLOAT", "deg/day", 1},
  {"rowc", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"rowcErr", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"colc", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"colcErr", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"sky", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes/square-arcsec", 1},
  {"skyErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes/square-arcsec", 1},
  {"psfCounts", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"psfCountsErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"fiberCounts", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"fiberCountsErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"petroCounts", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"petroCountsErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"petroRad", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"petroRadErr", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"petroR50", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"petroR50Err", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"petroR90", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"petroR90Err", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"Q", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"QErr", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"U", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"UErr", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"iso_rowc", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"iso_rowcErr", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"iso_rowcGrad", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels/(1 mag/arcsec^2 SB change) at object center", 1},
  {"iso_colc", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"iso_colcErr", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels", 1},
  {"iso_colcGrad", 1, {TA_NFILTERS, 0}, "FLOAT", "pixels/(1 mag/arcsec^2 SB change) at object center", 1},
  {"iso_a", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"iso_aErr", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"iso_aGrad", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs/(1 mag/arcsec^2 SB change) at object center", 1},
  {"iso_b", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"iso_bErr", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"iso_bGrad", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs/(1 mag/arcsec^2 SB change) at object center", 1},
  {"iso_phi", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees", 1},
  {"iso_phiErr", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees", 1},
  {"iso_phiGrad", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees/(1 mag/arcsec^2 SB change) at object center", 1},
  {"r_deV", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"r_deVErr", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"ab_deV", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"ab_deVErr", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"phi_deV", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees", 1},
  {"phi_deVErr", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees", 1},
  {"counts_deV", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"counts_deVErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"r_exp", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"r_expErr", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 1},
  {"ab_exp", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"ab_expErr", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"phi_exp", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees", 1},
  {"phi_expErr", 1, {TA_NFILTERS, 0}, "FLOAT", "degrees", 1},
  {"counts_exp", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"counts_expErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"counts_model", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"counts_modelErr", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 1},
  {"texture", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"star_L", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"exp_L", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"deV_L", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"fracPSF", 1, {TA_NFILTERS, 0}, "FLOAT", "", 1},
  {"flags", 1, {TA_NFILTERS, 0}, "INT", "", 1},
  {"flags2", 1, {TA_NFILTERS, 0}, "INT", "", 1},
  {"type", 1, {TA_NFILTERS, 0}, "INT", "", 1},
  {"nprof", 1, {TA_NFILTERS, 0}, "INT", "", 1},
  {"profMean", 2, {TA_NFILTERS, MAX_PROFILES}, "FLOAT",
   "luptidues/square-arcsec", 1},
  {"profErr", 2, {TA_NFILTERS, MAX_PROFILES}, "FLOAT",
   "luptidues/square-arcsec", 1},
  {"status", 0, {0, 0}, "INT", "", 0},
  {"ra", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"dec", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"lambda", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"eta", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"l", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"b", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"offsetRa", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 0},
  {"offsetDec", 1, {TA_NFILTERS, 0}, "FLOAT", "arcsecs", 0},
  {"primTarget", 0, {0, 0}, "INT", "", 0},
  {"secTarget", 0, {0, 0}, "INT", "", 0},
  {"reddening", 1, {TA_NFILTERS, 0}, "FLOAT", "luptitudes", 0},
  {"propermotionmatch", 0, {0, 0}, "INT", "", 0},
  {"propermotiondelta", 0, {0, 0}, "FLOAT", "arcsec", 0},
  {"propermotion", 0, {0, 0}, "FLOAT", "arcsec/century", 0},
  {"propermotionangle", 0, {0, 0}, "FLOAT", "degrees, + N thru E", 0},
  {"usnoBlue", 0, {0, 0}, "FLOAT", "mag", 0},
  {"usnoRed", 0, {0, 0}, "FLOAT", "mag", 0},
  {"firstMatch", 0, {0, 0}, "INT", "", 0},
  {"firstId", 0, {0, 0}, "INT", "", 0},
  {"firstLambda", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"firstEta", 0, {0, 0}, "DOUBLE", "degrees", 0},
  {"firstDelta", 0, {0, 0}, "FLOAT", "arcsec", 0},
  {"firstPeak", 0, {0, 0}, "FLOAT", "mJy", 0},
  {"firstInt", 0, {0, 0}, "FLOAT", "mJy", 0},
  {"firstRms", 0, {0, 0}, "FLOAT", "mJy", 0},
  {"firstMajor", 0, {0, 0}, "FLOAT", "arcsec", 0},
  {"firstMinor", 0, {0, 0}, "FLOAT", "arcsec", 0},
  {"firstPa", 0, {0, 0}, "FLOAT", "degrees", 0},
  {"rosatMatch", 0, {0, 0}, "INT", "", 0},
  {"rosatDelta", 0, {0, 0}, "FLOAT", "arcsec", 0},
  {"rosatPosErr", 0, {0, 0}, "FLOAT", "arcsec", 0},
  {"rosatCps", 0, {0, 0}, "FLOAT", "counts/sec", 0},
  {"rosatCpsErr", 0, {0, 0}, "FLOAT", "counts/sec", 0},
  {"rosatHr1", 0, {0, 0}, "FLOAT", "", 0},
  {"rosatHr1Err", 0, {0, 0}, "FLOAT", "", 0},
  {"rosatHr2", 0, {0, 0}, "FLOAT", "", 0},
  {"rosatHr2Err", 0, {0, 0}, "FLOAT", "", 0},
  {"rosatExt", 0, {0, 0}, "FLOAT", "arcsecs", 0},
  {"rosatExtLike", 0, {0, 0}, "FLOAT", "", 0},
  {"rosatDetectLike", 0, {0, 0}, "FLOAT", "", 0},
  {"rosatExposure", 0, {0, 0}, "FLOAT", "seconds", 0},
  {"priority", 0, {0, 0}, "UINT", "", 0},
  {"matchid", 2, {10, 5}, "INT", "", 0}
};

enum objFieldNames {
  RUN,
  CAMCOL,
  RERUN,
  FIELD,
  PARENT,
  ID,
  NCHILD,
  OBJC_TYPE,
  CATID,
  OBJC_FLAGS,
  OBJC_FLAGS2,
  OBJC_ROWC,
  OBJC_ROWCERR,
  OBJC_COLC,
  OBJC_COLCERR,
  ROWV,
  ROWVERR,
  COLV,
  COLVERR,
  ROWC,
  ROWCERR,
  COLC,
  COLCERR,
  SKY,
  SKYERR,
  PSFCOUNTS,
  PSFCOUNTSERR,
  FIBERCOUNTS,
  FIBERCOUNTSERR,
  PETROCOUNTS,
  PETROCOUNTSERR,
  PETRORAD,
  PETRORADERR,
  PETROR50,
  PETROR50ERR,
  PETROR90,
  PETROR90ERR,
  Q,
  QERR,
  U,
  UERR,
  ISO_ROWC,
  ISO_ROWCERR,
  ISO_ROWCGRAD,
  ISO_COLC,
  ISO_COLCERR,
  ISO_COLCGRAD,
  ISO_A,
  ISO_AERR,
  ISO_AGRAD,
  ISO_B,
  ISO_BERR,
  ISO_BGRAD,
  ISO_PHI,
  ISO_PHIERR,
  ISO_PHIGRAD,
  R_DEV,
  R_DEVERR,
  AB_DEV,
  AB_DEVERR,
  PHI_DEV,
  PHI_DEVERR,
  COUNTS_DEV,
  COUNTS_DEVERR,
  R_EXP,
  R_EXPERR,
  AB_EXP,
  AB_EXPERR,
  PHI_EXP,
  PHI_EXPERR,
  COUNTS_EXP,
  COUNTS_EXPERR,
  COUNTS_MODEL,
  COUNTS_MODELERR,
  TEXTURE,
  STAR_L,
  EXP_L,
  DEV_L,
  FRACPSF,
  FLAGS,
  FLAGS2,
  OTYPE,
  NPROF,
  PROFMEAN,
  PROFERR,
  STATUS,
  RA,
  DEC,
  LAMBDA,
  ETA,
  L,
  B,
  OFFSETRA,
  OFFSETDEC,
  PRIM_TARGET,
  SEC_TARGET,
  REDDENING,
  PROPERMOTIONMATCH,
  PROPERMOTIONDELTA,
  PROPERMOTION,
  PROPERMOTIONANGLE,
  USNOBLUE,
  USNORED,
  FIRSTMATCH,
  FIRSTID,
  FIRSTLAMBDA,
  FIRSTETA,
  FIRSTDELTA,
  FIRSTPEAK,
  FIRSTINT,
  FIRSTRMS,
  FIRSTMAJOR,
  FIRSTMINOR,
  FIRSTPA,
  ROSATMATCH,
  ROSATDELTA,
  ROSATPOSERR,
  ROSATCPS,
  ROSATCPSERR,
  ROSATHR1,
  ROSATHR1ERR,
  ROSATHR2,
  ROSATHR2ERR,
  ROSATEXT,
  ROSATEXTLIKE,
  ROSATDETECTLIKE,
  ROSATEXPOSURE,
  PRIORITY,
  MATCHID
};

#define ioff(field,idx) (((int*)(array[(field)]->arrayPtr))[(idx)])
#define foff(field,idx) (((float*)(array[(field)]->arrayPtr))[(idx)])
#define doff(field,idx) (((double*)(array[(field)]->arrayPtr))[(idx)])
#define i2off(field,idx1,idx2) (((int**)(array[(field)]->arrayPtr))[(idx1)][(idx2)])
#define i3off(field,idx1,idx2,idx3) (((int***)(array[(field)]->arrayPtr))[(idx1)][(idx2)][(idx3)])
#define f2off(field,idx1,idx2) (((float**)(array[(field)]->arrayPtr))[(idx1)][(idx2)])
#define d2off(field,idx1,idx2) (((double**)(array[(field)]->arrayPtr))[(idx1)][(idx2)])
#define f3off(field,idx1,idx2,idx3) (((float***)(array[(field)]->arrayPtr))[(idx1)][(idx2)][(idx3)])


/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	taCalibObjsWriteToTbl
**
**<HTML>
**	Write a CHAIN of calibrated objects (TA_CALIB_OBJ) to a TBLCOL.
**	If successful, a pointer to the new TBLCOL is returned; if an
**	error occurred NULL is returned.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
TBLCOL *
taCalibObjsWriteToTbl(const CHAIN *chain   /* IN: CHAIN of objects */)
{
  ARRAY* array[N_OBJ_FIELDS];
  int fldIdx, nRows, dimIdx, k, m, p, nProf;
  TBLCOL *tblCol = NULL;
  TA_CALIB_OBJ *obj = NULL;
  CURSOR_T curse;

  /* Create the TBLCOL */
  nRows = shChainSize(chain);
  if (shTblColCreate(nRows, N_OBJ_FIELDS, 0, &tblCol) != SH_SUCCESS)
    {
      shErrStackPush("taCalibObjsWriteToTbl: couldn't create TBLCOL");
      return NULL;
    }

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

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

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

  /* For each object ... */
  k = -1;
  curse = shChainCursorNew(chain);
  while ((obj = (TA_CALIB_OBJ*) shChainWalk(chain, curse, NEXT)) != NULL)
    {
      /* Copy it to the TBLCOL */
      k++;
      ioff(RUN,k) = obj->run;
      ioff(CAMCOL,k) = obj->camCol;
      ioff(RERUN,k) = obj->rerun;
      ioff(FIELD,k) = obj->fieldId;
      ioff(PARENT,k) = obj->parent;
      ioff(ID,k) = obj->id;
      ioff(NCHILD,k) = obj->nchild;
      ioff(OBJC_TYPE,k) = obj->objc_type;
      ioff(CATID,k) = obj->catId;
      ioff(OBJC_FLAGS,k) = obj->objc_flags;
      ioff(OBJC_FLAGS2,k) = obj->objc_flags2;
      foff(OBJC_ROWC,k) = obj->objc_rowc.val;
      foff(OBJC_ROWCERR,k) = obj->objc_rowc.err;
      foff(OBJC_COLC,k) = obj->objc_colc.val;
      foff(OBJC_COLCERR,k) = obj->objc_colc.err;
      foff(ROWV,k) = obj->rowv.val;
      foff(ROWVERR,k) = obj->rowv.err;
      foff(COLV,k) = obj->colv.val;
      foff(COLVERR,k) = obj->colv.err;
      ioff(STATUS,k) = obj->status;
      doff(RA,k) = obj->ra;
      doff(DEC,k) = obj->dec;
      doff(LAMBDA,k) = obj->lambda;
      doff(ETA,k) = obj->eta;
      doff(L,k) = obj->l;
      doff(B,k) = obj->b;
      ioff(PRIM_TARGET,k) = obj->primTarget;
      ioff(SEC_TARGET,k) = obj->secTarget;
      ioff(PROPERMOTIONMATCH,k) = obj->properMotionMatch; 
      foff(PROPERMOTIONDELTA,k) = obj->properMotionDelta; 
      foff(PROPERMOTION,k) = obj->properMotion; 
      foff(PROPERMOTIONANGLE,k) = obj->properMotionAngle; 
      foff(USNOBLUE,k) = obj->usnoBlue; 
      foff(USNORED,k) = obj->usnoRed; 
      ioff(FIRSTMATCH,k) = obj->firstMatch; 
      ioff(FIRSTID,k) = obj->firstId;
      doff(FIRSTLAMBDA,k) = obj->firstLambda;
      doff(FIRSTETA,k) = obj->firstEta;
      foff(FIRSTDELTA,k) = obj->firstDelta; 
      foff(FIRSTPEAK,k) = obj->firstPeak; 
      foff(FIRSTINT,k) = obj->firstInt; 
      foff(FIRSTRMS,k) = obj->firstrms; 
      foff(FIRSTMAJOR,k) = obj->firstmajor; 
      foff(FIRSTMINOR,k) = obj->firstminor; 
      foff(FIRSTPA,k) = obj->firstpa; 
      ioff(ROSATMATCH,k) = obj->rosatMatch; 
      foff(ROSATDELTA,k) = obj->rosatDelta; 
      foff(ROSATPOSERR,k) = obj->rosatPosErr; 
      foff(ROSATCPS,k) = obj->rosatCPS.val; 
      foff(ROSATCPSERR,k) = obj->rosatCPS.err; 
      foff(ROSATHR1,k) = obj->rosatHR1.val; 
      foff(ROSATHR1ERR,k) = obj->rosatHR1.err; 
      foff(ROSATHR2,k) = obj->rosatHR2.val; 
      foff(ROSATHR2ERR,k) = obj->rosatHR2.err; 
      foff(ROSATEXT,k) = obj->rosatExt; 
      foff(ROSATEXTLIKE,k) = obj->rosatExtLike; 
      foff(ROSATDETECTLIKE,k) = obj->rosatDetectLike; 
      foff(ROSATEXPOSURE,k) = obj->rosatExposure; 
      ioff(PRIORITY,k) = obj->priority; 
      for (p = 0; p < 10; p++)
	for (m = 0; m < 5; m++) i3off(MATCHID,k,p,m) = obj->matchId[p][m];

      /* For each filter ... */
      for (m = 0; m < TA_NFILTERS; m++)
	{
	  f2off(ROWC,k,m) = obj->detection[m].rowc.val;
	  f2off(ROWCERR,k,m) = obj->detection[m].rowc.err;
	  f2off(COLC,k,m) = obj->detection[m].colc.val;
	  f2off(COLCERR,k,m) = obj->detection[m].colc.err;
	  f2off(SKY,k,m) = obj->detection[m].sky.val;
	  f2off(SKYERR,k,m) = obj->detection[m].sky.err;
	  f2off(PSFCOUNTS,k,m) = obj->detection[m].psfCounts.val;
	  f2off(PSFCOUNTSERR,k,m) = obj->detection[m].psfCounts.err;
	  f2off(FIBERCOUNTS,k,m) = obj->detection[m].fiberCounts.val;
	  f2off(FIBERCOUNTSERR,k,m) = obj->detection[m].fiberCounts.err;
	  f2off(PETROCOUNTS,k,m) = obj->detection[m].petroCounts.val;
	  f2off(PETROCOUNTSERR,k,m) = obj->detection[m].petroCounts.err;
	  f2off(PETRORAD,k,m) = obj->detection[m].petroRad.val;
	  f2off(PETRORADERR,k,m) = obj->detection[m].petroRad.err;
	  f2off(PETROR50,k,m) = obj->detection[m].petroR50.val;
	  f2off(PETROR50ERR,k,m) = obj->detection[m].petroR50.err;
	  f2off(PETROR90,k,m) = obj->detection[m].petroR90.val;
	  f2off(PETROR90ERR,k,m) = obj->detection[m].petroR90.err;
	  f2off(Q,k,m) = obj->detection[m].q.val;
	  f2off(QERR,k,m) = obj->detection[m].q.err;
	  f2off(U,k,m) = obj->detection[m].u.val;
	  f2off(UERR,k,m) = obj->detection[m].u.err;
	  f2off(ISO_ROWC,k,m) = obj->detection[m].iso_rowc.val;
	  f2off(ISO_ROWCERR,k,m) = obj->detection[m].iso_rowc.err;
	  f2off(ISO_ROWCGRAD,k,m) = obj->detection[m].iso_rowc.grad;
	  f2off(ISO_COLC,k,m) = obj->detection[m].iso_colc.val;
	  f2off(ISO_COLCERR,k,m) = obj->detection[m].iso_colc.err;
	  f2off(ISO_COLCGRAD,k,m) = obj->detection[m].iso_colc.grad;
	  f2off(ISO_A,k,m) = obj->detection[m].iso_a.val;
	  f2off(ISO_AERR,k,m) = obj->detection[m].iso_a.err;
	  f2off(ISO_AGRAD,k,m) = obj->detection[m].iso_a.grad;
	  f2off(ISO_B,k,m) = obj->detection[m].iso_b.val;
	  f2off(ISO_BERR,k,m) = obj->detection[m].iso_b.err;
	  f2off(ISO_BGRAD,k,m) = obj->detection[m].iso_b.grad;
	  f2off(ISO_PHI,k,m) = obj->detection[m].iso_phi.val;
	  f2off(ISO_PHIERR,k,m) = obj->detection[m].iso_phi.err;
	  f2off(ISO_PHIGRAD,k,m) = obj->detection[m].iso_phi.grad;
	  f2off(R_DEV,k,m) = obj->detection[m].r_deV.val;
	  f2off(R_DEVERR,k,m) = obj->detection[m].r_deV.err;
	  f2off(AB_DEV,k,m) = obj->detection[m].ab_deV.val;
	  f2off(AB_DEVERR,k,m) = obj->detection[m].ab_deV.err;
	  f2off(PHI_DEV,k,m) = obj->detection[m].phi_deV.val;
	  f2off(PHI_DEVERR,k,m) = obj->detection[m].phi_deV.err;
	  f2off(COUNTS_DEV,k,m) = obj->detection[m].counts_deV.val;
	  f2off(COUNTS_DEVERR,k,m) = obj->detection[m].counts_deV.err;
	  f2off(R_EXP,k,m) = obj->detection[m].r_exp.val;
	  f2off(R_EXPERR,k,m) = obj->detection[m].r_exp.err;
	  f2off(AB_EXP,k,m) = obj->detection[m].ab_exp.val;
	  f2off(AB_EXPERR,k,m) = obj->detection[m].ab_exp.err;
	  f2off(PHI_EXP,k,m) = obj->detection[m].phi_exp.val;
	  f2off(PHI_EXPERR,k,m) = obj->detection[m].phi_exp.err;
	  f2off(COUNTS_EXP,k,m) = obj->detection[m].counts_exp.val;
	  f2off(COUNTS_EXPERR,k,m) = obj->detection[m].counts_exp.err;
	  f2off(COUNTS_MODEL,k,m) = obj->detection[m].counts_model.val;
	  f2off(COUNTS_MODELERR,k,m) = obj->detection[m].counts_model.err;
	  f2off(TEXTURE,k,m) = obj->detection[m].texture;
	  f2off(STAR_L,k,m) = obj->detection[m].star_L;
	  f2off(EXP_L,k,m) = obj->detection[m].exp_L;
	  f2off(DEV_L,k,m) = obj->detection[m].deV_L;
	  f2off(FRACPSF,k,m) = obj->detection[m].fracPSF;
	  i2off(FLAGS,k,m) = obj->detection[m].flags;
	  i2off(FLAGS2,k,m) = obj->detection[m].flags2;
	  i2off(OTYPE,k,m) = obj->detection[m].type;
	  nProf = obj->detection[m].nProf;
	  i2off(NPROF,k,m) = nProf;
	  for (p = 0; p < nProf; p++)
	    {
	      f3off(PROFMEAN,k,m,p) = obj->detection[m].profile[p].val;
	      f3off(PROFERR,k,m,p) = obj->detection[m].profile[p].err;
	    }
	  for (p = nProf; p < MAX_PROFILES; p++)
	    {
	      f3off(PROFMEAN,k,m,p) = 0.;
	      f3off(PROFERR,k,m,p) = 0.;
	    }
	  f2off(OFFSETRA,k,m) = obj->offsetRa[m];
	  f2off(OFFSETDEC,k,m) = obj->offsetDec[m];
	  f2off(REDDENING,k,m) = obj->reddening[m];
	}
    }
  shChainCursorDel(chain, curse);

  /* Return the TBLCOL */
  return tblCol;
}


/*****************************************************************************
******************************************************************************
**<AUTO EXTRACT>
**
** ROUTINE:	taCalibObjsReadFromTbl
**
**<HTML>
**	Read objects from a TBLCOL
**	and append them to a CHAIN of TA_CALIB_OBJs.  It is assumed that
**	all objects come from the same field, and so each object has a
**	pointer set to the input field info structure.
**<p>
**	If "calibrated" = 1 then it is assumed we are reading a "tsObj" TBLCOL;
**	if 0 then it is assumed we are reading an "fpObjc" TBLCOL and only
**	those fields are read in (but they are still read into a TA_CALIB_OBJ
**	chain).
**<P>
**	NOTE: On error, new elements may have been added to the CHAIN.
**</HTML>
**</AUTO>
******************************************************************************
******************************************************************************
*/
RET_CODE
taCalibObjsReadFromTbl(TBLCOL *tblCol,        /* IN: TBLCOL to read */
		       TA_FIELD_INFO *field,  /* IN: pointer to field info */
		       int calibrated,	      /* IN: 1=tsObj, 0=fpObjc */
		       CHAIN *chain           /* MOD: CHAIN of objects */)
{
  ARRAY* array[N_OBJ_FIELDS];
  int newArray[N_OBJ_FIELDS];
  TA_CALIB_OBJ *obj = NULL;
  int fldIdx, dimIdx, k, m, p, nProf, kk;

  /* Locate each field in the TBLCOL, saving its array pointer. */
  for (fldIdx = 0; fldIdx < N_OBJ_FIELDS; fldIdx++)
    {
      /* Skip calibrated-only fields if this is an fpObjc file */
      newArray[fldIdx] = 0;
      if (calibrated == 0 && objFields[fldIdx].raw == 0) continue;

      /* Locate the field */
      if (shTblFldLoc(tblCol, -1, objFields[fldIdx].name, 0,
		      SH_CASE_INSENSITIVE, &(array[fldIdx]), NULL, NULL)
	  != SH_SUCCESS)
	{
#ifdef OPTIONAL_FIELDS
	  /* If there are optional fields that you want to be able to
	   * handle, then remove the IFDEF (and the one below) and update the
	   * list of optional fields. */
	  if (fldIdx == OBJC_FLAGS2 || fldIdx == FLAGS2 || fldIdx == ROWV ||
	      fldIdx == ROWVERR || fldIdx == COLV || fldIdx == COLVERR ||
	      fldIdx == FIRSTID || fldIdx == FIRSTLAMBDA ||
	      fldIdx == FIRSTETA || fldIdx == ROSATPOSERR ||
	      fldIdx == ROSATCPSERR || fldIdx == ROSATHR1ERR ||
	      fldIdx == ROSATHR2ERR || fldIdx == ROSATEXTLIKE ||
	      fldIdx == ROSATDETECTLIKE || fldIdx == ROSATEXPOSURE ||
	      fldIdx == RUN || fldIdx == CAMCOL || fldIdx == RERUN ||
	      fldIdx == FIELD || fldIdx == MATCHID ||
	      fldIdx == PROPERMOTIONANGLE || fldIdx == USNOBLUE ||
	      fldIdx == USNORED)
	    {
	      /* Allocate the field */
	      newArray[fldIdx] = 1;
	      array[fldIdx] = shArrayNew();
	      if (array[fldIdx] == 0)
		{
		  for (kk = 0; kk < fldIdx; kk++)
		    if (newArray[kk])
		      shArrayDel(array[kk]);
		  shErrStackPush("taCalibObjsReadFromTbl: couldn't make field");
		  return SH_GENERIC_ERROR;
		}

	      /* Set the array dimensions */
	      array[fldIdx]->dim[0] = tblCol->rowCnt;
	      array[fldIdx]->dimCnt = 1;
	      for (dimIdx = 0; dimIdx < objFields[fldIdx].dimCnt; dimIdx++)
		{
		  array[fldIdx]->dim[array[fldIdx]->dimCnt++] =
		    objFields[fldIdx].dim[dimIdx];
		}

	      /* Allocate storage for the array */
	      if (shArrayDataAlloc(array[fldIdx],objFields[fldIdx].type) 
		  != SH_SUCCESS)
		{
		  for (kk = 0; kk <= fldIdx; kk++)
		    if (newArray[kk])
		      shArrayDel(array[kk]);
		  shErrStackPush("taCalibObjsReadFromTbl: couldn't allocate array");
		  return SH_GENERIC_ERROR;
		}
	    }
	  else
	    {
#endif
	      for (kk = 0; kk < fldIdx; kk++)
		if (newArray[kk])
		  shArrayDel(array[kk]);
	      shErrStackPush("taCalibObjsReadFromTbl: couldn't locate field in FITS file");
	      return SH_GENERIC_ERROR;
#ifdef OPTIONAL_FIELDS
	    }
#endif
	}
      else
	{
	  newArray[fldIdx] = 0;
	  /* Verify its dimensions */
	  for (dimIdx = 0; dimIdx < objFields[fldIdx].dimCnt; dimIdx++)
	    {
	      if (array[fldIdx]->dim[dimIdx+1] != objFields[fldIdx].dim[dimIdx])
		{
		  for (kk = 0; kk < fldIdx; kk++)
		    if (newArray[kk])
		      shArrayDel(array[kk]);
		  shErrStackPush("taCalibObjsReadFromTbl: incorrect field dimensions in FITS file");
		  return SH_GENERIC_ERROR;
		}
	    }
	}
    }

  /* For each object ... */
  for (k = 0; k < tblCol->rowCnt; k++)
    {
      /* Create a new object and append it to the CHAIN*/
      TA_CALIB_OBJ *obj = shMalloc(sizeof(TA_CALIB_OBJ));
      if (shChainElementAddByPos(chain, obj, "TA_CALIB_OBJ", TAIL, AFTER)
	  != SH_SUCCESS)
	{
	  for (kk = 0; kk < N_OBJ_FIELDS; kk++)
	    if (newArray[kk])
	      shArrayDel(array[kk]);
	  shErrStackPush("taCalibObjsReadFromTbl: couldn't append object to chain");
	  return SH_GENERIC_ERROR;
	}

      /* Copy its contents from the TBLCOL */
      obj->field = field;
      obj->parent = ioff(PARENT,k);
      obj->id = ioff(ID,k);
      obj->nchild = ioff(NCHILD,k);
      obj->objc_type = (AR_OBJECT_TYPE) ioff(OBJC_TYPE,k);
      obj->catId = ioff(CATID,k);
      obj->objc_flags = ioff(OBJC_FLAGS,k);
      obj->objc_flags2 = ioff(OBJC_FLAGS2,k);
      obj->objc_rowc.val = foff(OBJC_ROWC,k);
      obj->objc_rowc.err = foff(OBJC_ROWCERR,k);
      obj->objc_colc.val = foff(OBJC_COLC,k);
      obj->objc_colc.err = foff(OBJC_COLCERR,k);
      obj->rowv.val = foff(ROWV,k);
      obj->rowv.err = foff(ROWVERR,k);
      obj->colv.val = foff(COLV,k);
      obj->colv.err = foff(COLVERR,k);
      if (calibrated == 1)
	{
	  obj->run = ioff(RUN,k);
	  obj->camCol = ioff(CAMCOL,k);
	  obj->rerun = ioff(RERUN,k);
	  obj->fieldId = ioff(FIELD,k);
	  obj->status = ioff(STATUS,k);
	  obj->ra = doff(RA,k);
	  obj->dec = doff(DEC,k);
	  obj->lambda = doff(LAMBDA,k);
	  obj->eta = doff(ETA,k);
	  obj->l = doff(L,k);
	  obj->b = doff(B,k);
	  obj->primTarget = ioff(PRIM_TARGET,k);
	  obj->secTarget = ioff(SEC_TARGET,k);
	  obj->properMotionMatch = ioff(PROPERMOTIONMATCH,k); 
	  obj->properMotionDelta = foff(PROPERMOTIONDELTA,k); 
	  obj->properMotion = foff(PROPERMOTION,k); 
	  obj->properMotionAngle = foff(PROPERMOTIONANGLE,k); 
	  obj->usnoBlue = foff(USNOBLUE,k); 
	  obj->usnoRed = foff(USNORED,k); 
	  obj->firstMatch = ioff(FIRSTMATCH,k); 
	  obj->firstId = ioff(FIRSTID,k); 
	  obj->firstLambda = doff(FIRSTLAMBDA,k); 
	  obj->firstEta = doff(FIRSTETA,k); 
	  obj->firstDelta = foff(FIRSTDELTA,k); 
	  obj->firstPeak = foff(FIRSTPEAK,k); 
	  obj->firstInt = foff(FIRSTINT,k); 
	  obj->firstrms = foff(FIRSTRMS,k); 
	  obj->firstmajor = foff(FIRSTMAJOR,k); 
	  obj->firstminor = foff(FIRSTMINOR,k); 
	  obj->firstpa = foff(FIRSTPA,k); 
	  obj->rosatMatch = ioff(ROSATMATCH,k); 
	  obj->rosatDelta = foff(ROSATDELTA,k); 
	  obj->rosatPosErr = foff(ROSATPOSERR,k); 
	  obj->rosatCPS.val = foff(ROSATCPS,k); 
	  obj->rosatCPS.err = foff(ROSATCPSERR,k); 
	  obj->rosatHR1.val = foff(ROSATHR1,k); 
	  obj->rosatHR1.err = foff(ROSATHR1ERR,k); 
	  obj->rosatHR2.val = foff(ROSATHR2,k); 
	  obj->rosatHR2.err = foff(ROSATHR2ERR,k); 
	  obj->rosatExt = foff(ROSATEXT,k); 
	  obj->rosatExtLike = foff(ROSATEXTLIKE,k); 
	  obj->rosatDetectLike = foff(ROSATDETECTLIKE,k); 
	  obj->rosatExposure = foff(ROSATEXPOSURE,k); 
	  obj->priority = ioff(PRIORITY,k); 
	  for (p = 0; p < 10; p++)
	    for (m = 0; m < 5; m++) obj->matchId[p][m] = i3off(MATCHID,k,p,m);
	}
      else
	{
	  obj->run = 0;
	  obj->camCol = 0;
	  obj->rerun = 0;
	  obj->fieldId = 0;
	  obj->status = 0;
	  obj->ra = 0;
	  obj->dec = 0;
	  obj->lambda = 0;
	  obj->eta = 0;
	  obj->l = 0;
	  obj->b = 0;
	  obj->primTarget = 0;
	  obj->secTarget = 0;
	  obj->properMotionMatch = 0;
	  obj->properMotionDelta = 0;
	  obj->properMotion = 0;
	  obj->properMotionAngle = 0;
	  obj->usnoBlue = 0;
	  obj->usnoRed = 0;
	  obj->firstMatch = 0;
	  obj->firstId = 0;
	  obj->firstLambda = 0;
	  obj->firstEta = 0;
	  obj->firstDelta = 0;
	  obj->firstPeak = 0;
	  obj->firstInt = 0;
	  obj->firstrms = 0;
	  obj->firstmajor = 0;
	  obj->firstminor = 0;
	  obj->firstpa = 0;
	  obj->rosatMatch = 0;
	  obj->rosatDelta = 0;
	  obj->rosatPosErr = 0;
	  obj->rosatCPS.val = 0;
	  obj->rosatCPS.err = 0;
	  obj->rosatHR1.val = 0;
	  obj->rosatHR1.err = 0;
	  obj->rosatHR2.val = 0;
	  obj->rosatHR2.err = 0;
	  obj->rosatExt = 0;
	  obj->rosatExtLike = 0;
	  obj->rosatDetectLike = 0;
	  obj->rosatExposure = 0;
	  obj->priority = 0;
	  for (p = 0; p < 10; p++)
	    for (m = 0; m < 5; m++) obj->matchId[p][m] = 0;
	}

      /* For each filter ... */
      for (m = 0; m < TA_NFILTERS; m++)
	{
	  obj->detection[m].rowc.val = f2off(ROWC,k,m);
	  obj->detection[m].rowc.err = f2off(ROWCERR,k,m);
	  obj->detection[m].colc.val = f2off(COLC,k,m);
	  obj->detection[m].colc.err = f2off(COLCERR,k,m);
	  obj->detection[m].sky.val = f2off(SKY,k,m);
	  obj->detection[m].sky.err = f2off(SKYERR,k,m);
	  obj->detection[m].psfCounts.val = f2off(PSFCOUNTS,k,m);
	  obj->detection[m].psfCounts.err = f2off(PSFCOUNTSERR,k,m);
	  obj->detection[m].fiberCounts.val = f2off(FIBERCOUNTS,k,m);
	  obj->detection[m].fiberCounts.err = f2off(FIBERCOUNTSERR,k,m);
	  obj->detection[m].petroCounts.val = f2off(PETROCOUNTS,k,m);
	  obj->detection[m].petroCounts.err = f2off(PETROCOUNTSERR,k,m);
	  obj->detection[m].petroRad.val = f2off(PETRORAD,k,m);
	  obj->detection[m].petroRad.err = f2off(PETRORADERR,k,m);
	  obj->detection[m].petroR50.val = f2off(PETROR50,k,m);
	  obj->detection[m].petroR50.err = f2off(PETROR50ERR,k,m);
	  obj->detection[m].petroR90.val = f2off(PETROR90,k,m);
	  obj->detection[m].petroR90.err = f2off(PETROR90ERR,k,m);
	  obj->detection[m].q.val = f2off(Q,k,m);
	  obj->detection[m].q.err = f2off(QERR,k,m);
	  obj->detection[m].u.val = f2off(U,k,m);
	  obj->detection[m].u.err = f2off(UERR,k,m);
	  obj->detection[m].iso_rowc.val = f2off(ISO_ROWC,k,m);
	  obj->detection[m].iso_rowc.err = f2off(ISO_ROWCERR,k,m);
	  obj->detection[m].iso_rowc.grad = f2off(ISO_ROWCGRAD,k,m);
	  obj->detection[m].iso_colc.val = f2off(ISO_COLC,k,m);
	  obj->detection[m].iso_colc.err = f2off(ISO_COLCERR,k,m);
	  obj->detection[m].iso_colc.grad = f2off(ISO_COLCGRAD,k,m);
	  obj->detection[m].iso_a.val = f2off(ISO_A,k,m);
	  obj->detection[m].iso_a.err = f2off(ISO_AERR,k,m);
	  obj->detection[m].iso_a.grad = f2off(ISO_AGRAD,k,m);
	  obj->detection[m].iso_b.val = f2off(ISO_B,k,m);
	  obj->detection[m].iso_b.err = f2off(ISO_BERR,k,m);
	  obj->detection[m].iso_b.grad = f2off(ISO_BGRAD,k,m);
	  obj->detection[m].iso_phi.val = f2off(ISO_PHI,k,m);
	  obj->detection[m].iso_phi.err = f2off(ISO_PHIERR,k,m);
	  obj->detection[m].iso_phi.grad = f2off(ISO_PHIGRAD,k,m);
	  obj->detection[m].r_deV.val = f2off(R_DEV,k,m);
	  obj->detection[m].r_deV.err = f2off(R_DEVERR,k,m);
	  obj->detection[m].ab_deV.val = f2off(AB_DEV,k,m);
	  obj->detection[m].ab_deV.err = f2off(AB_DEVERR,k,m);
	  obj->detection[m].phi_deV.val = f2off(PHI_DEV,k,m);
	  obj->detection[m].phi_deV.err = f2off(PHI_DEVERR,k,m);
	  obj->detection[m].counts_deV.val = f2off(COUNTS_DEV,k,m);
	  obj->detection[m].counts_deV.err = f2off(COUNTS_DEVERR,k,m);
	  obj->detection[m].r_exp.val = f2off(R_EXP,k,m);
	  obj->detection[m].r_exp.err = f2off(R_EXPERR,k,m);
	  obj->detection[m].ab_exp.val = f2off(AB_EXP,k,m);
	  obj->detection[m].ab_exp.err = f2off(AB_EXPERR,k,m);
	  obj->detection[m].phi_exp.val = f2off(PHI_EXP,k,m);
	  obj->detection[m].phi_exp.err = f2off(PHI_EXPERR,k,m);
	  obj->detection[m].counts_exp.val = f2off(COUNTS_EXP,k,m);
	  obj->detection[m].counts_exp.err = f2off(COUNTS_EXPERR,k,m);
	  obj->detection[m].counts_model.val = f2off(COUNTS_MODEL,k,m);
	  obj->detection[m].counts_model.err = f2off(COUNTS_MODELERR,k,m);
	  obj->detection[m].texture = f2off(TEXTURE,k,m);
	  obj->detection[m].star_L = f2off(STAR_L,k,m);
	  obj->detection[m].exp_L = f2off(EXP_L,k,m);
	  obj->detection[m].deV_L = f2off(DEV_L,k,m);
	  obj->detection[m].fracPSF = f2off(FRACPSF,k,m);
	  obj->detection[m].flags = i2off(FLAGS,k,m);
	  obj->detection[m].flags2 = i2off(FLAGS2,k,m);
	  obj->detection[m].type = (AR_OBJECT_TYPE) i2off(OTYPE,k,m);
	  nProf = i2off(NPROF,k,m);
	  obj->detection[m].nProf = nProf;
	  for (p = 0; p < nProf; p++)
	    {
	      obj->detection[m].profile[p].val = f3off(PROFMEAN,k,m,p);
	      obj->detection[m].profile[p].err = f3off(PROFERR,k,m,p);
	    }
	  for (p = nProf; p < MAX_PROFILES; p++)
	    {
	      obj->detection[m].profile[p].val = 0.;
	      obj->detection[m].profile[p].err = 0.;
	    }
	  if (calibrated == 1)
	    {
	      obj->offsetRa[m] = f2off(OFFSETRA,k,m);
	      obj->offsetDec[m] = f2off(OFFSETDEC,k,m);
	      obj->reddening[m] = f2off(REDDENING,k,m);
	    }
	  else
	    {
	      obj->offsetRa[m] = 0;
	      obj->offsetDec[m] = 0;
	      obj->reddening[m] = 0;
	    }
	}
    }

  /* Delete arrays for missing fields */
  for (kk = 0; kk < N_OBJ_FIELDS; kk++)
    if (newArray[kk] == 1)
      shArrayDel(array[kk]);

  /* Return */
  return SH_SUCCESS;
}

/*****************************************************************************
******************************************************************************
**
** ROUTINE:	taGenericChainDestroy
**
**<HTML>
**	Delete a chain, destroying (freeing the memory) for all elements
**	on the chain.  The elements are destroyed using shFree; their
**	specific destructor is not called.
**</HTML>
******************************************************************************
******************************************************************************
*/
void
taGenericChainDestroy(CHAIN *chain)
{
  CURSOR_T curse;
  void *elem;

  if (chain == NULL) return;
  curse = shChainCursorNew(chain);
  while ((elem = shChainWalk(chain, curse, NEXT)) != NULL)
    shFree(elem);
  shChainCursorDel(chain, curse);
  shChainDel(chain);
}
