44#include "ndarray/eigen.h"
50 std::shared_ptr<table::io::Persistable>
const&);
55int const SERIALIZATION_VERSION = 1;
63double const TIGHT_FITS_TOL = 0.0001;
65class SkyWcsPersistenceHelper {
72 SkyWcsPersistenceHelper(
const SkyWcsPersistenceHelper&) =
delete;
73 SkyWcsPersistenceHelper& operator=(
const SkyWcsPersistenceHelper&) =
delete;
76 SkyWcsPersistenceHelper(SkyWcsPersistenceHelper&&) =
delete;
77 SkyWcsPersistenceHelper& operator=(SkyWcsPersistenceHelper&&) =
delete;
79 explicit SkyWcsPersistenceHelper(
bool hasFitsApproximation)
81 wcs(
schema.addField<
table::Array<std::uint8_t>>(
"wcs",
"wcs string representation",
"")) {
82 if (hasFitsApproximation) {
84 "approx",
"wcs string representation of FITS approximation",
"");
92 }
catch (pex::exceptions::NotFoundError&) {
99 explicit SkyWcsFactory(std::string
const& name) : table::io::PersistableFactory(
name) {}
101 std::shared_ptr<table::io::Persistable>
read(InputArchive
const& archive,
102 CatalogVector
const& catalogs)
const override {
105 SkyWcsPersistenceHelper
keys(
catalogs.front().getSchema());
106 table::BaseRecord
const& record =
catalogs.front().front();
107 std::string stringRep = formatters::bytesToString(record.get(
keys.wcs));
108 auto result = SkyWcs::readString(stringRep);
109 if (
keys.approx.isValid()) {
110 auto bytes = record.get(
keys.approx);
111 if (!bytes.isEmpty()) {
112 auto approxStringRep = formatters::bytesToString(bytes);
113 result =
result->copyWithFitsApproximation(SkyWcs::readString(approxStringRep));
120std::string getSkyWcsPersistenceName() {
return "SkyWcs"; }
122SkyWcsFactory registration(getSkyWcsPersistenceName());
124ast::FrameDict makeSkyWcsFrameDict(TransformPoint2ToPoint2
const& pixelsToFieldAngle,
125 lsst::geom::Angle
const& orientation,
bool flipX,
126 lsst::geom::SpherePoint
const& crval,
127 std::string
const& projection =
"TAN") {
129 auto const initialWcs =
131 auto const initialFrameDict = initialWcs->getFrameDict();
132 auto const iwcToSkyMap = initialFrameDict->getMapping(
"IWC",
"SKY");
133 auto const pixelFrame = initialFrameDict->getFrame(
"PIXELS");
134 auto const iwcFrame = initialFrameDict->getFrame(
"IWC");
135 auto const skyFrame = initialFrameDict->getFrame(
"SKY");
138 ndarray::Array<double, 2, 2> fieldAngleToIwcNdArray = ndarray::allocate(2, 2);
139 asEigenMatrix(fieldAngleToIwcNdArray) = orientationAndFlipXMatrix * 180.0 /
lsst::geom::PI;
140 auto const pixelsToFieldAngleMap = pixelsToFieldAngle.getMapping();
141 auto const fieldAngleToIwcMap = ast::MatrixMap(fieldAngleToIwcNdArray);
142 auto const pixelsToIwcMap = pixelsToFieldAngleMap->then(fieldAngleToIwcMap);
143 auto finalFrameDict = ast::FrameDict(*pixelFrame, pixelsToIwcMap, *iwcFrame);
144 finalFrameDict.addFrame(
"IWC", *iwcToSkyMap, *skyFrame);
145 return finalFrameDict;
152 Eigen::Matrix2d cdMatrix;
153 double orientRad = orientation.asRadians();
154 double scaleDeg = scale.asDegrees();
155 double xmult = flipX ? 1.0 : -1.0;
156 cdMatrix(0, 0) =
std::cos(orientRad) * scaleDeg * xmult;
157 cdMatrix(0, 1) =
std::sin(orientRad) * scaleDeg;
158 cdMatrix(1, 0) = -
std::sin(orientRad) * scaleDeg * xmult;
159 cdMatrix(1, 1) =
std::cos(orientRad) * scaleDeg;
165 return src.getTransform()->then(*dstInverse);
178 double const side = 1.0;
188 auto skyLL = skyVec[0].getVector();
189 auto skyDx = skyVec[1].getVector() - skyLL;
190 auto skyDy = skyVec[2].getVector() - skyLL;
195 double skyAreaSq = skyDx.cross(skyDy).getSquaredNorm();
206 auto const crvalRad = skyFrame->getSkyRef();
213 return pixelToIwcTransform.getJacobian(pixel);
235 auto const iwcToSky = thisDict->getMapping(
"IWC",
"SKY");
236 auto const gridToSky = gridToPixel.
then(*pixelToIwc).
then(*iwcToSky);
241 os <<
"Encoding=FITS-WCS, CDMatrix=1, FitsAxisOrder=<copy>, FitsTol=" << TIGHT_FITS_TOL;
244 int const nObjectsWritten = fitsChan.write(frameSet);
245 if (nObjectsWritten == 0) {
251 header->remove(
"DATE-OBS");
252 header->remove(
"MJD-OBS");
255 bool const hasCd11 = header->exists(
"CD1_1");
256 bool const hasCd12 = header->exists(
"CD1_2");
257 bool const hasCd21 = header->exists(
"CD2_1");
258 bool const hasCd22 = header->exists(
"CD2_2");
259 if (hasCd11 || hasCd12 || hasCd21 || hasCd22) {
260 if (!hasCd11) header->set(
"CD1_1", 0.0,
"Transformation matrix element");
261 if (!hasCd12) header->set(
"CD1_2", 0.0,
"Transformation matrix element");
262 if (!hasCd21) header->set(
"CD2_1", 0.0,
"Transformation matrix element");
263 if (!hasCd22) header->set(
"CD2_2", 0.0,
"Transformation matrix element");
273 auto result = _getDirectFitsMetadata();
276 precise ?
"WCS is not directly FITS-compatible."
277 :
"WCS does not have an attached FITS approximation.");
285 if (fitsApproximation && fitsApproximation->hasFitsApproximation()) {
287 "Cannot add a FITS approximation that itself already has a FITS approximation.");
290 result->_fitsApproximation = fitsApproximation;
302 return _linearizePixelToSky(pix,
pixelToSky(pix), skyUnit);
312 return _linearizeSkyToPixel(pix,
pixelToSky(pix), skyUnit);
332 is >> shortClassName;
345 os <<
"The AST serialization was read as a " << astObjectPtr->getClassName()
346 <<
" instead of a FrameSet";
370 return std::make_unique<SkyWcs>(*
this);
376 os <<
"FITS standard SkyWcs:";
378 os <<
"Non-standard SkyWcs (Frames: ";
414 : _frameDict(frameDict), _transform(), _pixelOrigin(), _pixelScaleAtOrigin(0 *
lsst::
geom::radians) {
421 for (
auto const& domainName : domainNames) {
423 auto const frame = frameDict.
getFrame(domainName,
false);
424 if (frame->getNAxes() != 2) {
426 "Frame " + domainName +
" has " +
std::to_string(frame->getNAxes()) +
427 " axes instead of 2");
429 auto desiredClassName = domainName ==
"SKY" ?
"SkyFrame" :
"Frame";
430 if (frame->getClassName() != desiredClassName) {
432 "Frame " + domainName +
" is of type " + frame->getClassName() +
433 " instead of " + desiredClassName);
435 }
else if (domainName !=
"ACTUAL_PIXELS") {
437 throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
438 "No frame with domain " + domainName +
" found");
444 if (baseDomain !=
"ACTUAL_PIXELS" && baseDomain !=
"PIXELS") {
445 throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
446 "Base frame has domain " + baseDomain +
" instead of PIXELS or ACTUAL_PIXELS");
451 if (currentDomain !=
"SKY") {
452 throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
453 "Current frame has domain " + currentDomain +
" instead of SKY");
456 return frameDict.
copy();
460 lsst::geom::SpherePoint
const& coord,
461 lsst::geom::AngleUnit
const& skyUnit)
const {
465 const double side = 1.0;
471 m(0, 0) = dsky10.first.asAngularUnits(skyUnit) / side;
472 m(0, 1) = dsky01.first.asAngularUnits(skyUnit) / side;
473 m(1, 0) = dsky10.second.asAngularUnits(skyUnit) / side;
474 m(1, 1) = dsky01.second.asAngularUnits(skyUnit) / side;
476 Eigen::Vector2d sky00v;
477 sky00v << sky00.getX(), sky00.getY();
478 Eigen::Vector2d pix00v;
479 pix00v << pix00.getX(), pix00.getY();
481 return lsst::geom::AffineTransform(m, (sky00v - m * pix00v));
485 lsst::geom::SpherePoint
const& coord,
486 lsst::geom::AngleUnit
const& skyUnit)
const {
487 lsst::geom::AffineTransform inverse = _linearizePixelToSky(pix00, coord, skyUnit);
493 double const dx = 1000;
511 bool modifyActualPixels) {
512 auto const pixelMapping = pixelTransform.
getMapping();
513 auto oldFrameDict = wcs.getFrameDict();
514 bool const hasActualPixels = oldFrameDict->hasDomain(
"ACTUAL_PIXELS");
515 auto const pixelFrame = oldFrameDict->getFrame(
"PIXELS",
false);
516 auto const iwcFrame = oldFrameDict->getFrame(
"IWC",
false);
517 auto const skyFrame = oldFrameDict->getFrame(
"SKY",
false);
518 auto const oldPixelToIwc = oldFrameDict->getMapping(
"PIXELS",
"IWC");
519 auto const iwcToSky = oldFrameDict->getMapping(
"IWC",
"SKY");
523 if (hasActualPixels) {
524 auto const actualPixelFrame = oldFrameDict->getFrame(
"ACTUAL_PIXELS",
false);
525 auto const oldActualPixelToPixels = oldFrameDict->getMapping(
"ACTUAL_PIXELS",
"PIXELS");
527 if (modifyActualPixels) {
528 newActualPixelsToPixels = pixelMapping->then(*oldActualPixelToPixels).simplified();
529 newPixelToIwc = oldPixelToIwc;
531 newActualPixelsToPixels = oldActualPixelToPixels;
532 newPixelToIwc = pixelMapping->then(*oldPixelToIwc).simplified();
536 newFrameDict->addFrame(
"PIXELS", *newPixelToIwc, *iwcFrame);
538 newPixelToIwc = pixelMapping->then(*oldPixelToIwc).simplified();
541 newFrameDict->addFrame(
"IWC", *iwcToSky, *skyFrame);
550 Eigen::Matrix2d
const& cdMatrix,
std::string const& projection) {
558 auto frameDict = makeSkyWcsFrameDict(pixelsToFieldAngle, orientation, flipX, boresight, projection);
563 Eigen::Matrix2d
const& cdMatrix, Eigen::MatrixXd
const& sipA,
564 Eigen::MatrixXd
const& sipB) {
570 Eigen::Matrix2d
const& cdMatrix, Eigen::MatrixXd
const& sipA,
571 Eigen::MatrixXd
const& sipB, Eigen::MatrixXd
const& sipAp,
572 Eigen::MatrixXd
const& sipBp) {
579 auto iwcToSky = wcs.getFrameDict()->getMapping(
"IWC",
"SKY");
589 os << wcs.toString();
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Channel provides input/output of AST objects.
std::shared_ptr< Object > read()
Read an object from a channel.
A specialized form of Channel which reads and writes FITS header cards.
A FrameSet whose frames can be referenced by domain name.
bool hasDomain(std::string const &domain) const
Return True if a frame in this FrameDict has the specified domain.
std::shared_ptr< Mapping > getMapping(int from, std::string const &to) const
Variant of FrameSet::getMapping with the second frame specified by domain.
std::shared_ptr< FrameDict > copy() const
Return a deep copy of this object.
std::set< std::string > getAllDomains() const
Get the domain names for all contained Frames (excluding frames with empty or defaulted domain names)...
std::shared_ptr< Frame > getFrame(std::string const &domain, bool copy=true) const
Variant of getFrame(int, bool) where the frame is specified by domain name.
Frame is used to represent a coordinate system.
A FrameSet consists of a set of one or more Frames (which describe coordinate systems),...
static int constexpr CURRENT
index of current frame
static int constexpr BASE
index of base frame
SeriesMap then(Mapping const &next) const
Return a series compound mapping this(first(input)) containing shallow copies of the original.
void show(std::ostream &os, bool showComments=true) const
Print a textual description the object to an ostream.
ShiftMap is a linear Mapping which shifts each axis by a specified constant value.
A stream for ast::Channel.
String-based source and sink for channels.
A WinMap is a linear Mapping which transforms a rectangular window in one coordinate system into a si...
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
std::shared_ptr< SkyWcs > copyAtShiftedPixelOrigin(lsst::geom::Extent2D const &shift) const
Return a copy of this SkyWcs with the pixel origin shifted by the specified amount.
static std::shared_ptr< SkyWcs > readStream(std::istream &is)
Deserialize a SkyWcs from an input stream.
std::shared_ptr< const ast::FrameDict > getFrameDict() const
Get the contained FrameDict.
lsst::geom::SpherePoint pixelToSky(lsst::geom::Point2D const &pixel) const
Compute sky position(s) from pixel position(s)
lsst::geom::AffineTransform linearizeSkyToPixel(lsst::geom::SpherePoint const &coord, lsst::geom::AngleUnit const &skyUnit) const
Return the local linear approximation to skyToPixel at a point given in sky coordinates.
std::string toString() const override
Create a string representation of this object.
bool isFlipped() const
Does the WCS follow the convention of North=Up, East=Left?
std::shared_ptr< typehandling::Storable > cloneStorable() const override
Create a new SkyWcs that is a copy of this one.
lsst::geom::Angle getPixelScale() const
Get the pixel scale at the pixel origin.
std::shared_ptr< SkyWcs > getTanWcs(lsst::geom::Point2D const &pixel) const
Get a local TAN WCS approximation to this WCS at the specified pixel position.
lsst::geom::Point2D getPixelOrigin() const
Get the pixel origin, in pixels, using the LSST convention.
void writeStream(std::ostream &os) const
Serialize this SkyWcs to an output stream.
std::string writeString() const
Serialize this SkyWcs to a string, using the same format as writeStream.
Eigen::Matrix2d getCdMatrix() const
Get the 2x2 CD matrix at the pixel origin.
SkyWcs(SkyWcs const &)=default
lsst::geom::AffineTransform linearizePixelToSky(lsst::geom::SpherePoint const &coord, lsst::geom::AngleUnit const &skyUnit) const
Return the local linear approximation to pixelToSky at a point given in sky coordinates.
std::shared_ptr< const TransformPoint2ToSpherePoint > getTransform() const
Get a TransformPoint2ToSpherePoint that transforms pixels to sky in the forward direction and sky to ...
lsst::geom::Point2D skyToPixel(lsst::geom::SpherePoint const &sky) const
Compute pixel position(s) from sky position(s)
static std::string getShortClassName()
bool isFits() const
Return true getFitsMetadata(true) will succeed, false if not.
bool hasFitsApproximation() const
Does this SkyWcs have an approximate SkyWcs that can be represented as standard FITS WCS?
std::shared_ptr< SkyWcs > getFitsApproximation() const
Return FITS SkyWcs that approximates this one.
std::string getPythonModule() const override
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
std::string getPersistenceName() const override
Return the unique name used to persist this object and look up its factory.
lsst::geom::SpherePoint getSkyOrigin() const
Get the sky origin, the celestial fiducial point.
std::shared_ptr< SkyWcs > copyWithFitsApproximation(std::shared_ptr< SkyWcs > fitsApproximation) const
Return a copy of this SkyWcs with the given FITS approximation.
static std::shared_ptr< SkyWcs > readString(std::string &str)
Deserialize a SkyWcs from a string, using the same format as readStream.
std::shared_ptr< daf::base::PropertyList > getFitsMetadata(bool precise=false) const
Return the WCS as FITS WCS metadata.
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
bool equals(typehandling::Storable const &other) const noexcept override
Compare this object to another Storable.
bool operator==(SkyWcs const &other) const
Equality is based on the string representations being equal.
Tag types used to declare specialized field types.
std::shared_ptr< RecordT > addNew()
Create a new record, add it to the end of the catalog, and return a pointer to it.
A class used as a handle to a particular field in a table.
Defines the fields and offsets for a table.
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
static std::shared_ptr< T > dynamicCast(std::shared_ptr< Persistable > const &ptr)
Dynamically cast a shared_ptr.
A base class for factory classes used to reconstruct objects from records.
io::OutputArchiveHandle OutputArchiveHandle
Interface supporting iteration over heterogenous containers.
static bool singleClassEquals(T const &lhs, Storable const &rhs)
Test if a Storable is of a particular class and equal to another object.
Class for storing generic metadata.
A class representing an angle.
constexpr double asArcseconds() const noexcept
Return an Angle's value in arcseconds.
A class used to convert scalar POD types such as double to Angle.
Point in an unspecified spherical coordinate system.
std::pair< Angle, Angle > getTangentPlaneOffset(SpherePoint const &other) const
Get the offset from a tangent plane centered at this point to another point.
Point2D getPosition(AngleUnit unit) const noexcept
Return longitude, latitude as a Point2D object.
Reports errors in the logical structure of the program.
Reports errors that are due to events beyond the control of the program.
Reports errors from accepting an object of an unexpected or inappropriate type.
std::shared_ptr< daf::base::PropertyList > getPropertyListFromFitsChan(ast::FitsChan &fitsChan)
Copy values from an AST FitsChan into a PropertyList.
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
std::shared_ptr< daf::base::PropertyList > makeSimpleWcsMetadata(lsst::geom::Point2D const &crpix, lsst::geom::SpherePoint const &crval, Eigen::Matrix2d const &cdMatrix, std::string const &projection="TAN")
Make FITS metadata for a simple FITS WCS (one with no distortion).
std::shared_ptr< TransformPoint2ToPoint2 > getPixelToIntermediateWorldCoords(SkyWcs const &wcs, bool simplify=true)
Return a transform from pixel coordinates to intermediate world coordinates.
std::shared_ptr< SkyWcs > makeTanSipWcs(lsst::geom::Point2D const &crpix, lsst::geom::SpherePoint const &crval, Eigen::Matrix2d const &cdMatrix, Eigen::MatrixXd const &sipA, Eigen::MatrixXd const &sipB)
Construct a TAN-SIP SkyWcs with forward SIP distortion terms and an iterative inverse.
std::ostream & operator<<(std::ostream &os, GenericEndpoint const &endpoint)
Print "GenericEndpoint(_n_)" to the ostream where _n_ is the number of axes, e.g. "GenericAxes(4)".
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
Make a WCS CD matrix.
std::shared_ptr< TransformPoint2ToSpherePoint > getIntermediateWorldCoordsToSky(SkyWcs const &wcs, bool simplify=true)
Return a transform from intermediate world coordinates to sky.
std::shared_ptr< daf::base::PropertyList > makeTanSipMetadata(lsst::geom::Point2D const &crpix, lsst::geom::SpherePoint const &crval, Eigen::Matrix2d const &cdMatrix, Eigen::MatrixXd const &sipA, Eigen::MatrixXd const &sipB)
Make metadata for a TAN-SIP WCS without inverse matrices.
std::shared_ptr< SkyWcs > makeModifiedWcs(TransformPoint2ToPoint2 const &pixelTransform, SkyWcs const &wcs, bool modifyActualPixels)
Create a new SkyWcs whose pixels are transformed by pixelTransform, as described below.
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
A Transform obtained by putting two SkyWcs objects "back to back".
std::shared_ptr< SkyWcs > makeFlippedWcs(SkyWcs const &wcs, bool flipLR, bool flipTB, lsst::geom::Point2D const ¢er)
Return a copy of a FITS-WCS with pixel positions flipped around a specified center.
CatalogT< BaseRecord > BaseCatalog
AngleUnit constexpr degrees
constant with units of degrees
Extent< double, 2 > Extent2D
AngleUnit constexpr radians
constant with units of radians
Point< double, 2 > Point2D
double constexpr PI
The ratio of a circle's circumference to diameter.
T dynamic_pointer_cast(T... args)
std::shared_ptr< table::io::Persistable > read(table::io::InputArchive const &archive, table::io::CatalogVector const &catalogs) const override