/** @file  psAstrometry.c
*
*  @brief This file defines the basic types for astronomical coordinate
*  transformation
*
*  @ingroup AstroImage
*
*  @author GLG, MHPCC
*
* XXX: We should review the extent of the warning messages on these functions
* when the transformations are not successful.
* 
* XXX: Should we implement non-linear cell->chip transforms?
* 
*  @version $Revision: 1.10.2.1 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-12-12 21:52:17 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

/******************************************************************************/
/*  INCLUDE FILES                                                             */
/******************************************************************************/
#include <string.h>
#include <math.h>
#include "pslib.h"
#include "pmAstrometry.h"

/*****************************************************************************
checkValidImageCoords(): this is a private function which simply determines if
the supplied x,y coordinates are in the range for the supplied psImage.
 
XXX: What about col0 and row0
XXX: This should return a psBool.
XXX: Macro this for speed.
 *****************************************************************************/
static psS32 checkValidImageCoords(
    double x,
    double y,
    psImage* tmpImage)
{
    PS_ASSERT_IMAGE_NON_NULL(tmpImage, 0);

    // The FLT_EPSILON is because -0.0 was failing this.
    if (((x+FLT_EPSILON) < 0.0) || (x > (double)tmpImage->numCols) ||
            ((y+FLT_EPSILON) < 0.0) || (y > (double)tmpImage->numRows)) {
        return (0);
    }

    return (1);
}

/******************************************************************************
 *****************************************************************************/
#define PS_FREE_HIERARCHY 1
static void readoutFree(pmReadout *readout)
{
    if (readout != NULL) {
        psFree(readout->image);
        psFree(readout->mask);
        psFree(readout->weight);
        //
        // XXX: Not sure if this is the right way to do things.  Currently the psListAdd()
        // increase the memory reference counter to the list data.  So, we
        // iterate through the list, and decrement the reference counters.
        //
        if (1) {
            if ((readout->bias != NULL) && (readout->bias->head != NULL)) {
                psListElem *tmpElem = (psListElem *) readout->bias->head;
                while (NULL != tmpElem) {
                    psMemDecrRefCounter((psImage *) tmpElem->data);
                    tmpElem = tmpElem->next;
                }
            }
        }
        psFree(readout->bias);
        psFree(readout->analysis);
        psFree(readout->parent);
    }
}

static void cellFree(pmCell *cell)
{
    if (cell != NULL) {
        psFree(cell->toChip);
        psFree(cell->toFPA);
        psFree(cell->toSky);
        psFree(cell->concepts);
        psFree(cell->analysis);
        //
        // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
        // in order to avoid memory reference counter problems.
        //
        for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
            pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
            tmpReadout->parent = NULL;
            if (PS_FREE_HIERARCHY == 1) {
                psFree(tmpReadout);
            }
        }
        psFree(cell->readouts);
        psFree(cell->parent);
        psFree(cell->private);
    }
}

static void chipFree(pmChip* chip)
{
    if (chip != NULL) {
        psFree(chip->toFPA);
        psFree(chip->fromFPA);
        psFree(chip->concepts);
        psFree(chip->analysis);
        //
        // Set the parent to NULL in all chip->cells before psFree(chip->cells)
        // in order to avoid memory reference counter problems.
        //
        for (psS32 i = 0 ; i < chip->cells->n ; i++) {
            pmCell *tmpCell = (pmCell *) chip->cells->data[i];
            tmpCell->parent = NULL;
            if (PS_FREE_HIERARCHY == 1) {
                psFree(tmpCell);
            }
        }
        psFree(chip->cells);
        psFree(chip->parent);
        psFree(chip->private);
    }
}


static void FPAFree(pmFPA *fpa)
{
    if (fpa != NULL) {
        psFree(fpa->fromTangentPlane);
        psFree(fpa->toTangentPlane);
        psFree(fpa->projection);
        psFree(fpa->concepts);
        psFree(fpa->analysis);
        psFree(fpa->camera);
        //
        // Set the parent to NULL in all fpa->chips before psFree(fpa->chips)
        // in order to avoid memory reference counter problems.
        //
        for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
            pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
            tmpChip->parent = NULL;
            if (PS_FREE_HIERARCHY == 1) {
                psFree(tmpChip);
            }
        }
        psFree(fpa->chips);
        psFree(fpa->private);
        psFree(fpa->phu);
    }
}

