27 #include "Eigen/Cholesky" 39 LOG_LOGGER _log =
LOG_GET(
"meas.astrom.sip");
57 double const MAX_DISTANCE_CRPIX_TO_BBOXCTR = 1000;
64 indexToPQ(
int const index,
int const order)
68 for (
int decrement = order; q >= decrement && decrement > 0; --decrement) {
77 calculateCMatrix(Eigen::VectorXd
const& axis1, Eigen::VectorXd
const& axis2,
int const order)
79 int nTerms = 0.5*order*(order+1);
81 int const n = axis1.size();
82 Eigen::MatrixXd C = Eigen::MatrixXd::Zero(n, nTerms);
83 for (
int i = 0; i < n; ++i) {
84 for (
int j = 0; j < nTerms; ++j) {
86 int p = pq.first, q = pq.second;
88 assert(p + q < order);
89 C(i, j) = ::pow(axis1[i], p)*::pow(axis2[i], q);
103 leastSquaresSolve(Eigen::VectorXd b, Eigen::MatrixXd A) {
104 assert(A.rows() == b.rows());
105 Eigen::VectorXd par = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
112 template<
class MatchT>
123 _linearWcs(
std::make_shared<afw::geom::SkyWcs>(linearWcs)),
125 _reverseSipOrder(order+2),
126 _sipA(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
127 _sipB(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
128 _sipAp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
129 _sipBp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
137 str(boost::format(
"SIP forward order %d exceeds the convention limit of 9") %
140 if (_reverseSipOrder > 9) {
142 str(boost::format(
"SIP reverse order %d exceeds the convention limit of 9") %
151 _ngrid = 5*_sipOrder;
164 ptr != _matches.
end();
170 float const borderFrac = 1/::sqrt(_matches.
size());
177 auto const initialCrpix = _linearWcs->getPixelOrigin();
179 if (
std::hypot(initialCrpix[0] - bboxCenter[0], initialCrpix[1] - bboxCenter[1]) >
180 MAX_DISTANCE_CRPIX_TO_BBOXCTR) {
182 "_linearWcs crpix = %d, %d farther than %f from bbox center; shifting to center %d, %d",
183 initialCrpix[0], initialCrpix[1], MAX_DISTANCE_CRPIX_TO_BBOXCTR, bboxCenter[0],
185 _linearWcs = _linearWcs->getTanWcs(bboxCenter);
189 _calculateForwardMatrices();
192 auto const crval = _linearWcs->getSkyOrigin();
193 auto const crpix = _linearWcs->getPixelOrigin();
194 Eigen::MatrixXd cdMatrix = _linearWcs->getCdMatrix();
198 _calculateReverseMatrices();
204 template<
class MatchT>
212 int const nPoints = _matches.
size();
213 Eigen::VectorXd u(nPoints), v(nPoints), iwc1(nPoints), iwc2(nPoints);
219 ptr != _matches.
end();
230 u[i] = match.
second->getX() - crpix[0];
231 v[i] = match.
second->getY() - crpix[1];
234 double uMax = u.cwiseAbs().maxCoeff();
235 double vMax = v.cwiseAbs().maxCoeff();
242 Eigen::MatrixXd forwardC = calculateCMatrix(u, v, ord);
243 Eigen::VectorXd mu = leastSquaresSolve(iwc1, forwardC);
244 Eigen::VectorXd nu = leastSquaresSolve(iwc2, forwardC);
258 CD(1,0) = nu[ord]/norm;
259 CD(1,1) = nu[1]/norm;
260 CD(0,0) = mu[ord]/norm;
261 CD(0,1) = mu[1]/norm;
263 Eigen::Matrix2d CDinv = CD.inverse();
266 crpix[0] -= mu[0]*CDinv(0,0) + nu[0]*CDinv(0,1);
267 crpix[1] -= mu[0]*CDinv(1,0) + nu[0]*CDinv(1,1);
269 auto const crval = _linearWcs->getSkyOrigin();
283 for(
int i=1; i< mu.rows(); ++i) {
285 int p = pq.first, q = pq.second;
287 if(p + q > 1 && p + q < ord) {
288 Eigen::Vector2d munu(2,1);
291 Eigen::Vector2d AB = CDinv*munu;
293 _sipA(p,q) = AB[0]/::pow(norm,p+q);
294 _sipB(p,q) = AB[1]/::pow(norm,p+q);
299 template<
class MatchT>
301 int const ngrid2 = _ngrid*_ngrid;
303 Eigen::VectorXd U(ngrid2), V(ngrid2);
304 Eigen::VectorXd delta1(ngrid2), delta2(ngrid2);
306 int const x0 = _bbox.
getMinX();
307 double const dx = _bbox.
getWidth()/(double)(_ngrid - 1);
308 int const y0 = _bbox.
getMinY();
309 double const dy = _bbox.
getHeight()/(double)(_ngrid - 1);
314 LOGL_DEBUG(_log,
"_calcReverseMatrices: x0,y0 %i,%i, W,H %i,%i, ngrid %i, dx,dy %g,%g, CRPIX %g,%g",
315 x0, y0, _bbox.
getWidth(), _bbox.
getHeight(), _ngrid, dx, dy, crpix[0], crpix[1]);
317 auto tanWcs = _newWcs->getTanWcs(_newWcs->getPixelOrigin());
318 auto applySipAB = afwGeom::makeWcsPairTransform(*_newWcs, *tanWcs);
320 for (
int i = 0; i < _ngrid; ++i) {
321 double const y = y0 + i*dy;
323 beforeSipABPoints.
reserve(_ngrid);
324 for (
int j = 0; j < _ngrid; ++j) {
325 double const x = x0 + j*dx;
328 auto const afterSipABPoints = applySipAB->applyForward(beforeSipABPoints);
329 for (
int j = 0; j < _ngrid; ++j, ++k) {
330 double const x = x0 + j*dx;
338 U[k] = xy[0] - crpix[0];
339 V[k] = xy[1] - crpix[1];
341 if ((i == 0 || i == (_ngrid-1) || i == (_ngrid/2)) &&
342 (j == 0 || j == (_ngrid-1) || j == (_ngrid/2))) {
343 LOGL_DEBUG(_log,
" x,y (%.1f, %.1f), u,v (%.1f, %.1f), U,V (%.1f, %.1f)", x, y, u, v, U[k], V[k]);
346 delta1[k] = u - U[k];
347 delta2[k] = v - V[k];
352 double UMax = U.cwiseAbs().maxCoeff();
353 double VMax = V.cwiseAbs().maxCoeff();
354 double norm = (UMax > VMax) ? UMax : VMax;
359 int const ord = _reverseSipOrder;
360 Eigen::MatrixXd reverseC = calculateCMatrix(U, V, ord);
361 Eigen::VectorXd tmpA = leastSquaresSolve(delta1, reverseC);
362 Eigen::VectorXd tmpB = leastSquaresSolve(delta2, reverseC);
364 assert(tmpA.rows() == tmpB.rows());
365 for(
int j=0; j< tmpA.rows(); ++j) {
367 int p = pq.first, q = pq.second;
369 _sipAp(p, q) = tmpA[j]/::pow(norm,p+q);
370 _sipBp(p, q) = tmpB[j]/::pow(norm,p+q);
374 template<
class MatchT>
376 assert(_newWcs.get());
380 template<
class MatchT>
382 assert(_linearWcs.get());
386 template<
class MatchT>
388 assert(_newWcs.get());
393 template<
class MatchT>
395 assert(_linearWcs.get());
401 #define INSTANTIATE(MATCH) \ 402 template class CreateWcsWithSip<MATCH>;
double getScatterInPixels() const
Compute the median separation, in pixels, between items in this object's match list.
std::shared_ptr< SkyWcs > makeTanSipWcs(Point2D const &crpix, coord::IcrsCoord const &crval, Eigen::Matrix2d const &cdMatrix, Eigen::MatrixXd const &sipA, Eigen::MatrixXd const &sipB)
AngleUnit constexpr radians
CreateWcsWithSip(std::vector< MatchT > const &matches, afw::geom::SkyWcs const &linearWcs, int const order, afw::geom::Box2I const &bbox=afw::geom::Box2I(), int const ngrid=0)
Construct a CreateWcsWithSip.
void include(Point2I const &point)
#define LOGL_DEBUG(logger, message...)
std::shared_ptr< Record2 > second
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
afw::math::Statistics makeMatchStatisticsInPixels(afw::geom::SkyWcs 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.
Point2D const getCenter() const
std::shared_ptr< Record1 > first
afw::geom::Angle getScatterOnSky() const
Compute the median on-sky separation between items in this object's match list.
double getValue(Property const prop=NOTHING) const
#define LSST_EXCEPT(type,...)
afw::math::Statistics makeMatchStatisticsInRadians(afw::geom::SkyWcs 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.
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.
#define INSTANTIATE(MATCH)
std::shared_ptr< TransformPoint2ToIcrsCoord > getIntermediateWorldCoordsToSky(SkyWcs const &wcs, bool simplify=true)
double getLinearScatterInPixels() const
Compute the median radial separation between items in this object's match list.
T emplace_back(T... args)