LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
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;
72  for (int i = 1; i <= order; ++i) {
73  nTerms += i;
74  }
75 
76  int const n = axis1.size();
77  Eigen::MatrixXd C = Eigen::MatrixXd::Zero(n, nTerms);
78  for (int i = 0; i < n; ++i) {
79  for (int j = 0; j < nTerms; ++j) {
80  std::pair<int, int> pq = indexToPQ(j, order);
81  int p = pq.first, q = pq.second;
82 
83  assert(p + q < order);
84  C(i, j) = ::pow(axis1[i], p)*::pow(axis2[i], q);
85  }
86  }
87 
88  return C;
89 }
90 
91 
92 
99 Eigen::VectorXd
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);
103  return par;
104 }
105 
106 } // anonymous namespace
107 
109 template<class MatchT>
111  std::vector<MatchT> const & matches,
112  afwImg::Wcs const& linearWcs,
113  int const order,
114  afwGeom::Box2I const& bbox,
115  int const ngrid
116 ):
117  _log(pexLog::Log(pexLog::Log::getDefaultLog(), "meas.astrom.sip")),
118  _matches(matches),
119  _bbox(bbox),
120  _ngrid(ngrid),
121  _linearWcs(linearWcs.clone()),
122  _sipOrder(order+1),
123  _reverseSipOrder(order+2), //Higher order for reverse transform
124  _sipA(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
125  _sipB(Eigen::MatrixXd::Zero(_sipOrder, _sipOrder)),
126  _sipAp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
127  _sipBp(Eigen::MatrixXd::Zero(_reverseSipOrder, _reverseSipOrder)),
128  _newWcs()
129 {
130  if (order < 2) {
131  throw LSST_EXCEPT(except::OutOfRangeError, "SIP must be at least 2nd order");
132  }
133  if (_sipOrder > 9) {
134  throw LSST_EXCEPT(except::OutOfRangeError,
135  str(boost::format("SIP forward order %d exceeds the convention limit of 9") %
136  _sipOrder));
137  }
138  if (_reverseSipOrder > 9) {
139  throw LSST_EXCEPT(except::OutOfRangeError,
140  str(boost::format("SIP reverse order %d exceeds the convention limit of 9") %
142  }
143 
144  if (_matches.size() < std::size_t(_sipOrder)) {
145  throw LSST_EXCEPT(except::LengthError, "Number of matches less than requested sip order");
146  }
147 
148  if (_ngrid <= 0) {
149  _ngrid = 5*_sipOrder; // should be plenty
150  }
151 
152  /*
153  * We need a bounding box to define the region over which:
154  * The forward transformation should be valid
155  * We calculate the reverse transformartion
156  * If no BBox is provided, guess one from the input points (extrapolated a bit to allow for fact
157  * that a finite number of points won't reach to the edge of the image)
158  */
159  if (_bbox.isEmpty() && !_matches.empty() > 0) {
160  for (
161  typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
162  ptr != _matches.end();
163  ++ptr
164  ) {
165  afwTable::SourceRecord const & src = *ptr->second;
166  _bbox.include(afwGeom::PointI(src.getX(), src.getY()));
167  }
168  float const borderFrac = 1/::sqrt(_matches.size()); // fractional border to add to exact BBox
169  afwGeom::Extent2I border(borderFrac*_bbox.getWidth(), borderFrac*_bbox.getHeight());
170 
171  _bbox.grow(border);
172  }
173  // Calculate the forward part of the SIP distortion
175 
176  //Build a new wcs incorporating the forward sip matrix, it's all we know so far
178  afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
179  Eigen::MatrixXd CD = _linearWcs->getCDMatrix();
180 
181  _newWcs = PTR(afwImg::TanWcs)(new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
182  // Use _newWcs to calculate the forward transformation on a grid, and derive the back transformation
184 
185  //Build a new wcs incorporating both of the sip matrices
186  _newWcs = PTR(afwImg::TanWcs)(new afwImg::TanWcs(crval, crpix, CD, _sipA, _sipB, _sipAp, _sipBp));
187 
188 }
189 
190 template<class MatchT>
191 void
193 {
194  // Assumes FITS (1-indexed) coordinates.
195  afwGeom::Point2D crpix = _linearWcs->getPixelOrigin();
196 
197  // Calculate u, v and intermediate world coordinates
198  int const nPoints = _matches.size();
199  Eigen::VectorXd u(nPoints), v(nPoints), iwc1(nPoints), iwc2(nPoints);
200 
201  int i = 0;
202  for (
203  typename std::vector<MatchT>::const_iterator ptr = _matches.begin();
204  ptr != _matches.end();
205  ++ptr, ++i
206  ) {
207  afwTable::ReferenceMatch const & match = *ptr;
208 
209  // iwc: intermediate world coordinate positions of catalogue objects
210  afwCoord::IcrsCoord c = match.first->getCoord();
211  afwGeom::Point2D p = _linearWcs->skyToIntermediateWorldCoord(c);
212  iwc1[i] = p[0];
213  iwc2[i] = p[1];
214  // u and v are intermediate pixel coordinates of observed (distorted) positions
215  u[i] = match.second->getX() - crpix[0];
216  v[i] = match.second->getY() - crpix[1];
217  }
218 
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  Eigen::Matrix2d CD;
237  CD(1,0) = nu[ord];
238  CD(1,1) = nu[1];
239  CD(0,0) = mu[ord];
240  CD(0,1) = mu[1];
241 
242  Eigen::Matrix2d CDinv = CD.inverse(); //Direct inverse OK for 2x2 matrix in Eigen
243 
244  // The zeroth elements correspond to a shift in crpix
245  crpix[0] -= mu[0]*CDinv(0,0) + nu[0]*CDinv(0,1);
246  crpix[1] -= mu[0]*CDinv(1,0) + nu[0]*CDinv(1,1);
247 
248  afwGeom::Point2D crval = _getCrvalAsGeomPoint();
249 
250  _linearWcs = afwImg::Wcs::Ptr( new afwImg::Wcs(crval, crpix, CD));
251 
252  //Get Sip terms
253 
254  //The rest of the elements correspond to
255  //mu[i] == CD11*Apq + CD12*Bpq and
256  //nu[i] == CD21*Apq + CD22*Bpq and
257  //
258  //We solve for Apq and Bpq with the equation
259  // (Apq) = (CD11 CD12)-1 * (mu[i])
260  // (Bpq) (CD21 CD22) (nu[i])
261 
262  for(int i=1; i< mu.rows(); ++i) {
263  std::pair<int, int> pq = indexToPQ(i, ord);
264  int p = pq.first, q = pq.second;
265 
266  if(p + q > 1 && p + q < ord) {
267  Eigen::Vector2d munu(2,1);
268  munu(0) = mu(i);
269  munu(1) = nu(i);
270  Eigen::Vector2d AB = CDinv*munu;
271  _sipA(p,q) = AB[0];
272  _sipB(p,q) = AB[1];
273  }
274  }
275 }
276 
277 template<class MatchT>
279  int const ngrid2 = _ngrid*_ngrid;
280 
281  Eigen::VectorXd U(ngrid2), V(ngrid2);
282  Eigen::VectorXd delta1(ngrid2), delta2(ngrid2);
283 
284  int const x0 = _bbox.getMinX();
285  double const dx = _bbox.getWidth()/(double)(_ngrid - 1);
286  int const y0 = _bbox.getMinY();
287  double const dy = _bbox.getHeight()/(double)(_ngrid - 1);
288 
289  // wcs->getPixelOrigin() returns LSST-style (0-indexed) pixel coords.
290  afwGeom::Point2D crpix = _newWcs->getPixelOrigin();
291 
292  _log.debugf("_calcReverseMatrices: x0,y0 %i,%i, W,H %i,%i, ngrid %i, dx,dy %g,%g, CRPIX %g,%g",
293  x0, y0, _bbox.getWidth(), _bbox.getHeight(), _ngrid, dx, dy, crpix[0], crpix[1]);
294 
295  int k = 0;
296  for (int i = 0; i < _ngrid; ++i) {
297  double const y = y0 + i*dy;
298  for (int j = 0; j < _ngrid; ++j, ++k) {
299  double const x = x0 + j*dx;
300  double u,v;
301  // u and v are intermediate pixel coordinates on a grid of positions
302  u = x - crpix[0];
303  v = y - crpix[1];
304 
305  // U and V are the result of applying the "forward" (A,B) SIP coefficients
306  // NOTE that the "undistortPixel()" function accepts 1-indexed (FITS-style)
307  // coordinates, and here we are treating "x" and "y" as LSST-style.
308  afwGeom::Point2D xy = _newWcs->undistortPixel(afwGeom::Point2D(x + 1, y + 1));
309  // "crpix", on the other hand, is LSST-style 0-indexed, so we have to remove
310  // the FITS-style 1-index from "xy"
311  U[k] = xy[0] - 1 - crpix[0];
312  V[k] = xy[1] - 1 - crpix[1];
313 
314  if ((i == 0 || i == (_ngrid-1) || i == (_ngrid/2)) &&
315  (j == 0 || j == (_ngrid-1) || j == (_ngrid/2))) {
316  _log.debugf(" x,y (%.1f, %.1f), u,v (%.1f, %.1f), U,V (%.1f, %.1f)", x, y, u, v, U[k], V[k]);
317  }
318 
319  delta1[k] = u - U[k];
320  delta2[k] = v - V[k];
321  }
322  }
323 
324  // Reverse transform
325  int const ord = _reverseSipOrder;
326  Eigen::MatrixXd reverseC = calculateCMatrix(U, V, ord);
327  Eigen::VectorXd tmpA = leastSquaresSolve(delta1, reverseC);
328  Eigen::VectorXd tmpB = leastSquaresSolve(delta2, reverseC);
329 
330  assert(tmpA.rows() == tmpB.rows());
331  for(int j=0; j< tmpA.rows(); ++j) {
332  std::pair<int, int> pq = indexToPQ(j, ord);
333  int p = pq.first, q = pq.second;
334  _sipAp(p, q) = tmpA[j];
335  _sipBp(p, q) = tmpB[j];
336  }
337 }
338 
339 template<class MatchT>
341  assert(_newWcs.get());
342  return _getScatterPixels(*_newWcs, _matches);
343 }
344 
345 template<class MatchT>
347  assert(_linearWcs.get());
348  return _getScatterPixels(*_linearWcs, _matches);
349 }
350 
351 template<class MatchT>
353  afwImg::Wcs const& wcs,
354  std::vector<MatchT> const & matches) {
355  std::vector<double> val;
356  val.reserve(matches.size());
357 
358  for (
359  typename std::vector<MatchT>::const_iterator ptr = matches.begin();
360  ptr != matches.end();
361  ++ptr
362  ) {
363  afwTable::ReferenceMatch const & match = *ptr;
364  PTR(afwTable::SimpleRecord) cat = match.first;
365  PTR(afwTable::SourceRecord) src = match.second;
366  double imgX = src->getX();
367  double imgY = src->getY();
368  afwGeom::Point2D xy = wcs.skyToPixel(cat->getCoord());
369  double catX = xy[0];
370  double catY = xy[1];
371  val.push_back(::hypot(imgX - catX, imgY - catY));
372  }
374 }
375 
376 
377 template<class MatchT>
379  assert(_newWcs.get());
380  return _getScatterSky(*_newWcs, _matches);
381 }
382 
383 template<class MatchT>
385  assert(_linearWcs.get());
386  return _getScatterSky(*_linearWcs, _matches);
387 }
388 
389 template<class MatchT>
391  afwImg::Wcs const & wcs,
392  std::vector<MatchT> const & matches) {
393  std::vector<double> val;
394  val.reserve(matches.size());
395 
396  for (
397  typename std::vector<MatchT>::const_iterator ptr = matches.begin();
398  ptr != matches.end();
399  ++ptr
400  ) {
401  afwTable::ReferenceMatch const & match = *ptr;
402  PTR(afwTable::SimpleRecord) cat = match.first;
403  PTR(afwTable::SourceRecord) src = match.second;
404  afwCoord::IcrsCoord catRadec = cat->getCoord();
405  CONST_PTR(afwCoord::Coord) imgRadec = wcs.pixelToSky(src->getCentroid());
406  afwGeom::Angle sep = catRadec.angularSeparation(*imgRadec);
407  val.push_back(sep.asDegrees());
408  }
409  assert(val.size() > 0);
411 }
412 
413 
414 template<class MatchT>
416  afwCoord::Fk5Coord coo = _linearWcs->getSkyOrigin()->toFk5();
417  return coo.getPosition(afwGeom::degrees);
418 }
419 
420 
421 #define INSTANTIATE(MATCH) \
422  template class CreateWcsWithSip<MATCH>;
423 
426 
427 }}}}
428 
429 
int y
double getValue(Property const prop=NOTHING) const
Return the value of the desired property (if specified in the constructor)
Definition: Statistics.cc:950
#define CONST_PTR(...)
Definition: base.h:47
double dx
Definition: ImageUtils.cc:90
double getX() const
Return the centroid slot x coordinate.
Definition: Source.h:836
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:788
double getY() const
Return the centroid slot y coordinate.
Definition: Source.h:839
MatchVector _matches
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
#define PTR(...)
Definition: base.h:41
double dy
Definition: ImageUtils.cc:90
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:864
int const x0
Definition: saturated.cc:45
double asDegrees() const
Definition: Angle.h:124
table::Key< table::Point< double > > crval
Definition: Wcs.cc:1041
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:838
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
table::Key< table::Point< double > > crpix
Definition: Wcs.cc:1042
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:187
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:1023
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:45
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:155
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:476
lsst::afw::geom::Angle angularSeparation(Coord const &c) const
compute the angular separation between two Coords
Definition: Coord.cc:688