LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
CreateWcsWithSip.cc
Go to the documentation of this file.
1 // -*- LSST-C++ -*-
2 
3 /*
4  * LSST Data Management System
5  * Copyright 2008, 2009, 2010 LSST Corporation.
6  *
7  * This product includes software developed by the
8  * LSST Project (http://www.lsst.org/).
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the LSST License Statement and
21  * the GNU General Public License along with this program. If not,
22  * see <http://www.lsstcorp.org/LegalNotices/>.
23  */
24 
25 
26 #include "Eigen/SVD"
27 #include "Eigen/Cholesky"
28 #include "Eigen/LU"
29 
32 #include "lsst/afw/geom/Angle.h"
34 #include "lsst/afw/image/TanWcs.h"
35 #include "lsst/pex/logging/Log.h"
37 
38 namespace lsst {
39 namespace meas {
40 namespace astrom {
41 namespace sip {
42 
43 namespace except = lsst::pex::exceptions;
44 namespace afwCoord = lsst::afw::coord;
45 namespace afwGeom = lsst::afw::geom;
46 namespace afwImg = lsst::afw::image;
47 namespace afwDet = lsst::afw::detection;
48 namespace afwMath = lsst::afw::math;
49 namespace afwTable = lsst::afw::table;
50 namespace pexLog = lsst::pex::logging;
51 
52 namespace {
53 /*
54  * Given an index and a SIP order, calculate p and q for the index'th term u^p v^q
55  * (Cf. Eqn 2 in http://fits.gsfc.nasa.gov/registry/sip/SIP_distortion_v1_0.pdf)
56  */
57 std::pair<int, int>
58 indexToPQ(int const index, int const order)
59 {
60  int p = 0, q = index;
61 
62  for (int decrement = order; q >= decrement && decrement > 0; --decrement) {
63  q -= decrement;
64  p++;
65  }
66 
67  return std::make_pair(p, q);
68 }
69 
70 Eigen::MatrixXd
71 calculateCMatrix(Eigen::VectorXd const& axis1, Eigen::VectorXd const& axis2, int const order)
72 {
73  int nTerms = 0.5*order*(order+1);
74 
75  int const n = axis1.size();
76  Eigen::MatrixXd C = Eigen::MatrixXd::Zero(n, nTerms);
77  for (int i = 0; i < n; ++i) {
78  for (int j = 0; j < nTerms; ++j) {
79  std::pair<int, int> pq = indexToPQ(j, order);
80  int p = pq.first, q = pq.second;
81 
82  assert(p + q < order);
83  C(i, j) = ::pow(axis1[i], p)*::pow(axis2[i], q);
84  }
85  }
86 
87  return C;
88 }
89 
96 Eigen::VectorXd
97 leastSquaresSolve(Eigen::VectorXd b, Eigen::MatrixXd A) {
98  assert(A.rows() == b.rows());
99  Eigen::VectorXd par = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
100  return par;
101 }
102 
103 } // anonymous namespace
104 
106 template<class MatchT>
108  std::vector<MatchT> const & matches,
109  afwImg::Wcs const& linearWcs,
110  int const order,
111  afwGeom::Box2I const& bbox,
112  int const ngrid
113 ):
114  _log(pexLog::Log(pexLog::Log::getDefaultLog(), "meas.astrom.sip")),
115  _matches(matches),
116  _bbox(bbox),
117  _ngrid(ngrid),
118  _linearWcs(linearWcs.clone()),
119  _sipOrder(order+1),
120  _reverseSipOrder(order+2), //Higher order for reverse transform
121  _sipA(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
122  _sipB(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
123  _sipAp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
124  _sipBp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
125  _newWcs()
126 {
127  if (order < 2) {
128  throw LSST_EXCEPT(except::OutOfRangeError, "SIP must be at least 2nd order");
129  }
130  if (_sipOrder > 9) {
131  throw LSST_EXCEPT(except::OutOfRangeError,
132  str(boost::format("SIP forward order %d exceeds the convention limit of 9") %
133  _sipOrder));
134  }
135  if (_reverseSipOrder > 9) {
136  throw LSST_EXCEPT(except::OutOfRangeError,
137  str(boost::format("SIP reverse order %d exceeds the convention limit of 9") %
139  }
140 
141  if (_matches.size() < std::size_t(_sipOrder)) {
142  throw LSST_EXCEPT(except::LengthError, "Number of matches less than requested sip order");
143  }
144 
145  if (_ngrid <= 0) {
146  _ngrid = 5*_sipOrder; // should be plenty
147  }
148 
149  /*
150  * We need a bounding box to define the region over which:
151  * The forward transformation should be valid
152  * We calculate the reverse transformartion
153  * If no BBox is provided, guess one from the input points (extrapolated a bit to allow for fact
154  * that a finite number of points won't reach to the edge of the image)
155  */
156  if (_bbox.isEmpty() && !_matches.empty() > 0) {
157  for (
158  typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
159  ptr != _matches.end();
160  ++ptr
161  ) {
162  afwTable::SourceRecord const & src = *ptr->second;
163  _bbox.include(afwGeom::PointI(src.getX(), src.getY()));
164  }
165  float const borderFrac = 1/::sqrt(_matches.size()); // fractional border to add to exact BBox
166  afwGeom::Extent2I border(borderFrac*_bbox.getWidth(), borderFrac*_bbox.getHeight());
167 
168  _bbox.grow(border);
169  }
170  // Calculate the forward part of the SIP distortion
172 
173  //Build a new wcs incorporating the forward sip matrix, it's all we know so far
175  afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
176  Eigen::MatrixXd CD = _linearWcs->getCDMatrix();
177 
178  _newWcs = PTR(afwImg::TanWcs)(new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
179  // Use _newWcs to calculate the forward transformation on a grid, and derive the back transformation
181 
182  //Build a new wcs incorporating both of the sip matrices
183  _newWcs = PTR(afwImg::TanWcs)(new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
184 
185 }
186 
187 template<class MatchT>
188 void
190 {
191  // Assumes FITS (1-indexed) coordinates.
192  afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
193 
194  // Calculate u, v and intermediate world coordinates
195  int const nPoints = _matches.size();
196  Eigen::VectorXd u(nPoints), v(nPoints), iwc1(nPoints), iwc2(nPoints);
197 
198  int i = 0;
199  for (
200  typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
201  ptr != _matches.end();
202  ++ptr, ++i
203  ) {
204  afwTable::ReferenceMatch const & match = *ptr;
205 
206  // iwc: intermediate world coordinate positions of catalogue objects
207  afwCoord::IcrsCoord c = match.first->getCoord();
208  afwGeom::Point2D p = _linearWcs->skyToIntermediateWorldCoord(c);
209  iwc1[i] = p[0];
210  iwc2[i] = p[1];
211  // u and v are intermediate pixel coordinates of observed (distorted) positions
212  u[i] = match.second->getX() - crpix[0];
213  v[i] = match.second->getY() - crpix[1];
214  }
215  // Scale u and v down to [-1,,+1] in order to avoid too large numbers in the polynomials
216  double uMax = u.cwiseAbs().maxCoeff();
217  double vMax = v.cwiseAbs().maxCoeff();
218  double norm = std::max(uMax, vMax);
219  u = u/norm;
220  v = v/norm;
221 
222  // Forward transform
223  int ord = _sipOrder;
224  Eigen::MatrixXd forwardC = calculateCMatrix(u, v, ord);
225  Eigen::VectorXd mu = leastSquaresSolve(iwc1, forwardC);
226  Eigen::VectorXd nu = leastSquaresSolve(iwc2, forwardC);
227 
228  // Use mu and nu to refine CD
229 
230  // Given the implementation of indexToPQ(), the refined values
231  // of the elements of the CD matrices are in elements 1 and "_sipOrder" of mu and nu
232  // If the implementation of indexToPQ() changes, these assertions
233  // will catch that change.
234  assert ((indexToPQ(0, ord) == std::pair<int, int>(0, 0)));
235  assert ((indexToPQ(1, ord) == std::pair<int, int>(0, 1)));
236  assert ((indexToPQ(ord, ord) == std::pair<int, int>(1, 0)));
237 
238  // Scale back CD matrix
239  Eigen::Matrix2d CD;
240  CD(1,0) = nu[ord]/norm;
241  CD(1,1) = nu[1]/norm;
242  CD(0,0) = mu[ord]/norm;
243  CD(0,1) = mu[1]/norm;
244 
245  Eigen::Matrix2d CDinv = CD.inverse(); //Direct inverse OK for 2x2 matrix in Eigen
246 
247  // The zeroth elements correspond to a shift in crpix
248  crpix[0] -= mu[0]*CDinv(0,0) + nu[0]*CDinv(0,1);
249  crpix[1] -= mu[0]*CDinv(1,0) + nu[0]*CDinv(1,1);
250 
251  afwGeom::Point2D crval = _getCrvalAsGeomPoint();
252 
253  _linearWcs = afwImg::Wcs::Ptr( new afwImg::Wcs(crval, crpix, CD));
254 
255  //Get Sip terms
256 
257  //The rest of the elements correspond to
258  //mu[i] == CD11*Apq + CD12*Bpq and
259  //nu[i] == CD21*Apq + CD22*Bpq and
260  //
261  //We solve for Apq and Bpq with the equation
262  // (Apq) = (CD11 CD12)-1 * (mu[i])
263  // (Bpq) (CD21 CD22) (nu[i])
264 
265  for(int i=1; i< mu.rows(); ++i) {
266  std::pair<int, int> pq = indexToPQ(i, ord);
267  int p = pq.first, q = pq.second;
268 
269  if(p + q > 1 && p + q < ord) {
270  Eigen::Vector2d munu(2,1);
271  munu(0) = mu(i);
272  munu(1) = nu(i);
273  Eigen::Vector2d AB = CDinv*munu;
274  // Scale back sip coefficients
275  _sipA(p,q) = AB[0]/::pow(norm,p+q);
276  _sipB(p,q) = AB[1]/::pow(norm,p+q);
277  }
278  }
279 }
280 
281 template<class MatchT>
283  int const ngrid2 = _ngrid*_ngrid;
284 
285  Eigen::VectorXd U(ngrid2), V(ngrid2);
286  Eigen::VectorXd delta1(ngrid2), delta2(ngrid2);
287 
288  int const x0 = _bbox.getMinX();
289  double const dx = _bbox.getWidth()/(double)(_ngrid - 1);
290  int const y0 = _bbox.getMinY();
291  double const dy = _bbox.getHeight()/(double)(_ngrid - 1);
292 
293  // wcs->getPixelOrigin() returns LSST-style (0-indexed) pixel coords.
294  afwGeom::Point2D crpix = _newWcs->getPixelOrigin();
295 
296  _log.debugf("_calcReverseMatrices: x0,y0 %i,%i, W,H %i,%i, ngrid %i, dx,dy %g,%g, CRPIX %g,%g",
297  x0, y0, _bbox.getWidth(), _bbox.getHeight(), _ngrid, dx, dy, crpix[0], crpix[1]);
298 
299  int k = 0;
300  for (int i = 0; i < _ngrid; ++i) {
301  double const y = y0 + i*dy;
302  for (int j = 0; j < _ngrid; ++j, ++k) {
303  double const x = x0 + j*dx;
304  double u,v;
305  // u and v are intermediate pixel coordinates on a grid of positions
306  u = x - crpix[0];
307  v = y - crpix[1];
308 
309  // U and V are the result of applying the "forward" (A,B) SIP coefficients
310  // NOTE that the "undistortPixel()" function accepts 1-indexed (FITS-style)
311  // coordinates, and here we are treating "x" and "y" as LSST-style.
312  afwGeom::Point2D xy = _newWcs->undistortPixel(afwGeom::Point2D(x + 1, y + 1));
313  // "crpix", on the other hand, is LSST-style 0-indexed, so we have to remove
314  // the FITS-style 1-index from "xy"
315  U[k] = xy[0] - 1 - crpix[0];
316  V[k] = xy[1] - 1 - crpix[1];
317 
318  if ((i == 0 || i == (_ngrid-1) || i == (_ngrid/2)) &&
319  (j == 0 || j == (_ngrid-1) || j == (_ngrid/2))) {
320  _log.debugf(" x,y (%.1f, %.1f), u,v (%.1f, %.1f), U,V (%.1f, %.1f)", x, y, u, v, U[k], V[k]);
321  }
322 
323  delta1[k] = u - U[k];
324  delta2[k] = v - V[k];
325  }
326  }
327 
328  // Scale down U and V in order to avoid too large numbers in the polynomials
329  double UMax = U.cwiseAbs().maxCoeff();
330  double VMax = V.cwiseAbs().maxCoeff();
331  double norm = (UMax > VMax) ? UMax : VMax;
332  U = U/norm;
333  V = V/norm;
334 
335  // Reverse transform
336  int const ord = _reverseSipOrder;
337  Eigen::MatrixXd reverseC = calculateCMatrix(U, V, ord);
338  Eigen::VectorXd tmpA = leastSquaresSolve(delta1, reverseC);
339  Eigen::VectorXd tmpB = leastSquaresSolve(delta2, reverseC);
340 
341  assert(tmpA.rows() == tmpB.rows());
342  for(int j=0; j< tmpA.rows(); ++j) {
343  std::pair<int, int> pq = indexToPQ(j, ord);
344  int p = pq.first, q = pq.second;
345  // Scale back sip coefficients
346  _sipAp(p, q) = tmpA[j]/::pow(norm,p+q);
347  _sipBp(p, q) = tmpB[j]/::pow(norm,p+q);
348  }
349 }
350 
351 template<class MatchT>
353  assert(_newWcs.get());
354  return makeMatchStatisticsInPixels(*_newWcs, _matches, afw::math::MEDIAN).getValue();
355 }
356 
357 template<class MatchT>
359  assert(_linearWcs.get());
360  return makeMatchStatisticsInPixels(*_linearWcs, _matches, afw::math::MEDIAN).getValue();
361 }
362 
363 template<class MatchT>
365  assert(_newWcs.get());
367  *_newWcs, _matches, afw::math::MEDIAN).getValue()*afw::geom::radians;
368 }
369 
370 template<class MatchT>
372  assert(_linearWcs.get());
374  *_linearWcs, _matches, afw::math::MEDIAN).getValue()*afw::geom::radians;
375 }
376 
377 template<class MatchT>
379  afwCoord::Fk5Coord coo = _linearWcs->getSkyOrigin()->toFk5();
380  return coo.getPosition(afwGeom::degrees);
381 }
382 
383 
384 #define INSTANTIATE(MATCH) \
385  template class CreateWcsWithSip<MATCH>;
386 
389 
390 }}}}
391 
392 
int y
lsst::afw::geom::Point2D getPosition(lsst::afw::geom::AngleUnit unit=lsst::afw::geom::degrees) const
Return our contents in a Point2D object.
Definition: Coord.cc:477
boost::shared_ptr< afw::image::TanWcs > _newWcs
boost::shared_ptr< Record1 > first
Definition: Match.h:48
boost::shared_ptr< Record2 > second
Definition: Match.h:49
void include(Point2I const &point)
Expand this to ensure that this-&gt;contains(point).
double getY() const
Return the centroid slot y coordinate.
Definition: Source.h:933
afw::geom::Angle getScatterOnSky() const
T norm(const T &x)
Definition: Integrate.h:191
table::PointKey< double > crval
Definition: Wcs.cc:1035
#define PTR(...)
Definition: base.h:41
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
double getX() const
Return the centroid slot x coordinate.
Definition: Source.h:930
int const x0
Definition: saturated.cc:45
An integer coordinate rectangle.
Definition: Box.h:53
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
virtual Fk5Coord toFk5(double const epoch) const
Convert ourself to Fk5 (ie. a no-op): RA, Dec (precess to new epoch)
Definition: Coord.cc:839
double getValue(Property const prop=NOTHING) const
Return the value of the desired property (if specified in the constructor)
Definition: Statistics.cc:1009
afw::geom::Point2D _getCrvalAsGeomPoint() const
estimate sample median
Definition: Statistics.h:70
boost::shared_ptr< afw::image::Wcs const > _linearWcs
Implementation of the WCS standard for the special case of the Gnomonic (tangent plane) projection...
Definition: TanWcs.h:68
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)
Constructor.
int getMinY() const
Definition: Box.h:125
AngleUnit const radians
constant with units of radians
Definition: Angle.h:91
Lightweight representation of a geometric match between two records.
Definition: fwd.h:90
geom::Box2I const & _bbox
Definition: fits_io_mpl.h:80
int getMinX() const
Definition: Box.h:124
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())
double x
bool isEmpty() const
Return true if the box contains no points.
Definition: Box.h:166
boost::shared_ptr< Wcs > Ptr
Definition: Wcs.h:113
A class to handle Fk5 coordinates (inherits from Coord)
Definition: Coord.h:191
int getWidth() const
Definition: Box.h:154
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
void grow(int buffer)
Increase the size of the box by the given buffer amount in all directions.
Definition: Box.h:193
int getHeight() const
Definition: Box.h:155
afw::geom::Angle getLinearScatterOnSky() const
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 Image Statistics.
afw::table::Key< double > b
Record class that contains measurements made on a single exposure.
Definition: Source.h:81
AngleUnit const degrees
Definition: Angle.h:92
#define INSTANTIATE(MATCH)
int const y0
Definition: saturated.cc:45
A class to handle Icrs coordinates (inherits from Coord)
Definition: Coord.h:157
std::vector< MatchT > const _matches
table::PointKey< double > crpix
Definition: Wcs.cc:1036