// XXX: Verify these default values for row0, col0, rowBins, colBins
pmReadout *pmReadoutAlloc(pmCell *cell)
{
    pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));

    tmpReadout->col0 = -1;
    tmpReadout->row0 = -1;
    tmpReadout->colBins = -1;
    tmpReadout->rowBins = -1;
    tmpReadout->image = NULL;
    tmpReadout->mask = NULL;
    tmpReadout->weight = NULL;
    tmpReadout->bias = NULL;
    tmpReadout->analysis = psMetadataAlloc();
    tmpReadout->parent = cell;
    if (cell != NULL) {
        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
    }
    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
    return(tmpReadout);
}

// XXX: Verify these default values for row0, col0.
pmCell *pmCellAlloc(
    pmChip *chip,
    psMetadata *cameradata,
    psString name)
{
    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));

    tmpCell->col0 = -1;
    tmpCell->row0 = -1;
    tmpCell->toChip = NULL;
    tmpCell->toFPA = NULL;
    tmpCell->toSky = NULL;
    tmpCell->concepts = psMetadataAlloc();
    psBool rc = psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name);
    if (rc == false) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
    }
    tmpCell->camera = cameradata;
    tmpCell->analysis = NULL;
    tmpCell->readouts = psArrayAlloc(0);
    tmpCell->parent = chip;
    if (chip != NULL) {
        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
    }
    tmpCell->valid = false;
    tmpCell->private = NULL;

    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
    return(tmpCell);
}

// XXX: Verify these default values for row0, col0.
pmChip *pmChipAlloc(
    pmFPA *fpa,
    psString name)
{
    pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));

    tmpChip->col0 = -1;
    tmpChip->row0 = -1;
    tmpChip->toFPA = NULL;
    tmpChip->fromFPA = NULL;
    tmpChip->concepts = psMetadataAlloc();
    psBool rc = psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name);
    if (rc == false) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME to metadata.\n");
    }
    tmpChip->analysis = NULL;
    tmpChip->cells = psArrayAlloc(0);
    tmpChip->parent = fpa;
    if (fpa != NULL) {
        fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
    }
    tmpChip->valid = false;
    tmpChip->private = NULL;

    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
    return(tmpChip);
}

pmFPA *pmFPAAlloc(const psMetadata *camera)
{
    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));

    tmpFPA->fromTangentPlane = NULL;
    tmpFPA->toTangentPlane = NULL;
    tmpFPA->projection = NULL;
    tmpFPA->concepts = psMetadataAlloc();
    tmpFPA->analysis = NULL;
    tmpFPA->camera = camera;
    tmpFPA->chips = psArrayAlloc(0);
    tmpFPA->private = NULL;
    tmpFPA->phu = NULL;

    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
    return(tmpFPA);
}

static psBool cellCheckParents(pmCell *cell)
{
    if (cell == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpReadout, false);
        if (tmpReadout->parent != cell) {
            tmpReadout->parent = cell;
            flag = false;
        }
    }
    return(flag);
}

static psBool chipCheckParents(pmChip *chip)
{
    if (chip == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < chip->cells->n ; i++) {
        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpCell, false);
        if (tmpCell->parent != chip) {
            tmpCell->parent = chip;
            flag = false;
        }

        flag &= cellCheckParents(tmpCell);
    }
    return(flag);
}

psBool pmFPACheckParents(pmFPA *fpa)
{
    if (fpa == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpChip, false);
        if (tmpChip->parent != fpa) {
            tmpChip->parent = fpa;
            flag = false;
        }

        flag &= chipCheckParents(tmpChip);
    }
    return(flag);
}



/*****************************************************************************/
/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
/*****************************************************************************/

pmCell* pmCellInFPA(
    const psPlane* fpaCoord,
    const pmFPA* FPA)
{
    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(FPA, NULL);

    pmChip* tmpChip = NULL;
    psPlane chipCoord;
    pmCell* outCell = NULL;

    // Determine which chip contains the fpaCoords.
    tmpChip = pmChipInFPA(fpaCoord, FPA);
    if (tmpChip == NULL) {
        return(NULL);
    }

    // Convert to those chip coordinates.
    psPlane *rc = pmCoordFPAToChip(&chipCoord, fpaCoord, tmpChip);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine Chip coords.\n");
        return(NULL);
    }

    // Determine which cell contains those chip coordinates.
    outCell = pmCellInChip(&chipCoord, tmpChip);
    if (outCell == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
        return(NULL);
    }

    return (outCell);
}

