LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
Wcs.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 #include <iostream>
26 #include <sstream>
27 #include <cmath>
28 #include <cstring>
29 
30 #include "boost/format.hpp"
31 
32 #include "wcslib/wcs.h"
33 #include "wcslib/wcsfix.h"
34 #include "wcslib/wcshdr.h"
35 
36 #include "lsst/daf/base.h"
37 #include "lsst/daf/base/Citizen.h"
40 #include "lsst/pex/exceptions.h"
42 #include "lsst/afw/image/Wcs.h"
43 #include "lsst/afw/coord/Coord.h"
44 #include "lsst/afw/geom/Angle.h"
48 
49 namespace except = lsst::pex::exceptions;
50 namespace afwImg = lsst::afw::image;
51 namespace afwCoord = lsst::afw::coord;
52 namespace afwGeom = lsst::afw::geom;
53 
54 
55 using namespace std;
56 
63 
64 //The amount of space allocated to strings in wcslib
65 const int STRLEN = 72;
66 
67 //Set internal params for wcslib
69 {
70  _wcsfixCtrl = // ctrl for wcsfix
71  2; // Translate "H" to "h"
72  _wcshdrCtrl = // ctrl for wcspih
73  2; // Report each rejected keyrecord and the reason why it was rejected
74  _relax = // relax parameter for wcspih;
75  WCSHDR_all; // Accept all extensions recognized by the parser
76 }
77 
78 const int lsstToFitsPixels = +1;
79 const int fitsToLsstPixels = -1;
80 
81 //
82 // Constructors
83 //
84 
85 
88  daf::base::Citizen(typeid(this)),
89  _wcsInfo(NULL), _nWcsInfo(0), _relax(0), _wcsfixCtrl(0), _wcshdrCtrl(0), _nReject(0),
90  _coordSystem(static_cast<afwCoord::CoordSystem>(-1)) {
92  _initWcs();
93 }
94 
95 
99  daf::base::Citizen(typeid(this)),
100  _wcsInfo(NULL),
101  _nWcsInfo(0),
102  _relax(0),
103  _wcsfixCtrl(0),
104  _wcshdrCtrl(0),
105  _nReject(0),
106  _coordSystem(static_cast<afwCoord::CoordSystem>(-1))
107 {
109 
110  initWcsLibFromFits(fitsMetadata);
111  _initWcs();
112 }
113 
114 /*
115  * Set some internal variables that we need to refer to
116  */
118 {
119  if (_wcsInfo) {
121 
122  // tell WCSlib that values have been updated
123  _wcsInfo->flag = 0;
124  // and then tell it to do its internal magic.
125  int status = wcsset(_wcsInfo);
126  if (status != 0) {
127  throw LSST_EXCEPT(except::RuntimeError,
128  (boost::format("Failed to setup wcs structure with wcsset. Status %d: %s") %
129  status % wcs_errmsg[status] ).str());
130  }
131  }
132 }
133 
148 Wcs::Wcs(GeomPoint const & crval, GeomPoint const & crpix, Eigen::Matrix2d const & CD,
149  std::string const & ctype1, std::string const & ctype2,
150  double equinox, std::string const & raDecSys,
151  std::string const & cunits1, std::string const & cunits2
152 ):
153  daf::base::Citizen(typeid(this)),
154  _wcsInfo(NULL),
155  _nWcsInfo(0),
156  _relax(0),
157  _wcsfixCtrl(0),
158  _wcshdrCtrl(0),
159  _nReject(0),
160  _coordSystem(static_cast<afwCoord::CoordSystem>(-1))
161 {
163  initWcsLib(crval, crpix, CD,
164  ctype1, ctype2,
165  equinox, raDecSys,
166  cunits1, cunits2);
167  _initWcs();
168 }
169 
170 
177  class HeaderAccess {
178  public:
180  CONST_PTR(lsst::daf::base::PropertySet) const& toRead() { return _constHeader; }
182  PTR(lsst::daf::base::PropertySet) const& toWrite() {
183  if (!_hackHeader) {
184  _hackHeader = _constHeader->deepCopy();
185  _constHeader = _hackHeader;
186  }
187  return _hackHeader;
188  }
189 
191  HeaderAccess(CONST_PTR(lsst::daf::base::PropertySet) const& header) :
192  _constHeader(header), _hackHeader() {}
193 
194  private:
196  PTR(lsst::daf::base::PropertySet) _hackHeader;
197  };
198 
199  HeaderAccess access(header);
200 
201  // Some headers (e.g. SDSS ones from FNAL) have EQUINOX as a string. Fix this,
202  // as wcslib 4.4.4 refuses to handle it
203  {
204  std::string const& key = "EQUINOX";
205  if (access.toRead()->exists(key) && access.toRead()->typeOf(key) == typeid(std::string)) {
206  double equinox = ::atof(access.toRead()->getAsString(key).c_str());
207  access.toWrite()->set(key, equinox);
208  }
209  }
210 
211  //Check header isn't empty
212  int nCards = lsst::afw::formatters::countFitsHeaderCards(access.toRead());
213  if (nCards <= 0) {
214  string msg = "Could not parse FITS WCS: no header cards found";
215  throw LSST_EXCEPT(except::InvalidParameterError, msg);
216  }
217 
218  // Scamp produces PVi_xx header cards that are inconsistent with WCS Paper 2
219  // and cause WCSLib to choke. Aggressively, rename all PV keywords to X_PV
220  for (int j=1; j<3; j++) {
221  for (int i=0; i<=99; i++) {
222  std::string key = (boost::format("PV%i_%i") % j % i).str();
223  if (!access.toRead()->exists(key)) {
224  break;
225  }
226  double val = access.toRead()->getAsDouble(key);
227  access.toWrite()->remove(key);
228  access.toWrite()->add("X_"+key, val);
229  }
230  }
231 
232  //While the standard does not insist on CRVAL and CRPIX being present, it
233  //is almost certain their absence indicates a problem.
234  //Check for CRPIX
235  if( !access.toRead()->exists("CRPIX1") && !access.toRead()->exists("CRPIX1a")) {
236  string msg = "Neither CRPIX1 not CRPIX1a found";
237  throw LSST_EXCEPT(except::InvalidParameterError, msg);
238  }
239 
240  if( !access.toRead()->exists("CRPIX2") && !access.toRead()->exists("CRPIX2a")) {
241  string msg = "Neither CRPIX2 not CRPIX2a found";
242  throw LSST_EXCEPT(except::InvalidParameterError, msg);
243  }
244 
245  //And the same for CRVAL
246  if( !access.toRead()->exists("CRVAL1") && !access.toRead()->exists("CRVAL1a")) {
247  string msg = "Neither CRVAL1 not CRVAL1a found";
248  throw LSST_EXCEPT(except::InvalidParameterError, msg);
249  }
250 
251  if( !access.toRead()->exists("CRVAL2") && !access.toRead()->exists("CRVAL2a")) {
252  string msg = "Neither CRVAL2 not CRVAL2a found";
253  throw LSST_EXCEPT(except::InvalidParameterError, msg);
254  }
255  /*
256  * According to Greisen and Calabretta (A&A 395, 1061–1075 (2002)) it's illegal to mix PCi_j and CDi_j
257  * headers; unfortunately Subaru puts both in its headers. It actually uses PC001002 instead of PC1_2
258  * (dating to a proposed FITS standard from 1996) and at least sometimes fails to include CDELT[12],
259  * so the CD and PC matrices are inconsistent
260  *
261  * If we detect any part of a CD matrix, delete all PC matrices
262  */
263  if(access.toRead()->exists("CD1_1") || access.toRead()->exists("CD1_2") ||
264  access.toRead()->exists("CD2_1") || access.toRead()->exists("CD2_2")) {
265  for (int i = 1; i <= 2; ++i) {
266  for (int j = 1; j <= 2; ++j) {
267  std::string key = (boost::format("PC%i_%i") % j % i).str();
268  if (access.toRead()->exists(key)) {
269  double const val = access.toRead()->getAsDouble(key);
270  access.toWrite()->remove(key);
271  access.toWrite()->add("X_" + key, val);
272  }
273 
274  key = (boost::format("PC%03d%03d") % j % i).str();
275  if (access.toRead()->exists(key)) {
276  double const val = access.toRead()->getAsDouble(key);
277  access.toWrite()->remove(key);
278  access.toWrite()->add("X_" + key, val);
279  }
280  }
281  }
282  }
283 
284  //Pass the header into wcslib's formatter to extract & setup the Wcs. First need
285  //to convert to a C style string, so the compile doesn't complain about constness
286  std::string metadataStr = lsst::afw::formatters::formatFitsProperties(access.toRead());
287  // We own the data, and wcslib is slack about constness, so no qualms with casting away const
288  char *hdrString = const_cast<char*>(metadataStr.c_str());
289  //printf("wcspih string:\n%s\n", hdrString);
290 
291  nCards = lsst::afw::formatters::countFitsHeaderCards(access.toRead()); // we may have dropped some
292  int pihStatus = wcspih(hdrString, nCards, _relax, _wcshdrCtrl, &_nReject, &_nWcsInfo, &_wcsInfo);
293 
294  if (pihStatus != 0) {
295  throw LSST_EXCEPT(except::RuntimeError,
296  (boost::format("Could not parse FITS WCS: wcspih status = %d (%s)") %
297  pihStatus % wcs_errmsg[pihStatus]).str());
298  }
299 
300  //Run wcsfix on _wcsInfo to try and fix any problems it knows about.
301  const int *naxes = NULL; // should be {NAXIS1, NAXIS2, ...} to check cylindrical projections
302  int stats[NWCSFIX]; // status returns from wcsfix
303  int fixStatus = wcsfix(_wcsfixCtrl, naxes, _wcsInfo, stats);
304  if (fixStatus != 0) {
305  std::stringstream errStream;
306  errStream << "Could not parse FITS WCS: wcsfix failed " << std::endl;
307  for (int ii = 0; ii < NWCSFIX; ++ii) {
308  if (stats[ii] >= 0) {
309  errStream << "\t" << ii << ": " << stats[ii] << " " << wcsfix_errmsg[stats[ii]] << std::endl;
310  } else {
311  errStream << "\t" << ii << ": " << stats[ii] << std::endl;
312  }
313  }
314  }
315 
316  //The Wcs standard requires a default value for RADESYS if the keyword
317  //doesn't exist in header, but wcslib doesn't set it. So we do so here. This code
318  //conforms to Calabretta & Greisen 2002 \S 3.1
319  if (!(access.toRead()->exists("RADESYS") || access.toRead()->exists("RADESYSa"))) {
320  // If RADECSYS exists, use that (counter to Calabretta & Greisen 2002 \S 3.1, but commonly used).
321  // If equinox exist and < 1984, use FK4. If >= 1984, use FK5
322  if (access.toRead()->exists("RADECSYS")) {
323  strncpy(_wcsInfo->radesys, access.toRead()->getAsString("RADECSYS").c_str(), STRLEN);
324  } else if (access.toRead()->exists("EQUINOX") || access.toRead()->exists("EQUINOXa")) {
325  std::string const EQUINOX = access.toRead()->exists("EQUINOX") ? "EQUINOX" : "EQUINOXa";
326  double const equinox = access.toRead()->getAsDouble(EQUINOX);
327  if(equinox < 1984) {
328  strncpy(_wcsInfo->radesys, "FK4", STRLEN);
329  } else {
330  strncpy(_wcsInfo->radesys, "FK5", STRLEN);
331  }
332  } else {
333  //If Equinox doesn't exist, default to ICRS
334  strncpy(_wcsInfo->radesys, "ICRS", STRLEN);
335  }
336  }
337  // strip trailing whitespace
338  {
339  for(int i = strlen(_wcsInfo->radesys) - 1; i >= 0; i--) {
340  if (isspace(_wcsInfo->radesys[i])) {
341  _wcsInfo->radesys[i] = '\0';
342  }
343  }
344  }
345  //
346  // If there are no CDi_j cards in the header, set CDi_j from PCi_j
347  // CDi_j == CDELTi*PCi_j
348  //
349  if ((_wcsInfo->altlin & 2) == 0) { // no CDi_j cards were found in the header
350  double const *cdelt = _wcsInfo->cdelt;
351  double const *pc = _wcsInfo->pc;
352  double *cd = _wcsInfo->cd;
353 
354  cd[0] = cdelt[0]*pc[0]; // 1_1
355  cd[1] = cdelt[0]*pc[1]; // 1_2
356  cd[2] = cdelt[1]*pc[2]; // 2_1
357  cd[3] = cdelt[1]*pc[3]; // 2_2
358  }
359 }
360 
371 void Wcs::initWcsLib(GeomPoint const & crval, GeomPoint const & crpix, Eigen::Matrix2d const & CD,
372  std::string const & ctype1, std::string const & ctype2,
373  double equinox, std::string const & raDecSys,
374  std::string const & cunits1, std::string const & cunits2) {
375 
376  //Check CD is a valid size
377  if( (CD.rows() != 2) || (CD.cols() != 2) ) {
378  string msg = "CD is not a 2x2 matrix";
379  throw LSST_EXCEPT(except::InvalidParameterError, msg);
380  }
381 
382  //Check that cunits are legitimate values
383  bool isValid = (cunits1 == "deg");
384  isValid |= (cunits1 == "arcmin");
385  isValid |= (cunits1 == "arcsec");
386  isValid |= (cunits1 == "mas");
387 
388  if (!isValid) {
389  string msg = "CUNITS1 must be one of {deg|arcmin|arcsec|mas}";
390  throw LSST_EXCEPT(except::InvalidParameterError, msg);
391  }
392 
393  isValid = (cunits2 == "deg");
394  isValid |= (cunits2 == "arcmin");
395  isValid |= (cunits2 == "arcsec");
396  isValid |= (cunits2 == "mas");
397 
398  if (!isValid) {
399  string msg = "CUNITS2 must be one of {deg|arcmin|arcsec|mas}";
400  throw LSST_EXCEPT(except::InvalidParameterError, msg);
401  }
402 
403  //Initialise the wcs struct
404  _wcsInfo = static_cast<struct wcsprm *>(malloc(sizeof(struct wcsprm)));
405  if (_wcsInfo == NULL) {
406  throw LSST_EXCEPT(except::MemoryError, "Cannot allocate WCS info");
407  }
408 
409  _wcsInfo->flag = -1;
410  int status = wcsini(true, 2, _wcsInfo); //2 indicates a naxis==2, a two dimensional image
411  if(status != 0) {
412  throw LSST_EXCEPT(except::MemoryError,
413  (boost::format("Failed to allocate memory with wcsini. Status %d: %s") %
414  status % wcs_errmsg[status] ).str());
415  }
416 
417  //Set crval, crpix and CD. Internally to the class, we use fits units for consistency with
418  //wcslib.
419  _wcsInfo->crval[0] = crval.getX();
420  _wcsInfo->crval[1] = crval.getY();
421  _wcsInfo->crpix[0] = crpix.getX() + lsstToFitsPixels;
422  _wcsInfo->crpix[1] = crpix.getY() + lsstToFitsPixels;
423 
424  //Set the CD matrix
425  for (int i=0; i<2; ++i) {
426  for (int j=0; j<2; ++j) {
427  _wcsInfo->cd[(2*i) + j] = CD(i,j);
428  }
429  }
430 
431  //Specify that we have a CD matrix, but no PC or CROTA
432  _wcsInfo->altlin = 2;
433  _wcsInfo->flag = 0; //values have been updated
434 
435  //This is a work around for what I think is a bug in wcslib. ->types is neither
436  //initialised or set to NULL by default, so if I try to delete a Wcs object,
437  //wcslib then attempts to free non-existent space, and the code can crash.
438  _wcsInfo->types = NULL;
439 
440  //Set the coordinate system
441  strncpy(_wcsInfo->ctype[0], ctype1.c_str(), STRLEN);
442  strncpy(_wcsInfo->ctype[1], ctype2.c_str(), STRLEN);
443  strncpy(_wcsInfo->radesys, raDecSys.c_str(), STRLEN);
444  _wcsInfo->equinox = equinox;
445 
446  //Set the units
447  strncpy(_wcsInfo->cunit[0], cunits1.c_str(), STRLEN);
448  strncpy(_wcsInfo->cunit[1], cunits2.c_str(), STRLEN);
449 
450  _nWcsInfo = 1; //Specify that we have only one coordinate representation
451 
452  //Tell wcslib that we are need to set up internal values
453  status=wcsset(_wcsInfo);
454  if(status != 0) {
455  throw LSST_EXCEPT(except::RuntimeError,
456  (boost::format("Failed to setup wcs structure with wcsset. Status %d: %s") %
457  status % wcs_errmsg[status] ).str());
458 
459  }
460 }
461 
462 
464 Wcs::Wcs(afwImg::Wcs const & rhs) :
465  daf::base::Citizen(typeid(this)),
466  _wcsInfo(NULL),
467  _nWcsInfo(rhs._nWcsInfo),
468  _relax(rhs._relax),
469  _wcsfixCtrl(rhs._wcsfixCtrl),
470  _wcshdrCtrl(rhs._wcshdrCtrl),
471  _nReject(rhs._nReject),
472  _coordSystem(static_cast<afwCoord::CoordSystem>(-1))
473 {
474 
475  if (rhs._nWcsInfo > 0) {
476  _wcsInfo = static_cast<struct wcsprm *>(calloc(rhs._nWcsInfo, sizeof(struct wcsprm)));
477  if (_wcsInfo == NULL) {
478  throw LSST_EXCEPT(lsst::pex::exceptions::MemoryError, "Cannot allocate WCS info");
479  }
480 
481  _wcsInfo->flag = -1;
482  int alloc = 1; //Unconditionally allocate memory when calling
483  for (int i = 0; i != rhs._nWcsInfo; ++i) {
484  int status = wcscopy(alloc, &rhs._wcsInfo[i], &_wcsInfo[i]);
485  if (status != 0) {
486  wcsvfree(&i, &_wcsInfo);
487  throw LSST_EXCEPT(lsst::pex::exceptions::MemoryError,
488  (boost::format("Could not copy WCS: wcscopy status = %d : %s") %
489  status % wcs_errmsg[status]).str());
490  }
491  }
492  }
493  _initWcs();
494 }
495 
496 bool Wcs::operator==(Wcs const & other) const {
497  if (&other == this) return true;
498  // We do a bidirectional test with a virtual member function in case one of us is a derived
499  // class with members we don't know about here.
500  // This is not the most efficient possible implementation, but I think it's the easiest one
501  // with which to ensure correctness, and I think that's more important in this case.
502  return this->_isSubset(other) && other._isSubset(*this);
503 }
504 
505 // convenience functions and a macro for implementing isSubset
506 namespace {
507 
508 inline bool compareArrays(double const * a, double const * b, int n) {
509  for (int i = 0; i < n; ++i) if (a[i] != b[i]) return false;
510  return true;
511 }
512 
513 template <typename T>
514 inline bool compareStringArrays(T a, T b, int n) {
515  for (int i = 0; i < n; ++i) if (strcmp(a[i], b[i]) != 0) return false;
516  return true;
517 }
518 
519 #define CHECK_NULLS(a, b) \
520  do { \
521  if ((a) == NULL) { \
522  if ((b) == NULL) return true; \
523  return false; \
524  } \
525  if ((b) == NULL) return false; \
526  } while (false)
527 
528 } // anonymous
529 
530 bool Wcs::_isSubset(Wcs const & rhs) const {
532  CHECK_NULLS(_wcsInfo->crval, rhs._wcsInfo->crval);
533  CHECK_NULLS(_wcsInfo->cd, rhs._wcsInfo->cd);
534  CHECK_NULLS(_wcsInfo->crpix, rhs._wcsInfo->crpix);
535  CHECK_NULLS(_wcsInfo->cunit, rhs._wcsInfo->cunit);
536  CHECK_NULLS(_wcsInfo->ctype, rhs._wcsInfo->ctype);
537  return _nWcsInfo == rhs._nWcsInfo &&
538  _coordSystem == rhs._coordSystem &&
539  _wcsInfo->naxis == rhs._wcsInfo->naxis &&
540  _wcsInfo->equinox == rhs._wcsInfo->equinox &&
541  _wcsInfo->altlin == rhs._wcsInfo->altlin &&
542  compareArrays(_wcsInfo->crval, rhs._wcsInfo->crval, 2) &&
543  compareArrays(_wcsInfo->crpix, rhs._wcsInfo->crpix, 2) &&
544  compareArrays(_wcsInfo->cd, rhs._wcsInfo->cd, 4) &&
545  compareStringArrays(_wcsInfo->cunit, rhs._wcsInfo->cunit, 2) &&
546  compareStringArrays(_wcsInfo->ctype, rhs._wcsInfo->ctype, 2) &&
548  _wcsInfo->crval[1] * afwGeom::degrees) ==
549  rhs.skyToPixel(_wcsInfo->crval[0] * afwGeom::degrees,
550  _wcsInfo->crval[1] * afwGeom::degrees) &&
551  *pixelToSky(_wcsInfo->crpix[0], _wcsInfo->crpix[1]) ==
552  *rhs.pixelToSky(_wcsInfo->crpix[0], _wcsInfo->crpix[1]);
553 }
554 
556  if (_wcsInfo != NULL) {
557  wcsvfree(&_nWcsInfo, &_wcsInfo);
558  }
559 }
560 
561 
562 Wcs::Ptr Wcs::clone(void) const {
563  return Wcs::Ptr(new Wcs(*this));
564 }
565 
566 //
567 // Accessors
568 //
569 
572  assert(_wcsInfo);
573  return makeCorrectCoord(_wcsInfo->crval[0] * afwGeom::degrees, _wcsInfo->crval[1] * afwGeom::degrees);
574 }
575 
578  assert(_wcsInfo);
579  //Convert from fits units back to lsst units
580  double p1 = _wcsInfo->crpix[0] + fitsToLsstPixels;
581  double p2 = _wcsInfo->crpix[1] + fitsToLsstPixels;
582  return afwGeom::Point2D(p1, p2);
583 }
584 
585 
587 Eigen::Matrix2d Wcs::getCDMatrix() const {
588  assert(_wcsInfo);
589  int const naxis = _wcsInfo->naxis;
590 
591  //If naxis != 2, I'm not sure if any of what follows is correct
592  assert(naxis == 2);
593 
594  Eigen::Matrix2d C;
595 
596  for (int i=0; i< naxis; ++i){
597  for (int j=0; j<naxis; ++j) {
598  C(i,j) = _wcsInfo->cd[ (i*naxis) + j ];
599  }
600  }
601 
602  return C;
603 }
605 void Wcs::flipImage(int flipLR, int flipTB, afwGeom::Extent2I dimensions) const {
606  assert(_wcsInfo);
607 
608  int const naxis = _wcsInfo->naxis;
609 
610  //If naxis != 2, I'm not sure if any of what follows is correct
611  assert(naxis == 2);
612  if (flipLR) {
613  _wcsInfo->cd[0] = -_wcsInfo->cd[0];
614  _wcsInfo->cd[2] = -_wcsInfo->cd[2];
615  _wcsInfo->crpix[0] = -_wcsInfo->crpix[0] + dimensions.getX();
616  }
617  if (flipTB) {
618  _wcsInfo->cd[1] = -_wcsInfo->cd[1];
619  _wcsInfo->cd[3] = -_wcsInfo->cd[3];
620  _wcsInfo->crpix[1] = -_wcsInfo->crpix[1]+dimensions.getY();
621  }
622 
623  // tells libwcs to invalidate cached data, since transformation has been modified
624  _wcsInfo->flag = 0;
625 }
626 
628  assert(_wcsInfo);
629 
630  while (nQuarter < 0 ) {
631  nQuarter += 4;
632  }
633 
634 
635  int const naxis = _wcsInfo->naxis;
636 
637  //If naxis != 2, I'm not sure if any of what follows is correct
638  assert(naxis == 2);
639  double a = _wcsInfo->cd[0];
640  double b = _wcsInfo->cd[1];
641  double c = _wcsInfo->cd[2];
642  double d = _wcsInfo->cd[3];
643  double crpx = _wcsInfo->crpix[0];
644  double crpy = _wcsInfo->crpix[1];
645  switch (nQuarter%4) {
646  case 0:
647  break;
648  case 1:
649  _wcsInfo->cd[0] = -b;
650  _wcsInfo->cd[1] = a;
651  _wcsInfo->cd[2] = -d;
652  _wcsInfo->cd[3] = c;
653  _wcsInfo->crpix[0] = -crpy + dimensions.getY();
654  _wcsInfo->crpix[1] = crpx;
655  break;
656  case 2:
657  _wcsInfo->cd[0] = -a;
658  _wcsInfo->cd[1] = -b;
659  _wcsInfo->cd[2] = -c;
660  _wcsInfo->cd[3] = -d;
661  _wcsInfo->crpix[0] = -crpx + dimensions.getX();
662  _wcsInfo->crpix[1] = -crpy + dimensions.getY();
663  break;
664  case 3:
665  _wcsInfo->cd[0] = b;
666  _wcsInfo->cd[1] = -a;
667  _wcsInfo->cd[2] = d;
668  _wcsInfo->cd[3] = -c;
669  _wcsInfo->crpix[0] = crpy;
670  _wcsInfo->crpix[1] = -crpx + dimensions.getX();
671  break;
672  }
673 
674  // tells libwcs to invalidate cached data, since transformation has been modified
675  _wcsInfo->flag = 0;
676 }
680 }
681 
682 
683 
684 
696 bool Wcs::isFlipped() const {
697  assert(_wcsInfo);
698  double det = (_wcsInfo->cd[0] * _wcsInfo->cd[3]) - (_wcsInfo->cd[1] * _wcsInfo->cd[2]);
699 
700  if (det == 0) {
701  throw(LSST_EXCEPT(except::RuntimeError, "Wcs CD matrix is singular"));
702  }
703 
704  return (det > 0);
705 }
706 
707 static double square(double x) {
708  return x*x;
709 }
710 
712 double Wcs::pixArea(GeomPoint pix00
713  ) const {
714  //
715  // Figure out the (0, 0), (0, 1), and (1, 0) ra/dec coordinates of the corners of a square drawn in pixel
716  // It'd be better to centre the square at sky00, but that would involve another conversion between sky and
717  // pixel coordinates so I didn't bother
718  //
719  const double side = 1; // length of the square's sides in pixels
720 
721  // Work in 3-space to avoid RA wrapping and pole issues.
722  afwGeom::Point3D v0 = pixelToSky(pix00)->getVector();
723 
724  // Step by "side" in x and y pixel directions...
725  GeomPoint px(pix00);
726  GeomPoint py(pix00);
727  px.shift(afwGeom::Extent2D(side, 0));
728  py.shift(afwGeom::Extent2D(0, side));
729  // Push the points through the WCS, and find difference in 3-space.
730  afwGeom::Extent3D dx = pixelToSky(px)->getVector() - v0;
731  afwGeom::Extent3D dy = pixelToSky(py)->getVector() - v0;
732 
733  // Compute |cross product| = area of parallelogram with sides dx,dy
734  // FIXME -- this is slightly incorrect -- it's making the small-angle
735  // approximation, taking the distance *through* the unit sphere
736  // rather than over its surface.
737  // This is in units of ~radians^2
738  double area = sqrt(square(dx[1]*dy[2] - dx[2]*dy[1]) +
739  square(dx[2]*dy[0] - dx[0]*dy[2]) +
740  square(dx[0]*dy[1] - dx[1]*dy[0]));
741 
742  return area / square(side) * square(180. / afwGeom::PI);
743 }
744 
746  return sqrt(pixArea(getPixelOrigin())) * afwGeom::degrees;
747 }
748 
749 /*
750  * Worker routine for skyToPixel
751  */
752 GeomPoint Wcs::skyToPixelImpl(afwGeom::Angle sky1, // RA (or, more generally, longitude)
753  afwGeom::Angle sky2 // Dec (or latitude)
754  ) const {
755  assert(_wcsInfo);
756 
757  double skyTmp[2];
758  double imgcrd[2];
759  double phi, theta;
760  double pixTmp[2];
761  /*
762  printf("_skyCoordsReversed: %c\n", (_skyCoordsReversed ? 'T' : 'F'));
763  printf("wcsinfo.lat: %i, lng: %i\n", _wcsInfo->lat, _wcsInfo->lng);
764  */
765  // WCSLib is smart enough to notice and handle crazy SDSS CTYPE1 = DEC--TAN,
766  // by recording the indices of the long and lat coordinates.
767  skyTmp[_wcsInfo->lng] = sky1.asDegrees();
768  skyTmp[_wcsInfo->lat] = sky2.asDegrees();
769 
770  int stat[1];
771  int status = 0;
772  status = wcss2p(_wcsInfo, 1, 2, skyTmp, &phi, &theta, imgcrd, pixTmp, stat);
773  if (status == 9) {
774  throw LSST_EXCEPT(except::DomainError,
775  (boost::format("sky coordinates %s, %s degrees is not valid for this WCS")
776  % sky1.asDegrees() % sky2.asDegrees()
777  ).str()
778  );
779  }
780  if (status > 0) {
781  throw LSST_EXCEPT(except::RuntimeError,
782  (boost::format("Error: wcslib returned a status code of %d at sky %s, %s deg: %s") %
783  status % sky1.asDegrees() % sky2.asDegrees() % wcs_errmsg[status]).str());
784  }
785 
786  // wcslib assumes 1-indexed coords
789 }
790 
793  return skyToPixelImpl(sky->getLongitude(), sky->getLatitude());
794 }
795 
796 
799  return coord.convert(_coordSystem);
800 }
801 
807 
809  return skyToPixelImpl(sky1, sky2);
810 }
811 
813  assert(_wcsInfo);
814 
816  double skyTmp[2];
817  double imgcrd[2];
818  double phi, theta;
819  double pixTmp[2];
820 
821  /*
822  printf("skyToIWC: _coordSystem = %i\n", (int)_coordSystem);
823  printf("coord (%.3f, %.3f)\n", coord->getLongitude().asDegrees(), coord->getLatitude().asDegrees());
824  printf("->sky (%.3f, %.3f)\n", sky->getLongitude().asDegrees(), sky->getLatitude().asDegrees());
825  */
826 
827  skyTmp[_wcsInfo->lng] = sky->getLongitude().asDegrees();
828  skyTmp[_wcsInfo->lat] = sky->getLatitude() .asDegrees();
829 
830  //Estimate pixel coordinates
831  int stat[1];
832  int status = 0;
833  imgcrd[0] = imgcrd[1] = -1e6;
834  /*
835  printf(" skyTmp[] = (%.3f, %.3f)\n", skyTmp[0], skyTmp[1]);
836  printf(" _wcsInfo->lng,lat = %i, %i\n", _wcsInfo->lng, _wcsInfo->lat);
837  */
838  status = wcss2p(_wcsInfo, 1, 2, skyTmp, &phi, &theta, imgcrd, pixTmp, stat);
839  if (status > 0) {
840  throw LSST_EXCEPT(except::RuntimeError,
841  (boost::format("Error: wcslib returned a status code of %d at sky %s, %s deg: %s") %
842  status % skyTmp[0] % skyTmp[1] % wcs_errmsg[status]).str());
843  }
844  /*
845  printf("->iwc (%.3f, %.3f)\n", imgcrd[0], imgcrd[1]);
846  printf("-> pix (%.2f, %.2f)\n", pixTmp[0], pixTmp[1]);
847  afwCoord::Coord::Ptr crval = getSkyOrigin();
848  printf("(crval is (%.3f, %.3f))\n", crval->getLongitude().asDegrees(), crval->getLatitude().asDegrees());
849  */
850  return GeomPoint(imgcrd[0], imgcrd[1]);
851 }
852 
853 /*
854  * Worker routine for pixelToSky
855  */
856 void
857 Wcs::pixelToSkyImpl(double pixel1, double pixel2, afwGeom::Angle skyTmp[2]) const
858 {
859  assert(_wcsInfo);
860 
861  // wcslib assumes 1-indexed coordinates
862  double pixTmp[2] = { pixel1 - lsst::afw::image::PixelZeroPos + lsstToFitsPixels,
863  pixel2 - lsst::afw::image::PixelZeroPos + lsstToFitsPixels};
864  double imgcrd[2];
865  double phi, theta;
866 
867  double sky[2];
868  int status = 0;
869  status = wcsp2s(_wcsInfo, 1, 2, pixTmp, imgcrd, &phi, &theta, sky, &status);
870  if (status > 0) {
871  throw LSST_EXCEPT(except::RuntimeError,
872  (boost::format("Error: wcslib returned a status code of %d at pixel %s, %s: %s") %
873  status % pixel1 % pixel2 % wcs_errmsg[status]).str());
874  }
875  // FIXME -- _wcsInfo.lat, _wcsInfo.lng ?
876  skyTmp[0] = sky[0] * afwGeom::degrees;
877  skyTmp[1] = sky[1] * afwGeom::degrees;
878 }
879 
886  return pixelToSky(pixel.getX(), pixel.getY());
887 }
888 
894 CoordPtr Wcs::pixelToSky(double pixel1, double pixel2) const {
895  assert(_wcsInfo);
896 
897  afwGeom::Angle skyTmp[2];
898  pixelToSkyImpl(pixel1, pixel2, skyTmp);
899  return makeCorrectCoord(skyTmp[0], skyTmp[1]);
900 }
901 
909 void Wcs::pixelToSky(double pixel1, double pixel2, afwGeom::Angle& sky1, afwGeom::Angle& sky2) const {
910  afwGeom::Angle skyTmp[2];
911  // HACK -- we shouldn't need to initialize these -- pixelToSkyImpl() sets them unless an
912  // exception is thrown -- but be safe.
913  skyTmp[0] = 0. * afwGeom::radians;
914  skyTmp[1] = 0. * afwGeom::radians;
915  pixelToSkyImpl(pixel1, pixel2, skyTmp);
916  sky1 = skyTmp[0];
917  sky2 = skyTmp[1];
918 }
919 
923 
924  //Construct a coord object of the correct type
925  int const ncompare = 4; // we only care about type's first 4 chars
926  char *type = _wcsInfo->ctype[0];
927  char *radesys = _wcsInfo->radesys;
928  double equinox = _wcsInfo->equinox;
929 
930  if (strncmp(type, "RA--", ncompare) == 0) { // Our default. If it's often something else, consider
931  ; // using an tr1::unordered_map
932  if(strcmp(radesys, "ICRS") == 0) {
933  return afwCoord::makeCoord(afwCoord::ICRS, sky0, sky1);
934  }
935  if(strcmp(radesys, "FK5") == 0) {
936  return afwCoord::makeCoord(afwCoord::FK5, sky0, sky1, equinox);
937  } else {
938  throw LSST_EXCEPT(except::RuntimeError,
939  (boost::format("Can't create Coord object: Unrecognised radesys %s") %
940  radesys).str());
941  }
942 
943  } else if (strncmp(type, "GLON", ncompare) == 0) {
944  return afwCoord::makeCoord(afwCoord::GALACTIC, sky0, sky1);
945  } else if (strncmp(type, "ELON", ncompare) == 0) {
946  return afwCoord::makeCoord(afwCoord::ECLIPTIC, sky0, sky1, equinox);
947  } else if (strncmp(type, "DEC-", ncompare) == 0) {
948  //check for the case where the ctypes are swapped. Note how sky0 and sky1 are swapped as well
949 
950  //Our default
951  if(strcmp(radesys, "ICRS") == 0) {
952  return afwCoord::makeCoord(afwCoord::ICRS, sky1, sky0);
953  }
954  if(strcmp(radesys, "FK5") == 0) {
955  return afwCoord::makeCoord(afwCoord::FK5, sky1, sky0, equinox);
956  } else {
957  throw LSST_EXCEPT(except::RuntimeError,
958  (boost::format("Can't create Coord object: Unrecognised radesys %s") %
959  radesys).str());
960  }
961  } else if (strncmp(type, "GLAT", ncompare) == 0) {
962  return afwCoord::makeCoord(afwCoord::GALACTIC, sky1, sky0);
963  } else if (strncmp(type, "ELAT", ncompare) == 0) {
964  return afwCoord::makeCoord(afwCoord::ECLIPTIC, sky1, sky0, equinox);
965  } else {
966  //Give up in disgust
967  throw LSST_EXCEPT(except::RuntimeError,
968  (boost::format("Can't create Coord object: Unrecognised sys %s") %
969  type).str());
970  }
971 
972  //Can't get here
973  assert(0);
974 }
975 
976 
978  lsst::afw::coord::Coord const & coord,
980 ) const {
981  return linearizePixelToSkyInternal(skyToPixel(coord), coord, skyUnit);
982 }
984  GeomPoint const & pix,
986 ) const {
987  return linearizePixelToSkyInternal(pix, *pixelToSky(pix), skyUnit);
988 }
989 
990 /*
991  * Implementation for the overloaded public linearizePixelToSky methods, requiring both a pixel coordinate
992  * and the corresponding sky coordinate.
993  */
995  GeomPoint const & pix00,
996  lsst::afw::coord::Coord const & coord,
998 ) const {
999  //
1000  // Figure out the (0, 0), (0, 1), and (1, 0) ra/dec coordinates of the corners of a square drawn in pixel
1001  // It'd be better to centre the square at sky00, but that would involve another conversion between sky and
1002  // pixel coordinates so I didn't bother
1003  //
1004  const double side = 10; // length of the square's sides in pixels
1005  GeomPoint const sky00 = coord.getPosition(skyUnit);
1006  typedef std::pair<lsst::afw::geom::Angle, lsst::afw::geom::Angle> AngleAngle;
1007  AngleAngle const dsky10 = coord.getTangentPlaneOffset(*pixelToSky(pix00 + afwGeom::Extent2D(side, 0)));
1008  AngleAngle const dsky01 = coord.getTangentPlaneOffset(*pixelToSky(pix00 + afwGeom::Extent2D(0, side)));
1009 
1010  Eigen::Matrix2d m;
1011  m(0, 0) = dsky10.first.asAngularUnits(skyUnit)/side;
1012  m(0, 1) = dsky01.first.asAngularUnits(skyUnit)/side;
1013  m(1, 0) = dsky10.second.asAngularUnits(skyUnit)/side;
1014  m(1, 1) = dsky01.second.asAngularUnits(skyUnit)/side;
1015 
1016  Eigen::Vector2d sky00v;
1017  sky00v << sky00.getX(), sky00.getY();
1018  Eigen::Vector2d pix00v;
1019  pix00v << pix00.getX(), pix00.getY();
1020  //return lsst::afw::geom::AffineTransform(m, lsst::afw::geom::Extent2D(sky00v - m * pix00v));
1021  return lsst::afw::geom::AffineTransform(m, (sky00v - m * pix00v));
1022 }
1023 
1025  lsst::afw::coord::Coord const & coord,
1027 ) const {
1028  return linearizeSkyToPixelInternal(skyToPixel(coord), coord, skyUnit);
1029 }
1030 
1032  GeomPoint const & pix,
1034 ) const {
1035  return linearizeSkyToPixelInternal(pix, *pixelToSky(pix), skyUnit);
1036 }
1037 
1043  GeomPoint const & pix00,
1044  lsst::afw::coord::Coord const & coord,
1046 ) const {
1047  lsst::afw::geom::AffineTransform inverse = linearizePixelToSkyInternal(pix00, coord, skyUnit);
1048  return inverse.invert();
1049 }
1050 
1051 
1052 
1059 {
1061 }
1062 
1063 // -------- table-based persistence -------------------------------------------------------------------------
1064 
1065 namespace lsst { namespace afw { namespace image {
1066 
1068 public:
1069 
1070  explicit WcsFactory(std::string const & name) : table::io::PersistableFactory(name) {}
1071 
1072  virtual PTR(table::io::Persistable) read(
1073  InputArchive const & archive,
1074  CatalogVector const & catalogs
1075  ) const;
1076 
1077 };
1078 
1079 namespace {
1080 
1081 // Read-only singleton struct containing the schema and keys that a simple Wcs is mapped
1082 // to in record persistence.
1083 struct WcsPersistenceHelper : private boost::noncopyable {
1094 
1095  static WcsPersistenceHelper const & get() {
1096  static WcsPersistenceHelper instance;
1097  return instance;
1098  };
1099 
1100 private:
1101  WcsPersistenceHelper() :
1102  schema(),
1103  crval(schema.addField< table::Point<double> >("crval", "celestial reference point")),
1104  crpix(schema.addField< table::Point<double> >("crpix", "pixel reference point")),
1105  cd(schema.addField< table::Array<double> >(
1106  "cd", "linear transform matrix, ordered (1_1, 2_1, 1_2, 2_2)", 4)),
1107  ctype1(schema.addField< std::string >("ctype1", "coordinate type", 72)),
1108  ctype2(schema.addField< std::string >("ctype2", "coordinate type", 72)),
1109  equinox(schema.addField< double >("equinox", "equinox of coordinates")),
1110  radesys(schema.addField< std::string >("radesys", "coordinate system for equinox", 72)),
1111  cunit1(schema.addField< std::string >("cunit1", "coordinate units", 72)),
1112  cunit2(schema.addField< std::string >("cunit2", "coordinate units", 72))
1113  {
1114  schema.getCitizen().markPersistent();
1115  }
1116 };
1117 
1118 std::string getWcsPersistenceName() { return "Wcs"; }
1119 
1120 WcsFactory registration(getWcsPersistenceName());
1121 
1122 } // anonymous
1123 
1124 std::string Wcs::getPersistenceName() const { return getWcsPersistenceName(); }
1125 
1126 std::string Wcs::getPythonModule() const { return "lsst.afw.image"; }
1127 
1128 void Wcs::write(OutputArchiveHandle & handle) const {
1129  WcsPersistenceHelper const & keys = WcsPersistenceHelper::get();
1130  afw::table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
1131  PTR(afw::table::BaseRecord) record = catalog.addNew();
1132  record->set(keys.crval, getSkyOrigin()->getPosition(afw::geom::degrees));
1133  record->set(keys.crpix, getPixelOrigin());
1134  Eigen::Matrix2d cdIn = getCDMatrix();
1135  Eigen::Map<Eigen::Matrix2d> cdOut((*record)[keys.cd].getData());
1136  cdOut = cdIn;
1137  record->set(keys.ctype1, std::string(_wcsInfo[0].ctype[0]));
1138  record->set(keys.ctype2, std::string(_wcsInfo[0].ctype[1]));
1139  record->set(keys.equinox, _wcsInfo[0].equinox);
1140  record->set(keys.radesys, std::string(_wcsInfo[0].radesys));
1141  record->set(keys.cunit1, std::string(_wcsInfo[0].cunit[0]));
1142  record->set(keys.cunit2, std::string(_wcsInfo[0].cunit[1]));
1143  handle.saveCatalog(catalog);
1144 }
1145 
1146 bool Wcs::isPersistable() const {
1147  if (_wcsInfo[0].naxis != 2) return false;
1148  if (std::strcmp(_wcsInfo[0].cunit[0], "deg") != 0) return false;
1149  if (std::strcmp(_wcsInfo[0].cunit[1], "deg") != 0) return false;
1150  return true;
1151 }
1152 
1154  daf::base::Citizen(typeid(this)),
1155  _wcsInfo(NULL),
1156  _nWcsInfo(0),
1157  _relax(0),
1158  _wcsfixCtrl(0),
1159  _wcshdrCtrl(0),
1160  _nReject(0),
1161  _coordSystem(static_cast<afw::coord::CoordSystem>(-1))
1162 {
1163  WcsPersistenceHelper const & keys = WcsPersistenceHelper::get();
1164  if (!record.getSchema().contains(keys.schema)) {
1165  throw LSST_EXCEPT(
1166  afw::table::io::MalformedArchiveError,
1167  "Incorrect schema for Wcs persistence"
1168  );
1169  }
1170  _setWcslibParams();
1171  Eigen::Matrix2d cd = Eigen::Map<Eigen::Matrix2d const>(record[keys.cd].getData());
1172  initWcsLib(
1173  record.get(keys.crval), record.get(keys.crpix), cd,
1174  record.get(keys.ctype1), record.get(keys.ctype2),
1175  record.get(keys.equinox), record.get(keys.radesys),
1176  record.get(keys.cunit1), record.get(keys.cunit2)
1177  );
1178  _initWcs();
1179 }
1180 
1182 WcsFactory::read(InputArchive const & inputs, CatalogVector const & catalogs) const {
1183  WcsPersistenceHelper const & keys = WcsPersistenceHelper::get();
1184  LSST_ARCHIVE_ASSERT(catalogs.size() >= 1u);
1185  LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
1186  LSST_ARCHIVE_ASSERT(catalogs.front().getSchema() == keys.schema);
1187  PTR(Wcs) result(new Wcs(catalogs.front().front()));
1188  return result;
1189 }
1190 
1191 }}} // namespace lsst::afw::image
1192 
1193 // ----------------------------------------------------------------------------------------------------------
1194 
1195 //
1196 //Mutators
1197 //
1198 
1199 
1205 void Wcs::shiftReferencePixel(double dx, double dy) {
1206  assert(_wcsInfo);
1207  _wcsInfo->crpix[0] += dx;
1208  _wcsInfo->crpix[1] += dy;
1209 
1210  // tells libwcs to invalidate cached data, since transformation has been modified
1211  _wcsInfo->flag = 0;
1212 }
1213 
1214 /************************************************************************************************************/
1215 /*
1216  * Now WCSA, pixel coordinates, but allowing for X0 and Y0
1217  */
1218 namespace lsst {
1219 namespace afw {
1220 namespace image {
1221 
1222 namespace detail {
1223 
1228 createTrivialWcsAsPropertySet(std::string const& wcsName,
1229  int const x0,
1230  int const y0
1231  ) {
1233 
1234  wcsMetaData->set("CRVAL1" + wcsName, x0, "Column pixel of Reference Pixel");
1235  wcsMetaData->set("CRVAL2" + wcsName, y0, "Row pixel of Reference Pixel");
1236  wcsMetaData->set("CRPIX1" + wcsName, 1, "Column Pixel Coordinate of Reference");
1237  wcsMetaData->set("CRPIX2" + wcsName, 1, "Row Pixel Coordinate of Reference");
1238  wcsMetaData->set("CTYPE1" + wcsName, "LINEAR", "Type of projection");
1239  wcsMetaData->set("CTYPE2" + wcsName, "LINEAR", "Type of projection");
1240  wcsMetaData->set("CUNIT1" + wcsName, "PIXEL", "Column unit");
1241  wcsMetaData->set("CUNIT2" + wcsName, "PIXEL", "Row unit");
1242 
1243  return wcsMetaData;
1244 }
1251 afwGeom::Point2I getImageXY0FromMetadata(std::string const& wcsName,
1252  lsst::daf::base::PropertySet *metadata
1253  ) {
1254 
1255  int x0 = 0; // Our value of X0
1256  int y0 = 0; // Our value of Y0
1257 
1258  try {
1259  //
1260  // Only use WCS if CRPIX[12] == 1 and CRVAL[12] is present
1261  //
1262  if (metadata->getAsDouble("CRPIX1" + wcsName) == 1 &&
1263  metadata->getAsDouble("CRPIX2" + wcsName) == 1) {
1264 
1265  x0 = metadata->getAsInt("CRVAL1" + wcsName);
1266  y0 = metadata->getAsInt("CRVAL2" + wcsName);
1267  //
1268  // OK, we've got it. Remove it from the header
1269  //
1270  metadata->remove("CRVAL1" + wcsName);
1271  metadata->remove("CRVAL2" + wcsName);
1272  metadata->remove("CRPIX1" + wcsName);
1273  metadata->remove("CRPIX2" + wcsName);
1274  metadata->remove("CTYPE1" + wcsName);
1275  metadata->remove("CTYPE1" + wcsName);
1276  metadata->remove("CUNIT1" + wcsName);
1277  metadata->remove("CUNIT2" + wcsName);
1278  }
1279  } catch(lsst::pex::exceptions::NotFoundError &) {
1280  ; // OK, not present
1281  }
1282 
1283  return afwGeom::Point2I(x0, y0);
1284 }
1285 
1295  CONST_PTR(Wcs) const& wcs
1296  )
1297 {
1298  PTR(lsst::daf::base::PropertySet) wcsMetadata = wcs->getFitsMetadata();
1299  std::vector<std::string> paramNames = wcsMetadata->paramNames();
1300  paramNames.push_back("CDELT1");
1301  paramNames.push_back("CDELT2");
1302  paramNames.push_back("LTV1");
1303  paramNames.push_back("LTV2");
1304  paramNames.push_back("PC001001");
1305  paramNames.push_back("PC001002");
1306  paramNames.push_back("PC002001");
1307  paramNames.push_back("PC002002");
1308  for (std::vector<std::string>::const_iterator ptr = paramNames.begin(); ptr != paramNames.end(); ++ptr) {
1309  metadata->remove(*ptr);
1310  }
1311 
1312  return 0; // would be ncard if remove returned a status
1313 }
1314 
1315 
1316 }}}}
1317 
1318 
1319 
1320 // -------------------------------------------------------------------------------------------------
1321 //
1322 // XYTransformFromWcsPair
1323 
1324 
1326  : XYTransform(), _dst(dst), _src(src)
1327 { }
1328 
1329 
1331 {
1332  return boost::make_shared<XYTransformFromWcsPair>(_dst->clone(), _src->clone());
1333 }
1334 
1335 
1337 {
1338  //
1339  // TODO there is an alternate version of pixelToSky() which is designated for the
1340  // "knowledgeable user in need of performance". This is probably better, but first I need
1341  // to understand exactly which checks are needed (e.g. I think we need to check by hand
1342  // that both Wcs's use the same celestial coordinate system)
1343  //
1344  PTR(afw::coord::Coord) x = _src->pixelToSky(pixel);
1345  return _dst->skyToPixel(*x);
1346 }
1347 
1349 {
1350  PTR(afw::coord::Coord) x = _dst->pixelToSky(pixel);
1351  return _src->skyToPixel(*x);
1352 }
1353 
1355 {
1356  // just swap src, dst
1357  return boost::make_shared<XYTransformFromWcsPair> (_src, _dst);
1358 }
int status
Defines the fields and offsets for a table.
Definition: Schema.h:46
geom::Point2I getImageXY0FromMetadata(std::string const &wcsName, lsst::daf::base::PropertySet *metadata)
Definition: Wcs.cc:1251
const int lsstToFitsPixels
Definition: Wcs.cc:78
void _setWcslibParams()
Definition: Wcs.cc:68
Interface for WcsFormatter class.
void shiftReferencePixel(geom::Extent2D const &d)
Definition: Wcs.h:283
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
Formatting utilities.
#define PTR(...)
Definition: base.h:41
double dx
Definition: ImageUtils.cc:90
geom::AffineTransform linearizePixelToSky(coord::Coord const &coord, geom::AngleUnit skyUnit=geom::degrees) const
Return the local linear approximation to Wcs::pixelToSky at a point given in sky coordinates.
Definition: Wcs.cc:977
WcsFactory(std::string const &name)
Definition: Wcs.cc:1070
table::Key< table::Array< double > > cd
Definition: Wcs.cc:1087
An object passed to Persistable::write to allow it to persist itself.
virtual void remove(std::string const &name)
Definition: PropertySet.cc:754
boost::shared_ptr< Coord > Ptr
Definition: Coord.h:73
virtual void pixelToSkyImpl(double pixel1, double pixel2, geom::Angle skyTmp[2]) const
Definition: Wcs.cc:857
A custom container class for records, based on std::vector.
Definition: Catalog.h:94
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
boost::shared_ptr< Wcs const > _src
Definition: Wcs.h:409
afw::table::Key< afw::table::Point< int > > dimensions
boost::shared_ptr< Wcs const > _dst
Definition: Wcs.h:408
A base class for factory classes used to reconstruct objects from records.
Definition: Persistable.h:231
geom::Point2D skyToPixel(geom::Angle sky1, geom::Angle sky2) const
Convert from sky coordinates (e.g ra/dec) to pixel positions.
Definition: Wcs.cc:808
AffineTransform const invert() const
afw::coord::Coord::Ptr convertCoordToSky(coord::Coord const &coord) const
Definition: Wcs.cc:798
virtual bool _isSubset(Wcs const &other) const
Definition: Wcs.cc:530
static lsst::daf::base::PropertyList::Ptr generatePropertySet(lsst::afw::image::Wcs const &wcs)
XYTransformFromWcsPair: Represents an XYTransform obtained by putting two Wcs&#39;s &quot;back to back&quot;...
Definition: Wcs.h:394
lsst::afw::geom::Point2D GeomPoint
Definition: Wcs.cc:60
afw::coord::Coord::Ptr makeCorrectCoord(geom::Angle sky0, geom::Angle sky1) const
Given a sky position, use the values stored in ctype and radesys to return the correct sub-class of C...
Definition: Wcs.cc:922
Image utility functions.
void initWcsLibFromFits(boost::shared_ptr< lsst::daf::base::PropertySet const > const &fitsMetadata)
Parse a fits header, extract the relevant metadata and create a Wcs object.
Definition: Wcs.cc:172
Point< double, 2 > Point2D
Definition: Point.h:277
void initWcsLib(geom::Point2D const &crval, geom::Point2D const &crpix, Eigen::Matrix2d const &CD, std::string const &ctype1, std::string const &ctype2, double equinox, std::string const &raDecSys, std::string const &cunits1, std::string const &cunits2)
Manually initialise a wcs struct using values passed by the constructor.
Definition: Wcs.cc:371
virtual geom::AffineTransform linearizeSkyToPixelInternal(geom::Point2D const &pix, coord::Coord const &coord, geom::AngleUnit skyUnit) const
Definition: Wcs.cc:1042
virtual void write(OutputArchiveHandle &handle) const
Write the object to one or more catalogs.
Definition: Wcs.cc:1128
table::Key< std::string > radesys
Definition: Wcs.cc:1091
geom::Point2D skyToIntermediateWorldCoord(coord::Coord const &coord) const
Convert from sky coordinates (e.g ra/dec) to intermediate world coordinates.
Definition: Wcs.cc:812
virtual ~Wcs()
Definition: Wcs.cc:555
virtual Ptr clone(void) const
Definition: Wcs.cc:562
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
Point< int, 2 > Point2I
Definition: Point.h:274
double dy
Definition: ImageUtils.cc:90
boost::shared_ptr< coord::Coord > pixelToSky(double pix1, double pix2) const
Convert from celestial coordinates to pixel coordinates.
Definition: Wcs.cc:894
lsst::daf::base::PropertyList PropertyList
Definition: Wcs.cc:58
table::Key< std::string > ctype2
Definition: Wcs.cc:1089
double asDegrees() const
Definition: Angle.h:124
A class used to convert scalar POD types such as double to Angle.
Definition: Angle.h:71
virtual geom::Point2D skyToPixelImpl(geom::Angle sky1, geom::Angle sky2) const
Definition: Wcs.cc:752
table::Key< table::Point< double > > crval
Definition: Wcs.cc:1085
table::Key< table::Point< int > > pixel
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
int d
Definition: KDTree.cc:89
A base class for objects that can be persisted via afw::table::io Archive classes.
Definition: Persistable.h:74
A 2D linear coordinate transformation.
lsst::daf::base::PropertySet PropertySet
Definition: Wcs.cc:57
virtual std::string getPythonModule() const
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
Definition: Wcs.cc:1126
Coord::Ptr convert(CoordSystem system) const
Convert to a specified Coord type.
Definition: Coord.cc:636
virtual boost::shared_ptr< lsst::daf::base::PropertyList > getFitsMetadata() const
Return a PropertyList containing FITS header keywords that can be used to save the Wcs...
Definition: Wcs.cc:678
virtual Point2D reverseTransform(Point2D const &pixel) const
Definition: Wcs.cc:1348
int getAsInt(std::string const &name) const
Definition: PropertySet.cc:333
Include files required for standard LSST Exception handling.
double const PI
The ratio of a circle&#39;s circumference to diameter.
Definition: Angle.h:18
tbl::Schema schema
Definition: CoaddPsf.cc:324
boost::shared_ptr< PropertyList > Ptr
Definition: PropertyList.h:84
void shift(Extent< T, N > const &offset)
Shift the point by the given offset.
Definition: Point.h:110
int x
An affine coordinate transformation consisting of a linear transformation and an offset.
virtual void rotateImageBy90(int nQuarter, lsst::afw::geom::Extent2I dimensions) const
Definition: Wcs.cc:627
table::Key< double > equinox
Definition: Wcs.cc:1090
std::pair< lsst::afw::geom::Angle, lsst::afw::geom::Angle > getTangentPlaneOffset(Coord const &c) const
Get the offset on the tangent plane.
Definition: Coord.cc:747
AngleUnit const radians
constant with units of radians
Definition: Angle.h:91
bool operator==(Wcs const &other) const
Definition: Wcs.cc:496
#define CHECK_NULLS(a, b)
Definition: Wcs.cc:519
Eigen::Matrix2d getCDMatrix() const
Returns CD matrix. You would never have guessed that from the name.
Definition: Wcs.cc:587
geom::Angle pixelScale() const
Returns the pixel scale [Angle/pixel].
Definition: Wcs.cc:745
table::Key< table::Point< double > > crpix
Definition: Wcs.cc:1086
const int STRLEN
Definition: Wcs.cc:65
Wcs()
Construct an invalid Wcs given no arguments.
Definition: Wcs.cc:87
lsst::afw::geom::Point2D getPixelOrigin() const
Returns CRPIX (corrected to LSST convention).
Definition: Wcs.cc:577
const int fitsToLsstPixels
Definition: Wcs.cc:79
Coord::Ptr makeCoord(CoordSystem const system, lsst::afw::geom::Angle const ra, lsst::afw::geom::Angle const dec, double const epoch)
Factory function to create a Coord of arbitrary type with decimal RA,Dec.
Definition: Coord.cc:1210
virtual void flipImage(int flipLR, int flipTB, lsst::afw::geom::Extent2I dimensions) const
Flip CD matrix around the y-axis.
Definition: Wcs.cc:605
boost::shared_ptr< lsst::daf::base::PropertyList > createTrivialWcsAsPropertySet(std::string const &wcsName, int const x0=0, int const y0=0)
Definition: Wcs.cc:1228
lsst::afw::coord::Coord::Ptr getSkyOrigin() const
Returns CRVAL.
Definition: Wcs.cc:571
virtual geom::AffineTransform linearizePixelToSkyInternal(geom::Point2D const &pix, coord::Coord const &coord, geom::AngleUnit skyUnit) const
Definition: Wcs.cc:994
#define CONST_PTR(...)
Definition: base.h:47
boost::shared_ptr< Wcs > Ptr
Definition: Wcs.h:113
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
lsst::afw::image::Wcs Wcs
Definition: Wcs.cc:59
virtual std::string getPersistenceName() const
Return the unique name used to persist this object and look up its factory.
Definition: Wcs.cc:1124
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
A vector of catalogs used by Persistable.
Definition: CatalogVector.h:26
bool isFlipped() const
Definition: Wcs.cc:696
Base class for all records.
Definition: BaseRecord.h:27
int contains(Schema const &other, int flags=EQUAL_KEYS) const
Test whether the given schema is a subset of this.
int _wcshdrCtrl
Controls messages to stderr from wcshdr (0 for none); see wcshdr.h for details.
Definition: Wcs.h:357
geom::LinearTransform getLinearTransform() const
Definition: Wcs.cc:1058
A class used as a handle to a particular field in a table.
Definition: fwd.h:44
int _relax
Degree of permissiveness for wcspih (0 for strict); see wcshdr.h for details.
Definition: Wcs.h:355
double getAsDouble(std::string const &name) const
Definition: PropertySet.cc:406
Class for storing generic metadata.
Definition: PropertySet.h:82
Virtual base class for 2D transforms.
Definition: XYTransform.h:48
int _wcsfixCtrl
Do potentially unsafe translations of non-standard unit strings? 0/1 = no/yes.
Definition: Wcs.h:356
boost::shared_ptr< RecordT > addNew()
Create a new record, add it to the end of the catalog, and return a pointer to it.
Definition: Catalog.h:469
XYTransformFromWcsPair(boost::shared_ptr< Wcs const > dst, boost::shared_ptr< Wcs const > src)
Definition: Wcs.cc:1325
virtual bool isPersistable() const
Whether the Wcs is persistable using afw::table::io archives.
Definition: Wcs.cc:1146
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:47
double pixArea(lsst::afw::geom::Point2D pix00) const
Sky area covered by a pixel at position pix00 in units of square degrees.
Definition: Wcs.cc:712
geom::AffineTransform linearizeSkyToPixel(coord::Coord const &coord, geom::AngleUnit skyUnit=geom::degrees) const
Return the local linear approximation to Wcs::skyToPixel at a point given in sky coordinates.
Definition: Wcs.cc:1024
afw::table::Key< double > b
Field< T >::Value get(Key< T > const &key) const
Return the value of a field for the given key.
Definition: BaseRecord.h:123
lsst::afw::image::XYTransformFromWcsPair XYTransformFromWcsPair
Definition: Wcs.cc:62
table::Key< std::string > cunit1
Definition: Wcs.cc:1092
CoordSystem makeCoordEnum(std::string const system)
A utility function to get the enum value of a coordinate system from a string name.
Definition: Coord.cc:116
AngleUnit const degrees
Definition: Angle.h:92
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
std::string formatFitsProperties(boost::shared_ptr< lsst::daf::base::PropertySet const > const &prop)
A multi-catalog archive object used to load table::io::Persistable objects.
Definition: InputArchive.h:28
int stripWcsKeywords(boost::shared_ptr< lsst::daf::base::PropertySet > const &metadata, boost::shared_ptr< Wcs const > const &wcs)
Definition: Wcs.cc:1294
const double PixelZeroPos
FITS uses 1.0, SDSS uses 0.5, LSST uses 0.0 (http://dev.lsstcorp.org/trac/wiki/BottomLeftPixelProposa...
Definition: ImageUtils.h:42
lsst::afw::coord::Coord::Ptr CoordPtr
Definition: Wcs.cc:61
ImageT val
Definition: CR.cc:154
virtual Point2D forwardTransform(Point2D const &pixel) const
virtuals for forward and reverse transforms
Definition: Wcs.cc:1336
dictionary Point
Definition: __init__.py:38
int countFitsHeaderCards(boost::shared_ptr< lsst::daf::base::PropertySet const > const &prop)
table::Key< std::string > ctype1
Definition: Wcs.cc:1088
struct wcsprm * _wcsInfo
Definition: Wcs.h:353
coord::CoordSystem _coordSystem
Definition: Wcs.h:359
Schema getSchema() const
Return the Schema that holds this record&#39;s fields and keys.
Definition: BaseRecord.h:48
afw::table::SourceRecord * record
table::Key< std::string > cunit2
Definition: Wcs.cc:1093
Functions to handle coordinates.