27 #include "Eigen/Cholesky" 30 #include "lsst/pex/exceptions/Runtime.h" 32 #include "lsst/afw/geom/Angle.h" 33 #include "lsst/afw/math/Statistics.h" 34 #include "lsst/afw/image/TanWcs.h" 35 #include "lsst/log/Log.h" 39 LOG_LOGGER _log = LOG_GET(
"meas.astrom.sip");
47 namespace except = lsst::pex::exceptions;
49 namespace afwGeom = lsst::afw::geom;
51 namespace afwDet = lsst::afw::detection;
52 namespace afwMath = lsst::afw::math;
53 namespace afwTable = lsst::afw::table;
61 indexToPQ(
int const index,
int const order)
65 for (
int decrement = order; q >= decrement && decrement > 0; --decrement) {
70 return std::make_pair(p, q);
74 calculateCMatrix(Eigen::VectorXd
const& axis1, Eigen::VectorXd
const& axis2,
int const order)
76 int nTerms = 0.5*order*(order+1);
78 int const n = axis1.size();
79 Eigen::MatrixXd C = Eigen::MatrixXd::Zero(n, nTerms);
80 for (
int i = 0; i < n; ++i) {
81 for (
int j = 0; j < nTerms; ++j) {
82 std::pair<int, int> pq = indexToPQ(j, order);
83 int p = pq.first, q = pq.second;
85 assert(p + q < order);
86 C(i, j) = ::pow(axis1[i], p)*::pow(axis2[i], q);
100 leastSquaresSolve(Eigen::VectorXd b, Eigen::MatrixXd A) {
101 assert(A.rows() == b.rows());
102 Eigen::VectorXd par = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
109 template<
class MatchT>
111 std::vector<MatchT>
const & matches,
112 afwImg::Wcs
const& linearWcs,
114 afwGeom::Box2I
const& bbox,
120 _linearWcs(linearWcs.clone()),
122 _reverseSipOrder(order+2),
123 _sipA(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
124 _sipB(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
125 _sipAp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
126 _sipBp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
130 throw LSST_EXCEPT(except::OutOfRangeError,
"SIP must be at least 2nd order");
133 throw LSST_EXCEPT(except::OutOfRangeError,
134 str(boost::format(
"SIP forward order %d exceeds the convention limit of 9") %
137 if (_reverseSipOrder > 9) {
138 throw LSST_EXCEPT(except::OutOfRangeError,
139 str(boost::format(
"SIP reverse order %d exceeds the convention limit of 9") %
143 if (_matches.size() < std::size_t(_sipOrder)) {
144 throw LSST_EXCEPT(except::LengthError,
"Number of matches less than requested sip order");
148 _ngrid = 5*_sipOrder;
158 if (_bbox.isEmpty() && !_matches.empty() > 0) {
160 typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
161 ptr != _matches.end();
164 afwTable::SourceRecord
const & src = *ptr->second;
165 _bbox.include(afwGeom::PointI(src.getX(), src.getY()));
167 float const borderFrac = 1/::sqrt(_matches.size());
168 afwGeom::Extent2I border(borderFrac*_bbox.getWidth(), borderFrac*_bbox.getHeight());
173 _calculateForwardMatrices();
176 afwGeom::Point2D crval = _getCrvalAsGeomPoint();
177 afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
178 Eigen::MatrixXd CD = _linearWcs->getCDMatrix();
180 _newWcs = PTR(afwImg::TanWcs)(
new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
182 _calculateReverseMatrices();
185 _newWcs = PTR(afwImg::TanWcs)(
new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
189 template<
class MatchT>
194 afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
197 int const nPoints = _matches.size();
198 Eigen::VectorXd u(nPoints), v(nPoints), iwc1(nPoints), iwc2(nPoints);
202 typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
203 ptr != _matches.end();
206 afwTable::ReferenceMatch
const & match = *ptr;
209 afwCoord::IcrsCoord c = match.first->getCoord();
210 afwGeom::Point2D p = _linearWcs->skyToIntermediateWorldCoord(c);
214 u[i] = match.second->getX() - crpix[0];
215 v[i] = match.second->getY() - crpix[1];
218 double uMax = u.cwiseAbs().maxCoeff();
219 double vMax = v.cwiseAbs().maxCoeff();
220 double norm = std::max(uMax, vMax);
226 Eigen::MatrixXd forwardC = calculateCMatrix(u, v, ord);
227 Eigen::VectorXd mu = leastSquaresSolve(iwc1, forwardC);
228 Eigen::VectorXd nu = leastSquaresSolve(iwc2, forwardC);
236 assert ((indexToPQ(0, ord) == std::pair<int, int>(0, 0)));
237 assert ((indexToPQ(1, ord) == std::pair<int, int>(0, 1)));
238 assert ((indexToPQ(ord, ord) == std::pair<int, int>(1, 0)));
242 CD(1,0) = nu[ord]/norm;
243 CD(1,1) = nu[1]/norm;
244 CD(0,0) = mu[ord]/norm;
245 CD(0,1) = mu[1]/norm;
247 Eigen::Matrix2d CDinv = CD.inverse();
250 crpix[0] -= mu[0]*CDinv(0,0) + nu[0]*CDinv(0,1);
251 crpix[1] -= mu[0]*CDinv(1,0) + nu[0]*CDinv(1,1);
253 afwGeom::Point2D crval = _getCrvalAsGeomPoint();
255 _linearWcs = std::shared_ptr<afwImg::Wcs>(
new afwImg::Wcs(crval, crpix, CD));
267 for(
int i=1; i< mu.rows(); ++i) {
268 std::pair<int, int> pq = indexToPQ(i, ord);
269 int p = pq.first, q = pq.second;
271 if(p + q > 1 && p + q < ord) {
272 Eigen::Vector2d munu(2,1);
275 Eigen::Vector2d AB = CDinv*munu;
277 _sipA(p,q) = AB[0]/::pow(norm,p+q);
278 _sipB(p,q) = AB[1]/::pow(norm,p+q);
283 template<
class MatchT>
285 int const ngrid2 = _ngrid*_ngrid;
287 Eigen::VectorXd U(ngrid2), V(ngrid2);
288 Eigen::VectorXd delta1(ngrid2), delta2(ngrid2);
290 int const x0 = _bbox.getMinX();
291 double const dx = _bbox.getWidth()/(double)(_ngrid - 1);
292 int const y0 = _bbox.getMinY();
293 double const dy = _bbox.getHeight()/(double)(_ngrid - 1);
296 afwGeom::Point2D crpix = _newWcs->getPixelOrigin();
298 LOGL_DEBUG(_log,
"_calcReverseMatrices: x0,y0 %i,%i, W,H %i,%i, ngrid %i, dx,dy %g,%g, CRPIX %g,%g",
299 x0, y0, _bbox.getWidth(), _bbox.getHeight(), _ngrid, dx, dy, crpix[0], crpix[1]);
302 for (
int i = 0; i < _ngrid; ++i) {
303 double const y = y0 + i*dy;
304 for (
int j = 0; j < _ngrid; ++j, ++k) {
305 double const x = x0 + j*dx;
314 afwGeom::Point2D xy = _newWcs->undistortPixel(afwGeom::Point2D(x + 1, y + 1));
317 U[k] = xy[0] - 1 - crpix[0];
318 V[k] = xy[1] - 1 - crpix[1];
320 if ((i == 0 || i == (_ngrid-1) || i == (_ngrid/2)) &&
321 (j == 0 || j == (_ngrid-1) || j == (_ngrid/2))) {
322 LOGL_DEBUG(_log,
" x,y (%.1f, %.1f), u,v (%.1f, %.1f), U,V (%.1f, %.1f)", x, y, u, v, U[k], V[k]);
325 delta1[k] = u - U[k];
326 delta2[k] = v - V[k];
331 double UMax = U.cwiseAbs().maxCoeff();
332 double VMax = V.cwiseAbs().maxCoeff();
333 double norm = (UMax > VMax) ? UMax : VMax;
338 int const ord = _reverseSipOrder;
339 Eigen::MatrixXd reverseC = calculateCMatrix(U, V, ord);
340 Eigen::VectorXd tmpA = leastSquaresSolve(delta1, reverseC);
341 Eigen::VectorXd tmpB = leastSquaresSolve(delta2, reverseC);
343 assert(tmpA.rows() == tmpB.rows());
344 for(
int j=0; j< tmpA.rows(); ++j) {
345 std::pair<int, int> pq = indexToPQ(j, ord);
346 int p = pq.first, q = pq.second;
348 _sipAp(p, q) = tmpA[j]/::pow(norm,p+q);
349 _sipBp(p, q) = tmpB[j]/::pow(norm,p+q);
353 template<
class MatchT>
355 assert(_newWcs.get());
359 template<
class MatchT>
361 assert(_linearWcs.get());
365 template<
class MatchT>
367 assert(_newWcs.get());
369 *_newWcs, _matches, afw::math::MEDIAN).getValue()*afw::geom::radians;
372 template<
class MatchT>
374 assert(_linearWcs.get());
376 *_linearWcs, _matches, afw::math::MEDIAN).getValue()*afw::geom::radians;
379 template<
class MatchT>
381 afwCoord::Fk5Coord coo = _linearWcs->getSkyOrigin()->toFk5();
382 return coo.getPosition(afwGeom::degrees);
386 #define INSTANTIATE(MATCH) \ 387 template class CreateWcsWithSip<MATCH>;
double getScatterInPixels() const
Compute the median separation, in pixels, between items in this object's match list.
CreateWcsWithSip(std::vector< MatchT > const &matches, afw::image::Wcs const &linearWcs, int const order, afw::geom::Box2I const &bbox=afw::geom::Box2I(), int const ngrid=0)
Construct a CreateWcsWithSip.
afw::math::Statistics makeMatchStatisticsInPixels(afw::image::Wcs const &wcs, std::vector< MatchT > const &matchList, int const flags, afw::math::StatisticsControl const &sctrl=afw::math::StatisticsControl())
Compute statistics of on-detector radial separation for a match list, in pixels.
afw::geom::Angle getScatterOnSky() const
Compute the median on-sky separation between items in this object's match list.
#define INSTANTIATE(MATCH)
afw::geom::Angle getLinearScatterOnSky() const
Compute the median on-sky separation between items in this object's match list,.
Measure the distortions in an image plane and express them a SIP polynomials.
afw::math::Statistics makeMatchStatisticsInRadians(afw::image::Wcs const &wcs, std::vector< MatchT > const &matchList, int const flags, afw::math::StatisticsControl const &sctrl=afw::math::StatisticsControl())
Compute statistics of on-sky radial separation for a match list, in radians.
double getLinearScatterInPixels() const
Compute the median radial separation between items in this object's match list.