32namespace poly = lsst::geom::polynomials;
36std::pair<poly::PolynomialFunction2dYX, poly::PolynomialFunction2dYX> fitPolynomial(
39 std::vector<lsst::geom::Point2D>
const & input,
40 std::vector<lsst::geom::Point2D>
const & output
42 auto basis = poly::PolynomialBasis2dYX(order);
43 auto workspace = basis.makeWorkspace();
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) {
53 basis.fill(input[i], tmp, workspace);
54 matrix.row(i) = tmp.tail(basis.size() - 1);
59 auto decomp = matrix.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV);
60 if (svdThreshold >= 0) {
61 decomp.setThreshold(svdThreshold);
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()) {
67 pex::exceptions::RuntimeError,
68 "Polynomial fit failed due to numerical instability; try decreasing the order."
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()) {
76 pex::exceptions::RuntimeError,
77 "Polynomial fit failed due to numerical instability; try decreasing the order."
85std::vector<lsst::geom::Point2D> makeGrid(lsst::geom::Box2D
const & bbox,
87 if (shape.getX() <= 1 || shape.getY() <= 1) {
89 pex::exceptions::InvalidParameterError,
90 "Grid shape values must be two or greater."
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) {
156 bbox(fit_grid.
bbox.erodedBy(0.5*fit_grid.getStep())),
159 sky(target.pixelToSky(
pix))
179 Eigen::Matrix2d
const & cd_,
180 poly::PolynomialFunction2dYX
const & a_,
181 poly::PolynomialFunction2dYX
const & b_
183 cd(cd_),
a(a_),
b(b_)
187 "A and B polynomials must have the same order (%d != %d).");
190 using Workspace = poly::PolynomialFunction2dYX::Workspace;
195 poly::PolynomialFunction2dYX
a;
196 poly::PolynomialFunction2dYX
b;
204 poly::PolynomialBasis2dYX basis(order);
205 if ((basis.size() - 1) > parent._fitGrid->dpix.size()) {
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()
215 auto [R, S] = fitPolynomial(order, svdThreshold, parent._fitGrid->dpix, parent._fitGrid->iwc);
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)];
227 Eigen::Matrix2d cdInv =
cd.inverse();
228 if (Eigen::isnan(cdInv.array()).any()) {
231 "CD matrix inversion failed due to numerical instability; try decreasing the SIP order."
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);
249 std::optional<lsst::geom::Point2D>
const & pixelOrigin,
253 _pixelOrigin(pixelOrigin.has_value() ? pixelOrigin.value() : bbox.getCenter()),
254 _skyOrigin(target.pixelToSky(_pixelOrigin))
256 if (!_bbox.contains(_pixelOrigin)) {
258 pex::exceptions::InvalidParameterError,
259 (boost::format(
"CRPIX value %s is outside the bounding box %s") % _pixelOrigin % _bbox).str()
272 _fitGrid.reset(
new FitGrid(bbox, gridShape, _pixelOrigin, *pixelToIwc));
281 return _solution->a.getBasis().
getOrder();
285 return _solution->a[_solution->a.getBasis().index(p, q)];
289 return _solution->b[_solution->b.getBasis().index(p, q)];
295Eigen::MatrixXd makeCoefficientMatrix(
std::size_t order, F getter) {
296 Eigen::MatrixXd result = Eigen::MatrixXd::Zero(order + 1, order + 1);
299 result(p, q) = getter(p, q);
308 return makeCoefficientMatrix(
310 [
this](
int p,
int q) {
return getA(p, q); }
315 return makeCoefficientMatrix(
317 [
this](
int p,
int q) {
return getB(p, q); }
322 return _solution->cd;
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);
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
#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.
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.
~SipApproximation() noexcept
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...
std::shared_ptr< const TransformPoint2ToSpherePoint > getTransform() const
Get a TransformPoint2ToSpherePoint that transforms pixels to sky in the forward direction and sky to ...
A floating-point coordinate rectangle geometry.
double getMinY() const noexcept
double getWidth() const noexcept
1-d interval accessors
double getMinX() const noexcept
double getHeight() const noexcept
1-d interval accessors
Reports invalid arguments.
Reports errors in the logical structure of the program.
Reports errors that are due to events beyond the control of the program.
T emplace_back(T... args)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
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.
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.
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
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...
AngleUnit constexpr degrees
constant with units of degrees
AngleUnit constexpr arcseconds
constant with units of arcseconds
Extent< double, 2 > Extent2D
Point< double, 2 > Point2D
Extent< int, 2 > Extent2I
std::vector< lsst::geom::Point2D > dpix
lsst::geom::Extent2D getStep() const noexcept
lsst::geom::Extent2I shape
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)
poly::PolynomialFunction2dYX a
static std::unique_ptr< Solution > fit(int order_, double svdThreshold, SipApproximation const &parent)
Workspace makeWorkspace() const
Solution(Eigen::Matrix2d const &cd_, poly::PolynomialFunction2dYX const &a_, poly::PolynomialFunction2dYX const &b_)
poly::PolynomialFunction2dYX b
poly::PolynomialFunction2dYX::Workspace Workspace
ValidationGrid(FitGrid const &fit_grid, SkyWcs const &target)
std::vector< lsst::geom::Point2D > pix
lsst::geom::Extent2I shape
std::vector< lsst::geom::SpherePoint > sky
std::size_t size() const noexcept