LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
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 
33 #include "lsst/afw/image/TanWcs.h"
34 #include "lsst/pex/logging/Log.h"
35 
36 namespace lsst {
37 namespace meas {
38 namespace astrom {
39 namespace sip {
40 
41 namespace except = lsst::pex::exceptions;
42 namespace afwCoord = lsst::afw::coord;
43 namespace afwGeom = lsst::afw::geom;
44 namespace afwImg = lsst::afw::image;
45 namespace afwDet = lsst::afw::detection;
46 namespace afwMath = lsst::afw::math;
47 namespace afwTable = lsst::afw::table;
48 namespace pexLog = lsst::pex::logging;
49 
50 namespace {
51 /*
52  * Given an index and a SIP order, calculate p and q for the index'th term u^p v^q
53  * (Cf. Eqn 2 in http://fits.gsfc.nasa.gov/registry/sip/SIP_distortion_v1_0.pdf)
54  */
55 std::pair<int, int>
56 indexToPQ(int const index, int const order)
57 {
58  int p = 0, q = index;
59 
60  for (int decrement = order; q >= decrement && decrement > 0; --decrement) {
61  q -= decrement;
62  p++;
63  }
64 
65  return std::make_pair(p, q);
66 }
67 
68 Eigen::MatrixXd
69 calculateCMatrix(Eigen::VectorXd const& axis1, Eigen::VectorXd const& axis2, int const order)
70 {
71  int nTerms = 0.5*order*(order+1);
72 
73  int const n = axis1.size();
74  Eigen::MatrixXd C = Eigen::MatrixXd::Zero(n, nTerms);
75  for (int i = 0; i < n; ++i) {
76  for (int j = 0; j < nTerms; ++j) {
77  std::pair<int, int> pq = indexToPQ(j, order);
78  int p = pq.first, q = pq.second;
79 
80  assert(p + q < order);
81  C(i, j) = ::pow(axis1[i], p)*::pow(axis2[i], q);
82  }
83  }
84 
85  return C;
86 }
87 
94 Eigen::VectorXd
95 leastSquaresSolve(Eigen::VectorXd b, Eigen::MatrixXd A) {
96  assert(A.rows() == b.rows());
97  Eigen::VectorXd par = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
98  return par;
99 }
100 
101 } // anonymous namespace
102 
104 template<class MatchT>
106  std::vector<MatchT> const & matches,
107  afwImg::Wcs const& linearWcs,
108  int const order,
109  afwGeom::Box2I const& bbox,
110  int const ngrid
111 ):
112  _log(pexLog::Log(pexLog::Log::getDefaultLog(), "meas.astrom.sip")),
113  _matches(matches),
114  _bbox(bbox),
115  _ngrid(ngrid),
116  _linearWcs(linearWcs.clone()),
117  _sipOrder(order+1),
118  _reverseSipOrder(order+2), //Higher order for reverse transform
119  _sipA(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
120  _sipB(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
121  _sipAp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
122  _sipBp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
123  _newWcs()
124 {
125  if (order < 2) {
126  throw LSST_EXCEPT(except::OutOfRangeError, "SIP must be at least 2nd order");
127  }
128  if (_sipOrder > 9) {
129  throw LSST_EXCEPT(except::OutOfRangeError,
130  str(boost::format("SIP forward order %d exceeds the convention limit of 9") %
131  _sipOrder));
132  }
133  if (_reverseSipOrder > 9) {
134  throw LSST_EXCEPT(except::OutOfRangeError,
135  str(boost::format("SIP reverse order %d exceeds the convention limit of 9") %
137  }
138 
139  if (_matches.size() < std::size_t(_sipOrder)) {
140  throw LSST_EXCEPT(except::LengthError, "Number of matches less than requested sip order");
141  }
142 
143  if (_ngrid <= 0) {
144  _ngrid = 5*_sipOrder; // should be plenty
145  }
146 
147  /*
148  * We need a bounding box to define the region over which:
149  * The forward transformation should be valid
150  * We calculate the reverse transformartion
151  * If no BBox is provided, guess one from the input points (extrapolated a bit to allow for fact
152  * that a finite number of points won't reach to the edge of the image)
153  */
154  if (_bbox.isEmpty() && !_matches.empty() > 0) {
155  for (
156  typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
157  ptr != _matches.end();
158  ++ptr
159  ) {
160  afwTable::SourceRecord const & src = *ptr->second;
161  _bbox.include(afwGeom::PointI(src.getX(), src.getY()));
162  }
163  float const borderFrac = 1/::sqrt(_matches.size()); // fractional border to add to exact BBox
164  afwGeom::Extent2I border(borderFrac*_bbox.getWidth(), borderFrac*_bbox.getHeight());
165 
166  _bbox.grow(border);
167  }
168  // Calculate the forward part of the SIP distortion
170 
171  //Build a new wcs incorporating the forward sip matrix, it's all we know so far
173  afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
174  Eigen::MatrixXd CD = _linearWcs->getCDMatrix();
175 
176  _newWcs = PTR(afwImg::TanWcs)(new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
177  // Use _newWcs to calculate the forward transformation on a grid, and derive the back transformation
179 
180  //Build a new wcs incorporating both of the sip matrices
181  _newWcs = PTR(afwImg::TanWcs)(new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
182 
183 }
184 
185 template<class MatchT>
186 void
188 {
189  // Assumes FITS (1-indexed) coordinates.
190  afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
191 
192  // Calculate u, v and intermediate world coordinates
193  int const nPoints = _matches.size();
194  Eigen::VectorXd u(nPoints), v(nPoints), iwc1(nPoints), iwc2(nPoints);
195 
196  int i = 0;
197  for (
198  typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
199  ptr != _matches.end();
200  ++ptr, ++i
201  ) {
202  afwTable::ReferenceMatch const & match = *ptr;
203 
204  // iwc: intermediate world coordinate positions of catalogue objects
205  afwCoord::IcrsCoord c = match.first->getCoord();
206  afwGeom::Point2D p = _linearWcs->skyToIntermediateWorldCoord(c);
207  iwc1[i] = p[0];
208  iwc2[i] = p[1];
209  // u and v are intermediate pixel coordinates of observed (distorted) positions
210  u[i] = match.second->getX() - crpix[0];
211  v[i] = match.second->getY() - crpix[1];
212  }
213  // Scale u and v down to [-1,,+1] in order to avoid too large numbers in the polynomials
214  double uMax = u.cwiseAbs().maxCoeff();
215  double vMax = v.cwiseAbs().maxCoeff();
216  double norm = std::max(uMax, vMax);
217  u = u/norm;
218  v = v/norm;
219 
220  // Forward transform
221  int ord = _sipOrder;
222  Eigen::MatrixXd forwardC = calculateCMatrix(u, v, ord);
223  Eigen::VectorXd mu = leastSquaresSolve(iwc1, forwardC);
224  Eigen::VectorXd nu = leastSquaresSolve(iwc2, forwardC);
225 
226  // Use mu and nu to refine CD
227 
228  // Given the implementation of indexToPQ(), the refined values
229  // of the elements of the CD matrices are in elements 1 and "_sipOrder" of mu and nu
230  // If the implementation of indexToPQ() changes, these assertions
231  // will catch that change.
232  assert ((indexToPQ(0, ord) == std::pair<int, int>(0, 0)));
233  assert ((indexToPQ(1, ord) == std::pair<int, int>(0, 1)));
234  assert ((indexToPQ(ord, ord) == std::pair<int, int>(1, 0)));
235 
236  // Scale back CD matrix
237  Eigen::Matrix2d CD;
238  CD(1,0) = nu[ord]/norm;
239  CD(1,1) = nu[1]/norm;
240  CD(0,0) = mu[ord]/norm;
241  CD(0,1) = mu[1]/norm;
242 
243  Eigen::Matrix2d CDinv = CD.inverse(); //Direct inverse OK for 2x2 matrix in Eigen
244 
245  // The zeroth elements correspond to a shift in crpix
246  crpix[0] -= mu[0]*CDinv(0,0) + nu[0]*CDinv(0,1);
247  crpix[1] -= mu[0]*CDinv(1,0) + nu[0]*CDinv(1,1);
248 
249  afwGeom::Point2D crval = _getCrvalAsGeomPoint();
250 
251  _linearWcs = afwImg::Wcs::Ptr( new afwImg::Wcs(crval, crpix, CD));
252 
253  //Get Sip terms
254 
255  //The rest of the elements correspond to
256  //mu[i] == CD11*Apq + CD12*Bpq and
257  //nu[i] == CD21*Apq + CD22*Bpq and
258  //
259  //We solve for Apq and Bpq with the equation
260  // (Apq) = (CD11 CD12)-1 * (mu[i])
261  // (Bpq) (CD21 CD22) (nu[i])
262 
263  for(int i=1; i< mu.rows(); ++i) {
264  std::pair<int, int> pq = indexToPQ(i, ord);
265  int p = pq.first, q = pq.second;
266 
267  if(p + q > 1 && p + q < ord) {
268  Eigen::Vector2d munu(2,1);
269  munu(0) = mu(i);
270  munu(1) = nu(i);
271  Eigen::Vector2d AB = CDinv*munu;
272  // Scale back sip coefficients
273  _sipA(p,q) = AB[0]/::pow(norm,p+q);
274  _sipB(p,q) = AB[1]/::pow(norm,p+q);
275  }
276  }
277 }
278 
279 template<class MatchT>
281  int const ngrid2 = _ngrid*_ngrid;
282 
283  Eigen::VectorXd U(ngrid2), V(ngrid2);
284  Eigen::VectorXd delta1(ngrid2), delta2(ngrid2);
285 
286  int const x0 = _bbox.getMinX();
287  double const dx = _bbox.getWidth()/(double)(_ngrid - 1);
288  int const y0 = _bbox.getMinY();
289  double const dy = _bbox.getHeight()/(double)(_ngrid - 1);
290 
291  // wcs->getPixelOrigin() returns LSST-style (0-indexed) pixel coords.
292  afwGeom::Point2D crpix = _newWcs->getPixelOrigin();
293 
294  _log.debugf("_calcReverseMatrices: x0,y0 %i,%i, W,H %i,%i, ngrid %i, dx,dy %g,%g, CRPIX %g,%g",
295  x0, y0, _bbox.getWidth(), _bbox.getHeight(), _ngrid, dx, dy, crpix[0], crpix[1]);
296 
297  int k = 0;
298  for (int i = 0; i < _ngrid; ++i) {
299  double const y = y0 + i*dy;
300  for (int j = 0; j < _ngrid; ++j, ++k) {
301  double const x = x0 + j*dx;
302  double u,v;
303  // u and v are intermediate pixel coordinates on a grid of positions
304  u = x - crpix[0];
305  v = y - crpix[1];
306 
307  // U and V are the result of applying the "forward" (A,B) SIP coefficients
308  // NOTE that the "undistortPixel()" function accepts 1-indexed (FITS-style)
309  // coordinates, and here we are treating "x" and "y" as LSST-style.
310  afwGeom::Point2D xy = _newWcs->undistortPixel(afwGeom::Point2D(x + 1, y + 1));
311  // "crpix", on the other hand, is LSST-style 0-indexed, so we have to remove
312  // the FITS-style 1-index from "xy"
313  U[k] = xy[0] - 1 - crpix[0];
314  V[k] = xy[1] - 1 - crpix[1];
315 
316  if ((i == 0 || i == (_ngrid-1) || i == (_ngrid/2)) &&
317  (j == 0 || j == (_ngrid-1) || j == (_ngrid/2))) {
318  _log.debugf(" x,y (%.1f, %.1f), u,v (%.1f, %.1f), U,V (%.1f, %.1f)", x, y, u, v, U[k], V[k]);
319  }
320 
321  delta1[k] = u - U[k];
322  delta2[k] = v - V[k];
323  }
324  }
325 
326  // Scale down U and V in order to avoid too large numbers in the polynomials
327  double UMax = U.cwiseAbs().maxCoeff();
328  double VMax = V.cwiseAbs().maxCoeff();
329  double norm = (UMax > VMax) ? UMax : VMax;
330  U = U/norm;
331  V = V/norm;
332 
333  // Reverse transform
334  int const ord = _reverseSipOrder;
335  Eigen::MatrixXd reverseC = calculateCMatrix(U, V, ord);
336  Eigen::VectorXd tmpA = leastSquaresSolve(delta1, reverseC);
337  Eigen::VectorXd tmpB = leastSquaresSolve(delta2, reverseC);
338 
339  assert(tmpA.rows() == tmpB.rows());
340  for(int j=0; j< tmpA.rows(); ++j) {
341  std::pair<int, int> pq = indexToPQ(j, ord);
342  int p = pq.first, q = pq.second;
343  // Scale back sip coefficients
344  _sipAp(p, q) = tmpA[j]/::pow(norm,p+q);
345  _sipBp(p, q) = tmpB[j]/::pow(norm,p+q);
346  }
347 }
348 
349 template<class MatchT>
351  assert(_newWcs.get());
352  return _getScatterPixels(*_newWcs, _matches);
353 }
354 
355 template<class MatchT>
357  assert(_linearWcs.get());
358  return _getScatterPixels(*_linearWcs, _matches);
359 }
360 
361 template<class MatchT>
363  afwImg::Wcs const& wcs,
364  std::vector<MatchT> const & matches) {
365  std::vector<double> val;
366  val.reserve(matches.size());
367 
368  for (
369  typename std::vector<MatchT>::const_iterator ptr = matches.begin();
370  ptr != matches.end();
371  ++ptr
372  ) {
373  afwTable::ReferenceMatch const & match = *ptr;
374  PTR(afwTable::SimpleRecord) cat = match.first;
375  PTR(afwTable::SourceRecord) src = match.second;
376  double imgX = src->getX();
377  double imgY = src->getY();
378  afwGeom::Point2D xy = wcs.skyToPixel(cat->getCoord());
379  double catX = xy[0];
380  double catY = xy[1];
381  val.push_back(::hypot(imgX - catX, imgY - catY));
382  }
384 }
385 
386 
387 template<class MatchT>
389  assert(_newWcs.get());
390  return _getScatterSky(*_newWcs, _matches);
391 }
392 
393 template<class MatchT>
395  assert(_linearWcs.get());
396  return _getScatterSky(*_linearWcs, _matches);
397 }
398 
399 template<class MatchT>
401  afwImg::Wcs const & wcs,
402  std::vector<MatchT> const & matches) {
403  std::vector<double> val;
404  val.reserve(matches.size());
405 
406  for (
407  typename std::vector<MatchT>::const_iterator ptr = matches.begin();
408  ptr != matches.end();
409  ++ptr
410  ) {
411  afwTable::ReferenceMatch const & match = *ptr;
412  PTR(afwTable::SimpleRecord) cat = match.first;
413  PTR(afwTable::SourceRecord) src = match.second;
414  afwCoord::IcrsCoord catRadec = cat->getCoord();
415  CONST_PTR(afwCoord::Coord) imgRadec = wcs.pixelToSky(src->getCentroid());
416  afwGeom::Angle sep = catRadec.angularSeparation(*imgRadec);
417  val.push_back(sep.asDegrees());
418  }
419  assert(val.size() > 0);
421 }
422 
423 
424 template<class MatchT>
426  afwCoord::Fk5Coord coo = _linearWcs->getSkyOrigin()->toFk5();
427  return coo.getPosition(afwGeom::degrees);
428 }
429 
430 
431 #define INSTANTIATE(MATCH) \
432  template class CreateWcsWithSip<MATCH>;
433 
436 
437 }}}}
438 
439 
int y
double getValue(Property const prop=NOTHING) const
Return the value of the desired property (if specified in the constructor)
Definition: Statistics.cc:1009
#define PTR(...)
Definition: base.h:41
double getX() const
Return the centroid slot x coordinate.
Definition: Source.h:930
void grow(int buffer)
Increase the size of the box by the given buffer amount in all directions.
Definition: Box.h:193
boost::shared_ptr< Wcs > Ptr
Definition: Wcs.h:113
geom::Point2D skyToPixel(geom::Angle sky1, geom::Angle sky2) const
Convert from sky coordinates (e.g. RA/dec) to pixel positions.
Definition: Wcs.cc:782
double getY() const
Return the centroid slot y coordinate.
Definition: Source.h:933
#define CONST_PTR(...)
Definition: base.h:47
T norm(const T &x)
Definition: Integrate.h:191
table::PointKey< double > crval
Definition: Wcs.cc:1035
tbl::Key< int > wcs
int getMinY() const
Definition: Box.h:125
boost::shared_ptr< afw::image::Wcs const > _linearWcs
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
boost::shared_ptr< coord::Coord > pixelToSky(double pix1, double pix2) const
Convert from pixel position to sky coordinates (e.g. RA/dec)
Definition: Wcs.cc:858
int const x0
Definition: saturated.cc:45
double asDegrees() const
Definition: Angle.h:124
An integer coordinate rectangle.
Definition: Box.h:53
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
double _getScatterPixels(afw::image::Wcs const &wcs, std::vector< MatchT > const &matches)
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
boost::shared_ptr< Record1 > first
Definition: Match.h:48
AngleUnit const degrees
Definition: Angle.h:92
int getHeight() const
Definition: Box.h:155
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.
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
void include(Point2I const &point)
Expand this to ensure that this-&gt;contains(point).
int x
A class to handle Fk5 coordinates (inherits from Coord)
Definition: Coord.h:191
estimate sample median
Definition: Statistics.h:70
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
bool isEmpty() const
Return true if the box contains no points.
Definition: Box.h:166
boost::shared_ptr< Record2 > second
Definition: Match.h:49
boost::shared_ptr< afw::image::TanWcs > _newWcs
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1082
Compute Image Statistics.
afw::table::Key< double > b
afw::geom::Angle _getScatterSky(afw::image::Wcs const &wcs, std::vector< MatchT > const &matches)
Record class that contains measurements made on a single exposure.
Definition: Source.h:81
#define INSTANTIATE(MATCH)
Record class that must contain a unique ID field and a celestial coordinate field.
Definition: Simple.h:46
int const y0
Definition: saturated.cc:45
ImageT val
Definition: CR.cc:154
A class to handle Icrs coordinates (inherits from Coord)
Definition: Coord.h:157
int getWidth() const
Definition: Box.h:154
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
lsst::afw::geom::Angle angularSeparation(Coord const &c) const
compute the angular separation between two Coords
Definition: Coord.cc:689
table::PointKey< double > crpix
Definition: Wcs.cc:1036