44#include "ndarray/eigen.h"
50 std::shared_ptr<table::io::Persistable>
const&);
55int const SERIALIZATION_VERSION = 1;
62double const TIGHT_FITS_TOL = 0.0001;
64class SkyWcsPersistenceHelper {
71 SkyWcsPersistenceHelper(
const SkyWcsPersistenceHelper&) =
delete;
72 SkyWcsPersistenceHelper& operator=(
const SkyWcsPersistenceHelper&) =
delete;
75 SkyWcsPersistenceHelper(SkyWcsPersistenceHelper&&) =
delete;
76 SkyWcsPersistenceHelper& operator=(SkyWcsPersistenceHelper&&) =
delete;
78 explicit SkyWcsPersistenceHelper(
bool hasFitsApproximation)
80 wcs(schema.addField<
table::Array<std::uint8_t>>(
"wcs",
"wcs string representation",
"")) {
81 if (hasFitsApproximation) {
83 "approx",
"wcs string representation of FITS approximation",
"");
88 : schema(schema), wcs(schema[
"wcs"]), approx() {
90 approx = schema[
"approx"];
91 }
catch (pex::exceptions::NotFoundError&) {
98 explicit SkyWcsFactory(std::string
const& name) : table::io::PersistableFactory(name) {}
100 std::shared_ptr<table::io::Persistable>
read(InputArchive
const& archive,
101 CatalogVector
const& catalogs)
const override {
104 SkyWcsPersistenceHelper
keys(catalogs.front().getSchema());
105 table::BaseRecord
const& record = catalogs.front().front();
106 std::string stringRep = formatters::bytesToString(record.get(
keys.wcs));
107 auto result = SkyWcs::readString(stringRep);
108 if (
keys.approx.isValid()) {
109 auto bytes = record.get(
keys.approx);
110 if (!bytes.isEmpty()) {
111 auto approxStringRep = formatters::bytesToString(bytes);
112 result = result->copyWithFitsApproximation(SkyWcs::readString(approxStringRep));
119std::string getSkyWcsPersistenceName() {
return "SkyWcs"; }
121SkyWcsFactory registration(getSkyWcsPersistenceName());
123ast::FrameDict makeSkyWcsFrameDict(TransformPoint2ToPoint2
const& pixelsToFieldAngle,
124 lsst::geom::Angle
const& orientation,
bool flipX,
125 lsst::geom::SpherePoint
const& crval,
126 std::string
const& projection =
"TAN") {
128 auto const initialWcs =
130 auto const initialFrameDict = initialWcs->getFrameDict();
131 auto const iwcToSkyMap = initialFrameDict->getMapping(
"IWC",
"SKY");
132 auto const pixelFrame = initialFrameDict->getFrame(
"PIXELS");
133 auto const iwcFrame = initialFrameDict->getFrame(
"IWC");
134 auto const skyFrame = initialFrameDict->getFrame(
"SKY");
137 ndarray::Array<double, 2, 2> fieldAngleToIwcNdArray = ndarray::allocate(2, 2);
138 asEigenMatrix(fieldAngleToIwcNdArray) = orientationAndFlipXMatrix * 180.0 /
lsst::geom::PI;
139 auto const pixelsToFieldAngleMap = pixelsToFieldAngle.getMapping();
140 auto const fieldAngleToIwcMap = ast::MatrixMap(fieldAngleToIwcNdArray);
141 auto const pixelsToIwcMap = pixelsToFieldAngleMap->then(fieldAngleToIwcMap);
142 auto finalFrameDict = ast::FrameDict(*pixelFrame, pixelsToIwcMap, *iwcFrame);
143 finalFrameDict.addFrame(
"IWC", *iwcToSkyMap, *skyFrame);
144 return finalFrameDict;
151 Eigen::Matrix2d cdMatrix;
152 double orientRad = orientation.asRadians();
153 double scaleDeg = scale.asDegrees();
154 double xmult = flipX ? 1.0 : -1.0;
155 cdMatrix(0, 0) =
std::cos(orientRad) * scaleDeg * xmult;
156 cdMatrix(0, 1) =
std::sin(orientRad) * scaleDeg;
157 cdMatrix(1, 0) = -
std::sin(orientRad) * scaleDeg * xmult;
158 cdMatrix(1, 1) =
std::cos(orientRad) * scaleDeg;
177 double const side = 1.0;
187 auto skyLL = skyVec[0].getVector();
188 auto skyDx = skyVec[1].getVector() - skyLL;
189 auto skyDy = skyVec[2].getVector() - skyLL;
194 double skyAreaSq = skyDx.cross(skyDy).getSquaredNorm();
205 auto const crvalRad = skyFrame->getSkyRef();
212 return pixelToIwcTransform.getJacobian(pixel);
236 auto const iwcToSky = thisDict->getMapping(
"IWC",
"SKY");
237 auto const gridToSky = gridToPixel.
then(*pixelToIwc).
then(*iwcToSky);
244 os <<
"Encoding=FITS-WCS, CDMatrix=1, FitsAxisOrder=<copy>, FitsTol=" << TIGHT_FITS_TOL;
247 fitsChan.setFitsI(
"NAXIS1", bbox.
getWidth());
248 fitsChan.setFitsI(
"NAXIS2", bbox.
getHeight());
249 int const nObjectsWritten = fitsChan.write(frameSet);
250 if (nObjectsWritten == 0) {
256 header->remove(
"NAXIS1");
257 header->remove(
"NAXIS2");
260 header->remove(
"DATE-OBS");
261 header->remove(
"MJD-OBS");
264 bool const hasCd11 = header->exists(
"CD1_1");
265 bool const hasCd12 = header->exists(
"CD1_2");
266 bool const hasCd21 = header->exists(
"CD2_1");
267 bool const hasCd22 = header->exists(
"CD2_2");
268 if (hasCd11 || hasCd12 || hasCd21 || hasCd22) {
269 if (!hasCd11) header->set(
"CD1_1", 0.0,
"Transformation matrix element");
270 if (!hasCd12) header->set(
"CD1_2", 0.0,
"Transformation matrix element");
271 if (!hasCd21) header->set(
"CD2_1", 0.0,
"Transformation matrix element");
272 if (!hasCd22) header->set(
"CD2_2", 0.0,
"Transformation matrix element");
284 auto result = _getDirectFitsMetadata(bbox);
287 precise ?
"WCS is not directly FITS-compatible."
288 :
"WCS does not have an attached FITS approximation.");
296 if (fitsApproximation && fitsApproximation->hasFitsApproximation()) {
298 "Cannot add a FITS approximation that itself already has a FITS approximation.");
301 result->_fitsApproximation = fitsApproximation;
306 return bool(_getDirectFitsMetadata(
317 return _linearizePixelToSky(pix,
pixelToSky(pix), skyUnit);
327 return _linearizeSkyToPixel(pix,
pixelToSky(pix), skyUnit);
347 is >> shortClassName;
360 os <<
"The AST serialization was read as a " << astObjectPtr->getClassName()
361 <<
" instead of a FrameSet";
385 return std::make_unique<SkyWcs>(*
this);
391 os <<
"FITS standard SkyWcs:";
393 os <<
"Non-standard SkyWcs (Frames: ";
429 : _frameDict(frameDict), _transform(), _pixelOrigin(), _pixelScaleAtOrigin(0 *
lsst::
geom::radians) {
436 for (
auto const& domainName : domainNames) {
438 auto const frame = frameDict.
getFrame(domainName,
false);
439 if (frame->getNAxes() != 2) {
441 "Frame " + domainName +
" has " +
std::to_string(frame->getNAxes()) +
442 " axes instead of 2");
444 auto desiredClassName = domainName ==
"SKY" ?
"SkyFrame" :
"Frame";
445 if (frame->getClassName() != desiredClassName) {
447 "Frame " + domainName +
" is of type " + frame->getClassName() +
448 " instead of " + desiredClassName);
450 }
else if (domainName !=
"ACTUAL_PIXELS") {
452 throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
453 "No frame with domain " + domainName +
" found");
459 if (baseDomain !=
"ACTUAL_PIXELS" && baseDomain !=
"PIXELS") {
460 throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
461 "Base frame has domain " + baseDomain +
" instead of PIXELS or ACTUAL_PIXELS");
466 if (currentDomain !=
"SKY") {
467 throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
468 "Current frame has domain " + currentDomain +
" instead of SKY");
471 return frameDict.
copy();
475 lsst::geom::SpherePoint
const& coord,
476 lsst::geom::AngleUnit
const& skyUnit)
const {
480 const double side = 1.0;
486 m(0, 0) = dsky10.first.asAngularUnits(skyUnit) / side;
487 m(0, 1) = dsky01.first.asAngularUnits(skyUnit) / side;
488 m(1, 0) = dsky10.second.asAngularUnits(skyUnit) / side;
489 m(1, 1) = dsky01.second.asAngularUnits(skyUnit) / side;
491 Eigen::Vector2d sky00v;
492 sky00v << sky00.getX(), sky00.getY();
493 Eigen::Vector2d pix00v;
494 pix00v << pix00.getX(), pix00.getY();
496 return lsst::geom::AffineTransform(m, (sky00v - m * pix00v));
500 lsst::geom::SpherePoint
const& coord,
501 lsst::geom::AngleUnit
const& skyUnit)
const {
502 lsst::geom::AffineTransform inverse = _linearizePixelToSky(pix00, coord, skyUnit);
508 double const dx = 1000;
526 bool modifyActualPixels) {
527 auto const pixelMapping = pixelTransform.
getMapping();
529 bool const hasActualPixels = oldFrameDict->
hasDomain(
"ACTUAL_PIXELS");
530 auto const pixelFrame = oldFrameDict->getFrame(
"PIXELS",
false);
531 auto const iwcFrame = oldFrameDict->getFrame(
"IWC",
false);
532 auto const skyFrame = oldFrameDict->getFrame(
"SKY",
false);
533 auto const oldPixelToIwc = oldFrameDict->getMapping(
"PIXELS",
"IWC");
534 auto const iwcToSky = oldFrameDict->getMapping(
"IWC",
"SKY");
538 if (hasActualPixels) {
539 auto const actualPixelFrame = oldFrameDict->getFrame(
"ACTUAL_PIXELS",
false);
540 auto const oldActualPixelToPixels = oldFrameDict->getMapping(
"ACTUAL_PIXELS",
"PIXELS");
542 if (modifyActualPixels) {
543 newActualPixelsToPixels = pixelMapping->then(*oldActualPixelToPixels).simplified();
544 newPixelToIwc = oldPixelToIwc;
546 newActualPixelsToPixels = oldActualPixelToPixels;
547 newPixelToIwc = pixelMapping->then(*oldPixelToIwc).simplified();
551 newFrameDict->addFrame(
"PIXELS", *newPixelToIwc, *iwcFrame);
553 newPixelToIwc = pixelMapping->then(*oldPixelToIwc).simplified();
556 newFrameDict->addFrame(
"IWC", *iwcToSky, *skyFrame);
565 Eigen::Matrix2d
const& cdMatrix,
std::string const& projection) {
573 auto frameDict = makeSkyWcsFrameDict(pixelsToFieldAngle, orientation, flipX, boresight, projection);
578 Eigen::Matrix2d
const& cdMatrix, Eigen::MatrixXd
const& sipA,
579 Eigen::MatrixXd
const& sipB) {
585 Eigen::Matrix2d
const& cdMatrix, Eigen::MatrixXd
const& sipA,
586 Eigen::MatrixXd
const& sipB, Eigen::MatrixXd
const& sipAp,
587 Eigen::MatrixXd
const& sipBp) {
#define LSST_EXCEPT(type,...)
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
std::shared_ptr< Object > read()
bool hasDomain(std::string const &domain) const
std::shared_ptr< Mapping > getMapping(int from, std::string const &to) const
std::shared_ptr< FrameDict > copy() const
std::set< std::string > getAllDomains() const
std::shared_ptr< Frame > getFrame(std::string const &domain, bool copy=true) const
static int constexpr CURRENT
static int constexpr BASE
SeriesMap then(Mapping const &next) const
void show(std::ostream &os, bool showComments=true) const
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Eigen::Matrix2d getCdMatrix() const
Get the 2x2 CD matrix at the pixel origin.
std::string writeString() const
Serialize this SkyWcs to a string, using the same format as writeStream.
lsst::geom::SpherePoint pixelToSky(lsst::geom::Point2D const &pixel) const
Compute sky position(s) from pixel position(s)
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< SkyWcs > getTanWcs(lsst::geom::Point2D const &pixel) const
Get a local TAN WCS approximation to this WCS at the specified pixel position.
lsst::geom::Angle getPixelScale() const
Get the pixel scale at the pixel origin.
static std::shared_ptr< SkyWcs > readString(std::string &str)
Deserialize a SkyWcs from a string, using the same format as readStream.
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.
std::string toString() const override
Create a string representation of this object.
lsst::geom::Point2D getPixelOrigin() const
Get the pixel origin, in pixels, using the LSST convention.
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.
bool operator==(SkyWcs const &other) const
Equality is based on the string representations being equal.
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.
SkyWcs(SkyWcs const &)=default
std::shared_ptr< daf::base::PropertyList > getFitsMetadata(bool precise=false, lsst::geom::Box2I const &bbox=lsst::geom::Box2I(lsst::geom::Point2I(0, 0), lsst::geom::Extent2I(100, 100))) const
Return the WCS as FITS WCS metadata.
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)
std::string getPythonModule() const override
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
bool isFlipped() const
Does the WCS follow the convention of North=Up, East=Left?
std::string getPersistenceName() const override
Return the unique name used to persist this object and look up its factory.
bool hasFitsApproximation() const
Does this SkyWcs have an approximate SkyWcs that can be represented as standard FITS WCS?
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
std::shared_ptr< SkyWcs > getFitsApproximation() const
Return FITS SkyWcs that approximates this one.
bool equals(typehandling::Storable const &other) const noexcept override
Compare this object to another Storable.
void writeStream(std::ostream &os) const
Serialize this SkyWcs to an output stream.
std::shared_ptr< typehandling::Storable > cloneStorable() const override
Create a new SkyWcs that is a copy of this one.
static std::string getShortClassName()
std::shared_ptr< const ast::FrameDict > getFrameDict() const
Get the contained FrameDict.
bool isFits() const
Return true getFitsMetadata(true) will succeed with no arguments, false if not.
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.
constexpr double asArcseconds() const noexcept
int getBeginX() const noexcept
int getHeight() const noexcept
int getWidth() const noexcept
int getBeginY() const noexcept
std::pair< Angle, Angle > getTangentPlaneOffset(SpherePoint const &other) const
Point2D getPosition(AngleUnit unit) const noexcept
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
Extent< double, 2 > Extent2D
AngleUnit constexpr radians
Point< double, 2 > Point2D
Extent< int, 2 > Extent2I
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