LSST Applications 30.0.7,g0e76e35be5+e8e946ae08,g19811a7679+138f7293ba,g199a45376c+5e234f8357,g1fd858c14a+2f48dbc4c4,g262e1987ae+fb36cac54d,g29ae962dfc+d9108a0941,g2c21b0017a+4f59a27f16,g31e44d4a5c+b0138be388,g33ac35c1f1+28b9f72785,g35bb328faa+b0138be388,g40c9b15c53+823ad735c1,g47891489e3+bcc48a0b46,g53246c7159+b0138be388,g64539dfbff+e8e946ae08,g67b6fd64d1+bcc48a0b46,g74acd417e5+422380537a,g76965917b2+a5ca99c4d9,g786e29fd12+796b79145d,g7aefaa3e3d+dc0c200193,g86b635cae8+734fe384f0,g87389fa792+d8b5378923,g89139ef638+bcc48a0b46,g8bbb235e95+3f4f7f9447,g8ea07a8fe4+78a4c88802,g9290983e33+ffdc83c6f7,g92c671f44c+e8e946ae08,gaa753fd333+03f406da14,gbf99507273+b0138be388,gc49b57b85e+8df26ee1f0,gca7fc764a6+bcc48a0b46,gd7ef33dd92+bcc48a0b46,gdab6d2f7ff+422380537a,ge1c02a5578+b0138be388,ge410e46f29+bcc48a0b46,ge80df9fc40+e6db5413d1,geaed405ab2+1de65a85c6,gf5dcc679e7+35a0ce2edd,gf5f1c85443+e8e946ae08
LSST Data Management Base Package
Loading...
Searching...
No Matches
SipApproximation.cc
Go to the documentation of this file.
1// -*- lsst-c++ -*-
2/*
3 * Developed for the LSST Data Management System.
4 * This product includes software developed by the LSST Project
5 * (https://www.lsst.org).
6 * See the COPYRIGHT file at the top-level directory of this distribution
7 * for details of code ownership.
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <algorithm>
24#include <vector>
25
26#include "Eigen/QR"
29
30namespace lsst { namespace afw { namespace geom {
31
32namespace poly = lsst::geom::polynomials;
33
34namespace {
35
36std::pair<poly::PolynomialFunction2dYX, poly::PolynomialFunction2dYX> fitPolynomial(
37 int order,
38 double svdThreshold,
39 std::vector<lsst::geom::Point2D> const & input,
40 std::vector<lsst::geom::Point2D> const & output
41) {
42 auto basis = poly::PolynomialBasis2dYX(order);
43 auto workspace = basis.makeWorkspace();
44 // Since we want to null the constant terms in the polynomial, we chop off
45 // the first column from the matrix we take the SVD of, and then set those
46 // coefficients to zero in the result.
47 Eigen::MatrixXd matrix = Eigen::MatrixXd::Zero(input.size(), basis.size() - 1);
48 Eigen::VectorXd xRhs(input.size());
49 Eigen::VectorXd yRhs(input.size());
50 Eigen::VectorXd tmp(basis.size());
51 for (int i = 0; i < matrix.rows(); ++i) {
52 tmp.setZero();
53 basis.fill(input[i], tmp, workspace);
54 matrix.row(i) = tmp.tail(basis.size() - 1);
55 auto rhs = output[i];
56 xRhs[i] = rhs.getX();
57 yRhs[i] = rhs.getY();
58 }
59 auto decomp = matrix.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV);
60 if (svdThreshold >= 0) {
61 decomp.setThreshold(svdThreshold);
62 }
63 Eigen::VectorXd xCoeff = Eigen::VectorXd::Zero(basis.size());
64 xCoeff.tail(basis.size() - 1) = decomp.solve(xRhs);
65 if (Eigen::isnan(xCoeff.array()).any()) {
66 throw LSST_EXCEPT(
67 pex::exceptions::RuntimeError,
68 "Polynomial fit failed due to numerical instability; try decreasing the order."
69 );
70 }
71 auto polyX = makeFunction2d(basis, xCoeff);
72 Eigen::VectorXd yCoeff = Eigen::VectorXd::Zero(basis.size());
73 yCoeff.tail(basis.size() - 1) = decomp.solve(yRhs);
74 if (Eigen::isnan(yCoeff.array()).any()) {
75 throw LSST_EXCEPT(
76 pex::exceptions::RuntimeError,
77 "Polynomial fit failed due to numerical instability; try decreasing the order."
78 );
79 }
80 auto polyY = makeFunction2d(basis, yCoeff);
81 return std::make_pair(polyX, polyY);
82}
83
84// Return a vector of points on a grid, covering the given bounding box.
85std::vector<lsst::geom::Point2D> makeGrid(lsst::geom::Box2D const & bbox,
86 lsst::geom::Extent2I const & shape) {
87 if (shape.getX() <= 1 || shape.getY() <= 1) {
88 throw LSST_EXCEPT(
89 pex::exceptions::InvalidParameterError,
90 "Grid shape values must be two or greater."
91 );
92 }
93 std::vector<lsst::geom::Point2D> points;
94 points.reserve(shape.getX()*shape.getY());
95 double const dx = bbox.getWidth()/(shape.getX() - 1);
96 double const dy = bbox.getHeight()/(shape.getY() - 1);
97 for (int iy = 0; iy < shape.getY(); ++iy) {
98 double const y = bbox.getMinY() + iy*dy;
99 for (int ix = 0; ix < shape.getX(); ++ix) {
100 points.emplace_back(bbox.getMinX() + ix*dx, y);
101 }
102 }
103 return points;
104}
105
106} // anonymous
107
108// Private implementation object for SipApproximation that manages the grid of points on which
109// we evaluate the exact transform when fitting.
111
113 lsst::geom::Box2D const & bbox_,
114 lsst::geom::Extent2I const & shape_,
115 lsst::geom::Point2D const & crpix,
116 TransformPoint2ToPoint2 const & pixelToIwc
117 ) :
118 bbox(bbox_),
119 shape(shape_)
120 {
121 // Make the pixel grid and transform it to get the intermediate world
122 // coordinates grid.
123 auto pix = makeGrid(bbox, shape);
124 iwc = pixelToIwc.applyForward(pix);
125
126 lsst::geom::Extent2D offset(crpix);
127
128 // Apply the CRPIX offset to make pix into dpix.
129 dpix = std::move(pix);
131 dpix.begin(), dpix.end(),
132 [&offset](lsst::geom::Point2D & p){ p -= offset; }
133 );
134 }
135
136 lsst::geom::Extent2D getStep() const noexcept {
138 bbox.getWidth()/(shape.getX() - 1),
139 bbox.getHeight()/(shape.getY() - 1)
140 );
141 }
142
145 std::vector<lsst::geom::Point2D> dpix; // [pixel coords] - CRPIX
146 std::vector<lsst::geom::Point2D> iwc; // [intermediate world coords]
147};
148
149// Private implementation object for SipApproximation that manages the grid of points on which
150// we evaluate the exact transform when validating.
152
153 ValidationGrid(FitGrid const & fit_grid, SkyWcs const & target) :
154 // Shrink the grid bbox by half of the step size, and shrink the shape
155 // by one, to get a grid that is offset from the fit grid.
156 bbox(fit_grid.bbox.erodedBy(0.5*fit_grid.getStep())),
157 shape(fit_grid.shape - lsst::geom::Extent2I(1, 1)),
158 pix(makeGrid(bbox, shape)),
159 sky(target.pixelToSky(pix))
160 {}
161
162 std::size_t size() const noexcept {
163 return pix.size();
164 }
165
170};
171
172
173// Private implementation object for SipApproximation that manages the solution
175
176 static std::unique_ptr<Solution> fit(int order_, double svdThreshold, SipApproximation const & parent);
177
179 Eigen::Matrix2d const & cd_,
180 poly::PolynomialFunction2dYX const & a_,
181 poly::PolynomialFunction2dYX const & b_
182 ) :
183 cd(cd_), a(a_), b(b_)
184 {
185 LSST_THROW_IF_NE(a.getBasis().getOrder(), b.getBasis().getOrder(),
187 "A and B polynomials must have the same order (%d != %d).");
188 }
189
190 using Workspace = poly::PolynomialFunction2dYX::Workspace;
191
192 Workspace makeWorkspace() const { return a.makeWorkspace(); }
193
194 Eigen::Matrix2d cd;
195 poly::PolynomialFunction2dYX a;
196 poly::PolynomialFunction2dYX b;
197};
198
200 int order,
201 double svdThreshold,
202 SipApproximation const & parent
203) {
204 poly::PolynomialBasis2dYX basis(order);
205 if ((basis.size() - 1) > parent._fitGrid->dpix.size()) {
206 throw LSST_EXCEPT(
208 (boost::format("Number of parameters (%d) is larger than number of data points (%d)")
209 % (2*(basis.size() - 1)) % (2*parent._fitGrid->dpix.size())).str()
210 );
211 }
212 // Fit polynomials R and S that directly map (u, v) = (pixel - crpix) to
213 // IWC. These are *not* the SIP A and B yet, because they don't separate
214 // out the CD matrix.
215 auto [R, S] = fitPolynomial(order, svdThreshold, parent._fitGrid->dpix, parent._fitGrid->iwc);
216 // Since there are no linear SIP terms, the linear terms in the polynomial
217 // are just the CD matrix coefficients:
218 Eigen::Matrix2d cd = Eigen::Matrix2d::Zero();
219 cd(0, 0) = R.getCoefficients()[basis.index(1, 0)];
220 cd(0, 1) = R.getCoefficients()[basis.index(0, 1)];
221 cd(1, 0) = S.getCoefficients()[basis.index(1, 0)];
222 cd(1, 1) = S.getCoefficients()[basis.index(0, 1)];
223 // The SIP A and B polynomial coefficients for a given order (p, q) can now
224 // be computed by multiplying the (R, S) coefficients for (p, q) by the
225 // inverse of the CD matrix. There are no constant or linear (A, B) terms
226 // so we leave the first three terms in each polynomial at zero.
227 Eigen::Matrix2d cdInv = cd.inverse();
228 if (Eigen::isnan(cdInv.array()).any()) {
229 throw LSST_EXCEPT(
231 "CD matrix inversion failed due to numerical instability; try decreasing the SIP order."
232 );
233 }
234 Eigen::Matrix2Xd RS = Eigen::Matrix2Xd::Zero(2, basis.size());
235 RS.row(0) = R.getCoefficients();
236 RS.row(1) = S.getCoefficients();
237 Eigen::Matrix2Xd AB = Eigen::Matrix2Xd::Zero(2, basis.size());
238 AB.rightCols(basis.size() - 3) = cdInv * RS.rightCols(basis.size() - 3);
239 poly::PolynomialFunction2dYX A(basis, AB.row(0));
240 poly::PolynomialFunction2dYX B(basis, AB.row(1));
241 return std::make_unique<Solution>(cd, A, B);
242}
243
245 SkyWcs const & target,
246 lsst::geom::Box2D const & bbox,
247 lsst::geom::Extent2I const & gridShape,
248 int order,
249 std::optional<lsst::geom::Point2D> const & pixelOrigin,
250 double svdThreshold
251) :
252 _bbox(bbox),
253 _pixelOrigin(pixelOrigin.has_value() ? pixelOrigin.value() : bbox.getCenter()),
254 _skyOrigin(target.pixelToSky(_pixelOrigin))
255{
256 if (!_bbox.contains(_pixelOrigin)) {
257 throw LSST_EXCEPT(
258 pex::exceptions::InvalidParameterError,
259 (boost::format("CRPIX value %s is outside the bounding box %s") % _pixelOrigin % _bbox).str()
260 );
261 }
262 if (order < 1) {
263 order = 1;
264 }
265 // Make a TAN WCS at the desired CRPIX and CRVAL with an arbitrary pixel
266 // scale, just so we can extract the sky->IWC transform from it (which
267 // doesn't depend on the pixel scale).
268 auto tanWcs = makeSkyWcs(_pixelOrigin, _skyOrigin, makeCdMatrix(1*lsst::geom::degrees));
269 auto pixelToIwc = target.getTransform()->then(
270 *getIntermediateWorldCoordsToSky(*tanWcs)->inverted()
271 );
272 _fitGrid.reset(new FitGrid(bbox, gridShape, _pixelOrigin, *pixelToIwc));
273 _validationGrid.reset(new ValidationGrid(*_fitGrid, target));
274 _solution = Solution::fit(order, svdThreshold, *this);
275 _wcs = makeTanSipWcs(_pixelOrigin, _skyOrigin, _solution->cd, getA(), getB());
276}
277
278SipApproximation::~SipApproximation() noexcept = default;
279
280int SipApproximation::getOrder() const noexcept {
281 return _solution->a.getBasis().getOrder();
282}
283
284double SipApproximation::getA(int p, int q) const {
285 return _solution->a[_solution->a.getBasis().index(p, q)];
286}
287
288double SipApproximation::getB(int p, int q) const {
289 return _solution->b[_solution->b.getBasis().index(p, q)];
290}
291
292namespace {
293
294template <typename F>
295Eigen::MatrixXd makeCoefficientMatrix(std::size_t order, F getter) {
296 Eigen::MatrixXd result = Eigen::MatrixXd::Zero(order + 1, order + 1);
297 for (std::size_t p = 0; p <= order; ++p) {
298 for (std::size_t q = 0; q <= order - p; ++q) {
299 result(p, q) = getter(p, q);
300 }
301 }
302 return result;
303}
304
305} // anonymous
306
307Eigen::MatrixXd SipApproximation::getA() const {
308 return makeCoefficientMatrix(
309 getOrder(),
310 [this](int p, int q) { return getA(p, q); }
311 );
312}
313
314Eigen::MatrixXd SipApproximation::getB() const {
315 return makeCoefficientMatrix(
316 getOrder(),
317 [this](int p, int q) { return getB(p, q); }
318 );
319}
320
321Eigen::Matrix2d SipApproximation::getCdMatrix() const noexcept {
322 return _solution->cd;
323}
324
326 return _wcs;
327}
328
330 auto approxSky = _wcs->pixelToSky(_validationGrid->pix);
331 auto approxPix = _wcs->skyToPixel(_validationGrid->sky);
333 for (std::size_t i = 0; i < _validationGrid->size(); ++i) {
334 auto deltaSky = approxSky[i].separation(_validationGrid->sky[i]);
335 auto deltaPix = (approxPix[i] - _validationGrid->pix[i]).computeNorm();
336 result.first = std::max(result.first, deltaSky);
337 result.second = std::max(result.second, deltaPix);
338 }
339 return result;
340}
341
342}}} // namespace lsst::afw::geom
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition Exception.h:48
#define LSST_THROW_IF_NE(N1, N2, EXC_CLASS, MSG)
Check whether the given values are equal, and throw an LSST Exception if they are not.
Definition asserts.h:38
SipApproximation(SkyWcs const &target, lsst::geom::Box2D const &bbox, lsst::geom::Extent2I const &gridShape, int order=5, std::optional< lsst::geom::Point2D > const &pixelOrigin=std::nullopt, double svdThreshold=-1)
Construct a new approximation by fitting on a grid of points.
Eigen::MatrixXd getB() const
Return the coefficients of the forward transform polynomial.
Eigen::MatrixXd getA() const
Return the coefficients of the forward transform polynomial.
std::shared_ptr< SkyWcs > getWcs() const noexcept
Return a TAN-SIP WCS object that evaluates the approximation.
int getOrder() const noexcept
Return the polynomial order of the current solution (same for forward and reverse).
Eigen::Matrix2d getCdMatrix() const noexcept
Return the CD matrix of the WCS (in degrees).
double getA(int p, int q) const
Return a coefficient of the forward transform polynomial.
std::pair< lsst::geom::Angle, double > computeDeltas() const
Return the maximum deviations of the solution from the exact transform on a grid offset from the one ...
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition SkyWcs.h:119
std::shared_ptr< const TransformPoint2ToSpherePoint > getTransform() const
Get a TransformPoint2ToSpherePoint that transforms pixels to sky in the forward direction and sky to ...
Definition SkyWcs.h:267
ToPoint applyForward(FromPoint const &point) const
Transform one point in the forward direction ("from" to "to")
A floating-point coordinate rectangle geometry.
Definition Box.h:413
double getMinY() const noexcept
Definition Box.h:515
double getWidth() const noexcept
1-d interval accessors
Definition Box.h:529
double getMinX() const noexcept
Definition Box.h:514
double getHeight() const noexcept
1-d interval accessors
Definition Box.h:530
Reports invalid arguments.
Definition Runtime.h:66
Reports errors in the logical structure of the program.
Definition Runtime.h:46
Reports errors that are due to events beyond the control of the program.
Definition Runtime.h:104
T emplace_back(T... args)
T for_each(T... args)
T make_pair(T... args)
T max(T... args)
T move(T... args)
Definition __init__.py:1
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition SkyWcs.cc:560
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.
Definition SkyWcs.cc:577
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.
Definition SkyWcs.cc:149
std::shared_ptr< TransformPoint2ToSpherePoint > getIntermediateWorldCoordsToSky(SkyWcs const &wcs, bool simplify=true)
Return a transform from intermediate world coordinates to sky.
Definition SkyWcs.cc:592
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
Definition Transform.h:300
Low-level polynomials (including special polynomials) in C++.
Function2d< Basis > makeFunction2d(Basis const &basis, Eigen::VectorXd const &coefficients)
Create a Function2d of the appropriate type from a Basis2d and an Eigen object containing coefficient...
Definition Function2d.h:155
AngleUnit constexpr degrees
constant with units of degrees
Definition Angle.h:110
AngleUnit constexpr arcseconds
constant with units of arcseconds
Definition Angle.h:113
Extent< double, 2 > Extent2D
Definition Extent.h:400
Point< double, 2 > Point2D
Definition Point.h:324
Extent< int, 2 > Extent2I
Definition Extent.h:397
T reserve(T... args)
T size(T... args)
std::vector< lsst::geom::Point2D > dpix
lsst::geom::Extent2D getStep() const noexcept
std::vector< lsst::geom::Point2D > iwc
FitGrid(lsst::geom::Box2D const &bbox_, lsst::geom::Extent2I const &shape_, lsst::geom::Point2D const &crpix, TransformPoint2ToPoint2 const &pixelToIwc)
static std::unique_ptr< Solution > fit(int order_, double svdThreshold, SipApproximation const &parent)
Solution(Eigen::Matrix2d const &cd_, poly::PolynomialFunction2dYX const &a_, poly::PolynomialFunction2dYX const &b_)
poly::PolynomialFunction2dYX::Workspace Workspace
ValidationGrid(FitGrid const &fit_grid, SkyWcs const &target)
std::vector< lsst::geom::SpherePoint > sky