pmChip* pmChipInFPA(
    const psPlane* fpaCoord,
    const pmFPA* FPA)
{
    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
    PS_ASSERT_PTR_NON_NULL(FPA->chips, NULL);

    psArray* chips = FPA->chips;
    psS32 nChips = chips->n;
    psPlane chipCoord;
    pmCell *tmpCell = NULL;

    //
    // Loop through every chip in this FPA.  Convert the original FPA
    // coordinates to chip coordinates for that chip.  Then, determine if any
    // cells in that chip contain those chip coordinates.
    // XXX: Depending on the number of chips, and their topology, there may be
    // a much more efficient way of doing this.
    //
    for (psS32 i = 0; i < nChips; i++) {
        pmChip* tmpChip = chips->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpChip, NULL);
        PS_ASSERT_PTR_NON_NULL(tmpChip->fromFPA, NULL);

        psPlaneTransformApply(&chipCoord, tmpChip->fromFPA, fpaCoord);

        tmpCell = pmCellInChip(&chipCoord, tmpChip);
        if (tmpCell != NULL) {
            return(tmpChip);
        }
    }

    psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the chip.\n");
    return (NULL);
}


pmCell* pmCellInChip(
    const psPlane* chipCoord,
    const pmChip* chip)
{
    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(chip, NULL);

    psPlane cellCoord;
    psArray* cells;

    cells = chip->cells;
    if (cells == NULL) {
        return NULL;
    }

    //
    // We loop over each cell in the chip.  We transform the chipCoord into
    // a cellCoord for that cell and determine if that cellCoord is valid.
    // If so, then we return that cell.
    // XXX: Depending on the number of cells, and their topology, there may be
    // a much more efficient way of doing this.
    //
    for (psS32 i = 0; i < cells->n; i++) {
        pmCell* tmpCell = (pmCell* ) cells->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpCell, NULL);

        psPlaneTransform *chipToCell = NULL;
        if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
            chipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
        } else {
            psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
            //chipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
            chipToCell = NULL;
        }
        if (chipToCell == NULL) {
            psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
            return(NULL);
        }
        psArray* readouts = tmpCell->readouts;

        if (readouts != NULL) {
            for (psS32 j = 0; j < readouts->n; j++) {
                pmReadout* tmpReadout = readouts->data[j];
                PS_ASSERT_READOUT_NON_NULL(tmpReadout, NULL);

                psPlaneTransformApply(&cellCoord,
                                      chipToCell,
                                      chipCoord);

                if (checkValidImageCoords(cellCoord.x,
                                          cellCoord.y,
                                          tmpReadout->image)) {
                    psFree(chipToCell);
                    return (tmpCell);
                }
            }
        }
        psFree(chipToCell);
    }

    //psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
    return (NULL);
}


psPlane* pmCoordCellToFPA(
    psPlane* fpaCoord,
    const psPlane* cellCoord,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);

    psPlane *rc = psPlaneTransformApply(fpaCoord, cell->toFPA, cellCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to FPA coords.\n");
    }
    return(rc);
}


psPlane* pmCoordChipToFPA(
    psPlane* outCoord,
    const psPlane* inCoord,
    const pmChip* chip)
{
    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(chip, NULL);

    psPlane *rc = psPlaneTransformApply(outCoord, chip->toFPA, inCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform chip coords to FPA coords.\n");
    }
    return(rc);
}


psPlane* pmCoordFPAToChip(
    psPlane* chipCoord,
    const psPlane* fpaCoord,
    const pmChip* chip)
{
    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(chip, NULL);
    PS_ASSERT_PTR_NON_NULL(chip->fromFPA, NULL);

    psPlane *rc = psPlaneTransformApply(chipCoord, chip->fromFPA, fpaCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to Chip coords.\n");
    }
    return(rc);
}

psPlane* pmCoordCellToChip(
    psPlane* outCoord,
    const psPlane* inCoord,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);

    psPlane *rc = psPlaneTransformApply(outCoord, cell->toChip, inCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Cell coords to Chip coords.\n");
    }
    return(rc);
}

psPlane* pmCoordChipToCell(
    psPlane* cellCoord,
    const psPlane* chipCoord,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);

    pmCell *tmpCell = pmCellInChip(chipCoord, cell->parent);
    if (tmpCell == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the proper cell.\n");
        return(NULL);
    }

    psPlaneTransform *tmpChipToCell = NULL;
    PS_ASSERT_PTR_NON_NULL(tmpCell->toChip, NULL);
    if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
        tmpChipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
    } else {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
        // XXX: tmpChipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
        tmpChipToCell = NULL;
    }
    if (tmpChipToCell == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
        return(NULL);
    }

    psPlane *rc = psPlaneTransformApply(cellCoord, tmpChipToCell, chipCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Chip coords to Cell coords.\n");
    }
    psFree(tmpChipToCell);
    return(rc);
}

psPlane* pmCoordFPAToTP(
    psPlane* outCoord,
    const psPlane* inCoord,
    double color,
    double magnitude,
    const pmFPA* fpa)
{
    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(fpa, NULL);

    psPlane *rc = psPlaneDistortApply(outCoord, fpa->toTangentPlane, inCoord, color, magnitude);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to tangent plane coords.\n");
    }
    return(rc);
}

psPlane* pmCoordTPToFPA(
    psPlane* fpaCoord,
    const psPlane* tpCoord,
    double color,
    double magnitude,
    const pmFPA* fpa)
{
    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
    PS_ASSERT_PTR_NON_NULL(fpa->fromTangentPlane, NULL);

    psPlane *rc = psPlaneDistortApply(fpaCoord, fpa->fromTangentPlane, tpCoord, color, magnitude);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to FPA coords.\n");
    }
    return(rc);
}


/*****************************************************************************
XXXDeproject(outSphere, coord, projection): This private routine is a wrapper
for p_psDeproject().  The reason: p_psDeproject() and p_psProject() combined
do not seem to produce the original coordinates when they even though they
should.  XXXDeproject() simply negates the ->r and ->d members of the output
psSphere if the input ->y is larger than 0.0.  I don't know why it works.
 
I'm guessing the p_psProject() and p_psDeproject() functions have bugs.
 
XXX: It appears that p_psProject() and p_psDeproject() have been fixed.
Remove this.
 *****************************************************************************/
psSphere* XXXDeproject(
    psSphere *outSphere,
    const psPlane* coord,
    const psProjection* projection)
{
    psSphere *rc = p_psDeproject(outSphere, coord, projection);

    if (coord->y >= 0.0) {
        rc->d = -rc->d;
        rc->r = -rc->r;
    }

    return(rc);
}

/*****************************************************************************
  *****************************************************************************/
psSphere* pmCoordTPToSky(
    psSphere* outSphere,
    const psPlane* tpCoord,
    const psProjection *projection)
{
    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(projection, NULL);

    //    psSphere *rc = XXXDeproject(outSphere, tpCoord, projection);
    psSphere *rc = p_psDeproject(outSphere, tpCoord, projection);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to sky coords.\n");
    }
    return(rc);
}

/*****************************************************************************
 *****************************************************************************/
psPlane* pmCoordSkyToTP(
    psPlane* tpCoord,
    const psSphere* in,
    const psProjection *projection)
{
    PS_ASSERT_PTR_NON_NULL(in, NULL);
    PS_ASSERT_PTR_NON_NULL(projection, NULL);

    psPlane *rc = p_psProject(tpCoord, in, projection);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to tangent plane coords.\n");
    }
    return(rc);
}

/*****************************************************************************
 *****************************************************************************/
psSphere* pmCoordCellToSky(
    psSphere* skyCoord,
    const psPlane* cellCoord,
    double color,
    double magnitude,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->toFPA, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->toTangentPlane, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->projection, NULL);
    psPlane fpaCoord;
    psPlane tpCoord;
    psPlane *rc;
    pmFPA* parFPA = (cell->parent)->parent;

    // Convert the input cell coordinates to FPA coordinates.
    rc = psPlaneTransformApply(&fpaCoord, cell->toFPA, cellCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to FPA coords.\n");
        return(NULL);
    }

    // Convert the FPA coordinates to tangent plane Coordinates.
    rc = psPlaneDistortApply(&tpCoord, parFPA->toTangentPlane, &fpaCoord, color, magnitude);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform FPA coords to tangent plane coords.\n");
        return(NULL);
    }

    // Convert the tangent plane Coordinates to sky coordinates.
    psSphere *rc2 = pmCoordTPToSky(skyCoord, &tpCoord, parFPA->projection);
    if (rc2 == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to sky coords.\n");
    }

    return(rc2);
}

/*****************************************************************************
 *****************************************************************************/
psPlane* pmCoordSkyToCell(
    psPlane* cellCoord,
    const psSphere* skyCoord,
    float color,
    float magnitude,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
    pmChip *parChip = cell->parent;
    pmFPA *parFPA = parChip->parent;
    psPlane tpCoord;
    psPlane fpaCoord;
    psPlane chipCoord;
    psPlane *rc;

    // Convert the skyCoords to tangent plane coords.
    rc = pmCoordSkyToTP(&tpCoord, skyCoord, parFPA->projection);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine tangent plane coords.\n");
        return(NULL);
    }

    // Convert the tangent plane coords to FPA coords.
    rc = pmCoordTPToFPA(&fpaCoord, &tpCoord, color, magnitude, parFPA);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine FPA coords.\n");
        return(NULL);
    }

    // Convert the FPA coords to chip coords.
    rc = pmCoordFPAToChip(&chipCoord, &fpaCoord, parChip);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine chip coords.\n");
        return(NULL);
    }

    // Convert the chip coords to cell coords.
    rc = pmCoordChipToCell(cellCoord, &chipCoord, cell);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine cell coords.\n");
        return(NULL);
    }

    return (cellCoord);
}

/*****************************************************************************
 *****************************************************************************/
psSphere* pmCoordCellToSkyQuick(
    psSphere* outSphere,
    const psPlane* cellCoord,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
    psPlane outPlane;
    psPlane *rc;
    rc = psPlaneTransformApply(&outPlane, cell->toSky, cellCoord);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to sky coords.\n");
        return(NULL);
    }

    psSphere *out = outSphere;
    if (out == NULL) {
        out = psSphereAlloc();
    }
    out->r = outPlane.y;
    out->d = outPlane.x;

    return(out);
}

/*****************************************************************************
 *****************************************************************************/
psPlane* pmCoordSkyToCellQuick(
    psPlane* cellCoord,
    const psSphere* skyCoord,
    const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
    psPlane skyPlane;
    skyPlane.y = skyCoord->r;
    skyPlane.x = skyCoord->d;

    psPlane *rc = psPlaneTransformApply(cellCoord, cell->toSky, &skyPlane);
    if (rc == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to cell coords.\n");
    }
    return(cellCoord);
}

/*****************************************************************************
 *****************************************************************************/
bool pmFPASelectChip(
    pmFPA *fpa,
    int chipNum)
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);
    if ((fpa->chips == NULL) || (chipNum >= fpa->chips->n)) {
        return(false);
    }
    psBool rc = true;

    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
        if (tmpChip == NULL) {
            rc = false;
        } else {
            if (i == chipNum) {
                tmpChip->valid = true;
            } else {
                tmpChip->valid = false;
            }
        }
    }

    return(rc);
}


/*****************************************************************************
XXX: The SDRS is ambiguous on a few things:
    Whether or not the other chips should be set valid=true.
    Should we return the number of chip valid=true before or after they're set,
 *****************************************************************************/
/**
 * 
 * pmFPAExcludeChip shall set valid to false only for the specified chip
 * number (chipNum). In the event that the specified chip number does not exist
 * within the fpa, the function shall generate a warning, and perform no action.
 * The function shall return the number of chips within the fpa that have valid
 * set to true.
 *  
 */
int pmFPAExcludeChip(
    pmFPA *fpa,
    int chipNum)
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);

    if (fpa->chips == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: fpa->chips == NULL\n");
        return(0);
    }
    if ((chipNum >= fpa->chips->n) || (NULL == (pmChip *) fpa->chips->data[chipNum])) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: the specified chip (%d) does not exist.\n", chipNum);
        return(0);
    }

    psS32 numChips = 0;
    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
        if (tmpChip != NULL) {
            if (i == chipNum) {
                tmpChip->valid = false;
            } else {
                tmpChip->valid = true;
                numChips++;
            }
        }
    }

    return(numChips);
}
