LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
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 #include <limits>
30 
31 #include "boost/format.hpp"
32 
33 #include "wcslib/wcs.h"
34 #include "wcslib/wcsfix.h"
35 #include "wcslib/wcshdr.h"
36 
37 #include "lsst/daf/base.h"
38 #include "lsst/daf/base/Citizen.h"
41 #include "lsst/pex/exceptions.h"
43 #include "lsst/afw/image/Wcs.h"
44 #include "lsst/afw/coord/Coord.h"
45 #include "lsst/afw/geom/Angle.h"
50 
51 namespace except = lsst::pex::exceptions;
52 namespace afwImg = lsst::afw::image;
53 namespace afwCoord = lsst::afw::coord;
54 namespace afwGeom = lsst::afw::geom;
55 
56 
57 using namespace std;
58 
65 
66 //The amount of space allocated to strings in wcslib
67 const int STRLEN = 72;
68 
69 //Set internal params for wcslib
70 void lsst::afw::image::Wcs::_setWcslibParams()
71 {
72  _wcsfixCtrl = // ctrl for wcsfix
73  2; // Translate "H" to "h"
74  _wcshdrCtrl = // ctrl for wcspih
75  2; // Report each rejected keyrecord and the reason why it was rejected
76  _relax = // relax parameter for wcspih;
77  WCSHDR_all; // Accept all extensions recognized by the parser
78 }
79 
80 const int lsstToFitsPixels = +1;
81 const int fitsToLsstPixels = -1;
82 
83 //
84 // Constructors
85 //
86 
87 
90  daf::base::Citizen(typeid(this)),
91  _wcsInfo(NULL), _nWcsInfo(0), _relax(0), _wcsfixCtrl(0), _wcshdrCtrl(0), _nReject(0),
92  _coordSystem(afwCoord::UNKNOWN) // set by _initWcs
93 {
95  _initWcs();
96 }
97 
98 
102  daf::base::Citizen(typeid(this)),
103  _wcsInfo(NULL),
104  _nWcsInfo(0),
105  _relax(0),
106  _wcsfixCtrl(0),
107  _wcshdrCtrl(0),
108  _nReject(0),
109  _coordSystem(afwCoord::UNKNOWN) // set by _initWcs
110 {
112 
113  initWcsLibFromFits(fitsMetadata);
114  _initWcs();
115 }
116 
117 /*
118  * Set some internal variables that we need to refer to
119  */
121 {
122  // first four characters of CTYPE1 (name of first axis)
123  std::string ctype1 = std::string(_wcsInfo->ctype[0]).substr(0, 4);
124 
125  if (_wcsInfo) {
126  if (ctype1[0] == 'G') {
128  _skyAxesSwapped = (ctype1[2] == 'A'); // GLAT instead of GLON
129  } else if (ctype1[0] == 'E') {
131  _skyAxesSwapped = (ctype1[2] == 'A'); // ELAT instead of ELON
132  } else {
134  _skyAxesSwapped = (ctype1[0] == 'D'); // DEC instead of RA
135  }
136 
137  // tell WCSlib that values have been updated
138  _wcsInfo->flag = 0;
139  // and then tell it to do its internal magic.
140  int status = wcsset(_wcsInfo);
141  if (status != 0) {
142  throw LSST_EXCEPT(except::RuntimeError,
143  (boost::format("Failed to setup wcs structure with wcsset. Status %d: %s") %
144  status % wcs_errmsg[status] ).str());
145  }
146  }
147 }
148 
163 Wcs::Wcs(GeomPoint const & crval, GeomPoint const & crpix, Eigen::Matrix2d const & CD,
164  std::string const & ctype1, std::string const & ctype2,
165  double equinox, std::string const & raDecSys,
166  std::string const & cunits1, std::string const & cunits2
167 ):
168  daf::base::Citizen(typeid(this)),
169  _wcsInfo(NULL),
170  _nWcsInfo(0),
171  _relax(0),
172  _wcsfixCtrl(0),
173  _wcshdrCtrl(0),
174  _nReject(0),
175  _coordSystem(afwCoord::UNKNOWN) // set by _initWcs
176 {
178  initWcsLib(crval, crpix, CD,
179  ctype1, ctype2,
180  equinox, raDecSys,
181  cunits1, cunits2);
182  _initWcs();
183 }
184 
185 
192  class HeaderAccess {
193  public:
195  CONST_PTR(lsst::daf::base::PropertySet) const& toRead() { return _constHeader; }
197  PTR(lsst::daf::base::PropertySet) const& toWrite() {
198  if (!_hackHeader) {
199  _hackHeader = _constHeader->deepCopy();
200  _constHeader = _hackHeader;
201  }
202  return _hackHeader;
203  }
204 
206  HeaderAccess(CONST_PTR(lsst::daf::base::PropertySet) const& header) :
207  _constHeader(header), _hackHeader() {}
208 
209  private:
211  PTR(lsst::daf::base::PropertySet) _hackHeader;
212  };
213 
214  HeaderAccess access(header);
215 
216  // Some headers (e.g. SDSS ones from FNAL) have EQUINOX as a string. Fix this,
217  // as wcslib 4.4.4 refuses to handle it. Furthermore, some headers (e.g. Skymapper)
218  // use a string but prepend 'J': "J2000.0" -- if we don't handle this, we deduce
219  // an equinox of 0.0
220  {
221  std::string const& key = "EQUINOX";
222  if (access.toRead()->exists(key) && access.toRead()->typeOf(key) == typeid(std::string)) {
223  auto equinoxStr = access.toRead()->getAsString(key).c_str();
224  double equinox = ::atof(equinoxStr[0] == 'J' ? equinoxStr + 1 : equinoxStr);
225  access.toWrite()->set(key, equinox);
226  }
227  }
228 
229  //Check header isn't empty
230  int nCards = lsst::afw::formatters::countFitsHeaderCards(access.toRead());
231  if (nCards <= 0) {
232  string msg = "Could not parse FITS WCS: no header cards found";
233  throw LSST_EXCEPT(except::InvalidParameterError, msg);
234  }
235 
236  //While the standard does not insist on CRVAL and CRPIX being present, it
237  //is almost certain their absence indicates a problem.
238  //Check for CRPIX
239  if( !access.toRead()->exists("CRPIX1") && !access.toRead()->exists("CRPIX1a")) {
240  string msg = "Neither CRPIX1 not CRPIX1a found";
241  throw LSST_EXCEPT(except::InvalidParameterError, msg);
242  }
243 
244  if( !access.toRead()->exists("CRPIX2") && !access.toRead()->exists("CRPIX2a")) {
245  string msg = "Neither CRPIX2 not CRPIX2a found";
246  throw LSST_EXCEPT(except::InvalidParameterError, msg);
247  }
248 
249  //And the same for CRVAL
250  if( !access.toRead()->exists("CRVAL1") && !access.toRead()->exists("CRVAL1a")) {
251  string msg = "Neither CRVAL1 not CRVAL1a found";
252  throw LSST_EXCEPT(except::InvalidParameterError, msg);
253  }
254 
255  if( !access.toRead()->exists("CRVAL2") && !access.toRead()->exists("CRVAL2a")) {
256  string msg = "Neither CRVAL2 not CRVAL2a found";
257  throw LSST_EXCEPT(except::InvalidParameterError, msg);
258  }
259  /*
260  * According to Greisen and Calabretta (A&A 395, 1061–1075 (2002)) it's illegal to mix PCi_j and CDi_j
261  * headers; unfortunately Subaru puts both in its headers. It actually uses PC001002 instead of PC1_2
262  * (dating to a proposed FITS standard from 1996) and at least sometimes fails to include CDELT[12],
263  * so the CD and PC matrices are inconsistent
264  *
265  * If we detect any part of a CD matrix, delete all PC matrices
266  */
267  if(access.toRead()->exists("CD1_1") || access.toRead()->exists("CD1_2") ||
268  access.toRead()->exists("CD2_1") || access.toRead()->exists("CD2_2")) {
269  for (int i = 1; i <= 2; ++i) {
270  for (int j = 1; j <= 2; ++j) {
271  std::string key = (boost::format("PC%i_%i") % j % i).str();
272  if (access.toRead()->exists(key)) {
273  double const val = access.toRead()->getAsDouble(key);
274  access.toWrite()->remove(key);
275  access.toWrite()->add("X_" + key, val);
276  }
277 
278  key = (boost::format("PC%03d%03d") % j % i).str();
279  if (access.toRead()->exists(key)) {
280  double const val = access.toRead()->getAsDouble(key);
281  access.toWrite()->remove(key);
282  access.toWrite()->add("X_" + key, val);
283  }
284  }
285  }
286  }
287 
288  //Pass the header into wcslib's formatter to extract & setup the Wcs. First need
289  //to convert to a C style string, so the compile doesn't complain about constness
290  std::string metadataStr = lsst::afw::formatters::formatFitsProperties(access.toRead());
291  // We own the data, and wcslib is slack about constness, so no qualms with casting away const
292  char *hdrString = const_cast<char*>(metadataStr.c_str());
293  //printf("wcspih string:\n%s\n", hdrString);
294 
295  nCards = lsst::afw::formatters::countFitsHeaderCards(access.toRead()); // we may have dropped some
296  int pihStatus = wcspih(hdrString, nCards, _relax, _wcshdrCtrl, &_nReject, &_nWcsInfo, &_wcsInfo);
297 
298  if (pihStatus != 0) {
299  throw LSST_EXCEPT(except::RuntimeError,
300  (boost::format("Could not parse FITS WCS: wcspih status = %d (%s)") %
301  pihStatus % wcs_errmsg[pihStatus]).str());
302  }
303 
304  //Run wcsfix on _wcsInfo to try and fix any problems it knows about.
305  const int *naxes = NULL; // should be {NAXIS1, NAXIS2, ...} to check cylindrical projections
306  int stats[NWCSFIX]; // status returns from wcsfix
307  int fixStatus = wcsfix(_wcsfixCtrl, naxes, _wcsInfo, stats);
308  if (fixStatus != 0) {
309  std::stringstream errStream;
310  errStream << "Could not parse FITS WCS: wcsfix failed " << std::endl;
311  for (int ii = 0; ii < NWCSFIX; ++ii) {
312  if (stats[ii] >= 0) {
313  errStream << "\t" << ii << ": " << stats[ii] << " " << wcsfix_errmsg[stats[ii]] << std::endl;
314  } else {
315  errStream << "\t" << ii << ": " << stats[ii] << std::endl;
316  }
317  }
318  }
319 
320  //The Wcs standard requires a default value for RADESYS if the keyword
321  //doesn't exist in header, but wcslib doesn't set it. So we do so here. This code
322  //conforms to Calabretta & Greisen 2002 \S 3.1
323  if (!(access.toRead()->exists("RADESYS") || access.toRead()->exists("RADESYSa"))) {
324  // If RADECSYS exists, use that (counter to Calabretta & Greisen 2002 \S 3.1, but commonly used).
325  // If equinox exist and < 1984, use FK4. If >= 1984, use FK5
326  if (access.toRead()->exists("RADECSYS")) {
327  strncpy(_wcsInfo->radesys, access.toRead()->getAsString("RADECSYS").c_str(), STRLEN);
328  } else if (access.toRead()->exists("EQUINOX") || access.toRead()->exists("EQUINOXa")) {
329  std::string const EQUINOX = access.toRead()->exists("EQUINOX") ? "EQUINOX" : "EQUINOXa";
330  double const equinox = access.toRead()->getAsDouble(EQUINOX);
331  if(equinox < 1984) {
332  strncpy(_wcsInfo->radesys, "FK4", STRLEN);
333  } else {
334  strncpy(_wcsInfo->radesys, "FK5", STRLEN);
335  }
336  } else {
337  //If Equinox doesn't exist, default to ICRS
338  strncpy(_wcsInfo->radesys, "ICRS", STRLEN);
339  }
340  }
341  // strip trailing whitespace
342  {
343  for(int i = strlen(_wcsInfo->radesys) - 1; i >= 0; i--) {
344  if (isspace(_wcsInfo->radesys[i])) {
345  _wcsInfo->radesys[i] = '\0';
346  }
347  }
348  }
349  //
350  // If there are no CDi_j cards in the header, set CDi_j from PCi_j
351  // CDi_j == CDELTi*PCi_j
352  //
353  if ((_wcsInfo->altlin & 2) == 0) { // no CDi_j cards were found in the header
354  double const *cdelt = _wcsInfo->cdelt;
355  double const *pc = _wcsInfo->pc;
356  double *cd = _wcsInfo->cd;
357 
358  cd[0] = cdelt[0]*pc[0]; // 1_1
359  cd[1] = cdelt[0]*pc[1]; // 1_2
360  cd[2] = cdelt[1]*pc[2]; // 2_1
361  cd[3] = cdelt[1]*pc[3]; // 2_2
362  }
363 }
364 
375 void Wcs::initWcsLib(GeomPoint const & crval, GeomPoint const & crpix, Eigen::Matrix2d const & CD,
376  std::string const & ctype1, std::string const & ctype2,
377  double equinox, std::string const & raDecSys,
378  std::string const & cunits1, std::string const & cunits2) {
379 
380  //Check CD is a valid size
381  if( (CD.rows() != 2) || (CD.cols() != 2) ) {
382  string msg = "CD is not a 2x2 matrix";
383  throw LSST_EXCEPT(except::InvalidParameterError, msg);
384  }
385 
386  //Check that cunits are legitimate values
387  bool isValid = (cunits1 == "deg");
388  isValid |= (cunits1 == "arcmin");
389  isValid |= (cunits1 == "arcsec");
390  isValid |= (cunits1 == "mas");
391 
392  if (!isValid) {
393  string msg = "CUNITS1 must be one of {deg|arcmin|arcsec|mas}";
394  throw LSST_EXCEPT(except::InvalidParameterError, msg);
395  }
396 
397  isValid = (cunits2 == "deg");
398  isValid |= (cunits2 == "arcmin");
399  isValid |= (cunits2 == "arcsec");
400  isValid |= (cunits2 == "mas");
401 
402  if (!isValid) {
403  string msg = "CUNITS2 must be one of {deg|arcmin|arcsec|mas}";
404  throw LSST_EXCEPT(except::InvalidParameterError, msg);
405  }
406 
407  //Initialise the wcs struct
408  _wcsInfo = static_cast<struct wcsprm *>(malloc(sizeof(struct wcsprm)));
409  if (_wcsInfo == NULL) {
410  throw LSST_EXCEPT(except::MemoryError, "Cannot allocate WCS info");
411  }
412 
413  _wcsInfo->flag = -1;
414  int status = wcsini(true, 2, _wcsInfo); //2 indicates a naxis==2, a two dimensional image
415  if(status != 0) {
416  throw LSST_EXCEPT(except::MemoryError,
417  (boost::format("Failed to allocate memory with wcsini. Status %d: %s") %
418  status % wcs_errmsg[status] ).str());
419  }
420 
421  //Set crval, crpix and CD. Internally to the class, we use fits units for consistency with
422  //wcslib.
423  _wcsInfo->crval[0] = crval.getX();
424  _wcsInfo->crval[1] = crval.getY();
425  _wcsInfo->crpix[0] = crpix.getX() + lsstToFitsPixels;
426  _wcsInfo->crpix[1] = crpix.getY() + lsstToFitsPixels;
427 
428  //Set the CD matrix
429  for (int i=0; i<2; ++i) {
430  for (int j=0; j<2; ++j) {
431  _wcsInfo->cd[(2*i) + j] = CD(i,j);
432  }
433  }
434 
435  //Specify that we have a CD matrix, but no PC or CROTA
436  _wcsInfo->altlin = 2;
437  _wcsInfo->flag = 0; //values have been updated
438 
439  //This is a work around for what I think is a bug in wcslib. ->types is neither
440  //initialised or set to NULL by default, so if I try to delete a Wcs object,
441  //wcslib then attempts to free non-existent space, and the code can crash.
442  _wcsInfo->types = NULL;
443 
444  //Set the coordinate system
445  strncpy(_wcsInfo->ctype[0], ctype1.c_str(), STRLEN);
446  strncpy(_wcsInfo->ctype[1], ctype2.c_str(), STRLEN);
447  strncpy(_wcsInfo->radesys, raDecSys.c_str(), STRLEN);
448  _wcsInfo->equinox = equinox;
449 
450  //Set the units
451  strncpy(_wcsInfo->cunit[0], cunits1.c_str(), STRLEN);
452  strncpy(_wcsInfo->cunit[1], cunits2.c_str(), STRLEN);
453 
454  _nWcsInfo = 1; //Specify that we have only one coordinate representation
455 
456  //Tell wcslib that we are need to set up internal values
457  status=wcsset(_wcsInfo);
458  if(status != 0) {
459  throw LSST_EXCEPT(except::RuntimeError,
460  (boost::format("Failed to setup wcs structure with wcsset. Status %d: %s") %
461  status % wcs_errmsg[status] ).str());
462 
463  }
464 }
465 
466 
468 Wcs::Wcs(afwImg::Wcs const & rhs) :
469  daf::base::Citizen(typeid(this)),
470  _wcsInfo(NULL),
471  _nWcsInfo(rhs._nWcsInfo),
472  _relax(rhs._relax),
473  _wcsfixCtrl(rhs._wcsfixCtrl),
474  _wcshdrCtrl(rhs._wcshdrCtrl),
475  _nReject(rhs._nReject),
476  _coordSystem(afwCoord::UNKNOWN) // set by _initWcs
477 {
478 
479  if (rhs._nWcsInfo > 0) {
480  _wcsInfo = static_cast<struct wcsprm *>(calloc(rhs._nWcsInfo, sizeof(struct wcsprm)));
481  if (_wcsInfo == NULL) {
482  throw LSST_EXCEPT(lsst::pex::exceptions::MemoryError, "Cannot allocate WCS info");
483  }
484 
485  int alloc = 1; //Unconditionally allocate memory when calling
486  for (int i = 0; i != rhs._nWcsInfo; ++i) {
487  _wcsInfo[i].flag = -1;
488  int status = wcscopy(alloc, &rhs._wcsInfo[i], &_wcsInfo[i]);
489  if (status != 0) {
490  wcsvfree(&i, &_wcsInfo);
491  throw LSST_EXCEPT(lsst::pex::exceptions::MemoryError,
492  (boost::format("Could not copy WCS: wcscopy status = %d : %s") %
493  status % wcs_errmsg[status]).str());
494  }
495  }
496  }
497  _initWcs();
498 }
499 
500 bool Wcs::operator==(Wcs const & other) const {
501  if (&other == this) return true;
502  // We do a bidirectional test with a virtual member function in case one of us is a derived
503  // class with members we don't know about here.
504  // This is not the most efficient possible implementation, but I think it's the easiest one
505  // with which to ensure correctness, and I think that's more important in this case.
506  return this->_isSubset(other) && other._isSubset(*this);
507 }
508 
509 // convenience functions and a macro for implementing isSubset
510 namespace {
511 
512 inline bool compareArrays(double const * a, double const * b, int n) {
513  for (int i = 0; i < n; ++i) if (a[i] != b[i]) return false;
514  return true;
515 }
516 
517 template <typename T>
518 inline bool compareStringArrays(T a, T b, int n) {
519  for (int i = 0; i < n; ++i) if (strcmp(a[i], b[i]) != 0) return false;
520  return true;
521 }
522 
523 #define CHECK_NULLS(a, b) \
524  do { \
525  if ((a) == NULL) { \
526  if ((b) == NULL) return true; \
527  return false; \
528  } \
529  if ((b) == NULL) return false; \
530  } while (false)
531 
532 } // anonymous
533 
534 bool Wcs::_isSubset(Wcs const & rhs) const {
536  CHECK_NULLS(_wcsInfo->crval, rhs._wcsInfo->crval);
537  CHECK_NULLS(_wcsInfo->cd, rhs._wcsInfo->cd);
538  CHECK_NULLS(_wcsInfo->crpix, rhs._wcsInfo->crpix);
539  CHECK_NULLS(_wcsInfo->cunit, rhs._wcsInfo->cunit);
540  CHECK_NULLS(_wcsInfo->ctype, rhs._wcsInfo->ctype);
541  return _nWcsInfo == rhs._nWcsInfo &&
542  _coordSystem == rhs._coordSystem &&
543  _wcsInfo->naxis == rhs._wcsInfo->naxis &&
544  _wcsInfo->equinox == rhs._wcsInfo->equinox &&
545  _wcsInfo->altlin == rhs._wcsInfo->altlin &&
546  compareArrays(_wcsInfo->crval, rhs._wcsInfo->crval, 2) &&
547  compareArrays(_wcsInfo->crpix, rhs._wcsInfo->crpix, 2) &&
548  compareArrays(_wcsInfo->cd, rhs._wcsInfo->cd, 4) &&
549  compareStringArrays(_wcsInfo->cunit, rhs._wcsInfo->cunit, 2) &&
550  compareStringArrays(_wcsInfo->ctype, rhs._wcsInfo->ctype, 2) &&
552  _wcsInfo->crval[1] * afwGeom::degrees) ==
553  rhs.skyToPixel(_wcsInfo->crval[0] * afwGeom::degrees,
554  _wcsInfo->crval[1] * afwGeom::degrees) &&
555  *pixelToSky(_wcsInfo->crpix[0], _wcsInfo->crpix[1]) ==
556  *rhs.pixelToSky(_wcsInfo->crpix[0], _wcsInfo->crpix[1]);
557 }
558 
560  if (_wcsInfo != NULL) {
561  wcsvfree(&_nWcsInfo, &_wcsInfo);
562  }
563 }
564 
565 
566 PTR(Wcs) Wcs::clone(void) const {
567  return PTR(Wcs)(new Wcs(*this));
568 }
569 
570 //
571 // Accessors
572 //
573 
575  assert(_wcsInfo);
576  return makeCorrectCoord(_wcsInfo->crval[0] * afwGeom::degrees, _wcsInfo->crval[1] * afwGeom::degrees);
577 }
578 
580  assert(_wcsInfo);
581  //Convert from fits units back to lsst units
582  double p1 = _wcsInfo->crpix[0] + fitsToLsstPixels;
583  double p2 = _wcsInfo->crpix[1] + fitsToLsstPixels;
584  return afwGeom::Point2D(p1, p2);
585 }
586 
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 }
604 
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  //Origin is at (1,1). Adjust to avoid off by one.
613  if (flipLR) {
614  _wcsInfo->cd[0] = -_wcsInfo->cd[0];
615  _wcsInfo->cd[2] = -_wcsInfo->cd[2];
616  _wcsInfo->crpix[0] = -_wcsInfo->crpix[0] + dimensions.getX() + 1;
617  }
618  if (flipTB) {
619  _wcsInfo->cd[1] = -_wcsInfo->cd[1];
620  _wcsInfo->cd[3] = -_wcsInfo->cd[3];
621  _wcsInfo->crpix[1] = -_wcsInfo->crpix[1]+dimensions.getY() + 1;
622  }
623 
624  // tells libwcs to invalidate cached data, since transformation has been modified
625  _wcsInfo->flag = 0;
626 }
627 
629  assert(_wcsInfo);
630 
631  while (nQuarter < 0 ) {
632  nQuarter += 4;
633  }
634 
635 
636  int const naxis = _wcsInfo->naxis;
637 
638  //If naxis != 2, I'm not sure if any of what follows is correct
639  assert(naxis == 2);
640  double a = _wcsInfo->cd[0];
641  double b = _wcsInfo->cd[1];
642  double c = _wcsInfo->cd[2];
643  double d = _wcsInfo->cd[3];
644  double crpx = _wcsInfo->crpix[0];
645  double crpy = _wcsInfo->crpix[1];
646  //Origin is at (1,1). Adjust to avoid off by one.
647  //E.g. CRPIX one pixel off the UR of the grid should go to
648  //(0,0) for nQuarter=2
649  switch (nQuarter%4) {
650  case 0:
651  break;
652  case 1:
653  _wcsInfo->cd[0] = -b;
654  _wcsInfo->cd[1] = a;
655  _wcsInfo->cd[2] = -d;
656  _wcsInfo->cd[3] = c;
657  _wcsInfo->crpix[0] = -crpy + dimensions.getY() + 1;
658  _wcsInfo->crpix[1] = crpx;
659  break;
660  case 2:
661  _wcsInfo->cd[0] = -a;
662  _wcsInfo->cd[1] = -b;
663  _wcsInfo->cd[2] = -c;
664  _wcsInfo->cd[3] = -d;
665  _wcsInfo->crpix[0] = -crpx + dimensions.getX() + 1;
666  _wcsInfo->crpix[1] = -crpy + dimensions.getY() + 1;
667  break;
668  case 3:
669  _wcsInfo->cd[0] = b;
670  _wcsInfo->cd[1] = -a;
671  _wcsInfo->cd[2] = d;
672  _wcsInfo->cd[3] = -c;
673  _wcsInfo->crpix[0] = crpy;
674  _wcsInfo->crpix[1] = -crpx + dimensions.getX() + 1;
675  break;
676  }
677 
678  // tells libwcs to invalidate cached data, since transformation has been modified
679  _wcsInfo->flag = 0;
680 }
681 
682 PTR(PropertyList) Wcs::getFitsMetadata() const {
684 }
685 
686 bool Wcs::isFlipped() const {
687  // We calculate the determinant of the CD (i.e the rotation and scaling)
688  // matrix. If this determinant is positive, then the image can be rotated
689  // to a position where increasing the right ascension and declination
690  // increases the horizontal and vertical pixel position. In this case the
691  // image is flipped.
692  assert(_wcsInfo);
693  double det = (_wcsInfo->cd[0] * _wcsInfo->cd[3]) - (_wcsInfo->cd[1] * _wcsInfo->cd[2]);
694 
695  if (det == 0) {
696  throw(LSST_EXCEPT(except::RuntimeError, "Wcs CD matrix is singular"));
697  }
698 
699  return (det > 0);
700 }
701 
702 static double square(double x) {
703  return x*x;
704 }
705 
706 double Wcs::pixArea(GeomPoint pix00
707  ) const {
708  //
709  // Figure out the (0, 0), (0, 1), and (1, 0) ra/dec coordinates of the corners of a square drawn in pixel
710  // It'd be better to centre the square at sky00, but that would involve another conversion between sky and
711  // pixel coordinates so I didn't bother
712  //
713  const double side = 1; // length of the square's sides in pixels
714 
715  // Work in 3-space to avoid RA wrapping and pole issues.
716  afwGeom::Point3D v0 = pixelToSky(pix00)->getVector();
717 
718  // Step by "side" in x and y pixel directions...
719  GeomPoint px(pix00);
720  GeomPoint py(pix00);
721  px.shift(afwGeom::Extent2D(side, 0));
722  py.shift(afwGeom::Extent2D(0, side));
723  // Push the points through the WCS, and find difference in 3-space.
724  afwGeom::Extent3D dx = pixelToSky(px)->getVector() - v0;
725  afwGeom::Extent3D dy = pixelToSky(py)->getVector() - v0;
726 
727  // Compute |cross product| = area of parallelogram with sides dx,dy
728  // FIXME -- this is slightly incorrect -- it's making the small-angle
729  // approximation, taking the distance *through* the unit sphere
730  // rather than over its surface.
731  // This is in units of ~radians^2
732  double area = sqrt(square(dx[1]*dy[2] - dx[2]*dy[1]) +
733  square(dx[2]*dy[0] - dx[0]*dy[2]) +
734  square(dx[0]*dy[1] - dx[1]*dy[0]));
735 
736  return area / square(side) * square(180. / afwGeom::PI);
737 }
738 
740  return sqrt(pixArea(getPixelOrigin())) * afwGeom::degrees;
741 }
742 
743 /*
744  * Worker routine for skyToPixel
745  */
746 GeomPoint Wcs::skyToPixelImpl(afwGeom::Angle sky1, // RA (or, more generally, longitude)
747  afwGeom::Angle sky2 // Dec (or latitude)
748  ) const {
749  assert(_wcsInfo);
750 
751  double skyTmp[2];
752  double imgcrd[2];
753  double phi, theta;
754  double pixTmp[2];
755  /*
756  printf("_skyCoordsReversed: %c\n", (_skyCoordsReversed ? 'T' : 'F'));
757  printf("wcsinfo.lat: %i, lng: %i\n", _wcsInfo->lat, _wcsInfo->lng);
758  */
759  // WCSLib is smart enough to notice and handle crazy SDSS CTYPE1 = DEC--TAN,
760  // by recording the indices of the long and lat coordinates.
761  skyTmp[_wcsInfo->lng] = sky1.asDegrees();
762  skyTmp[_wcsInfo->lat] = sky2.asDegrees();
763 
764  int stat[1];
765  int status = 0;
766  status = wcss2p(_wcsInfo, 1, 2, skyTmp, &phi, &theta, imgcrd, pixTmp, stat);
767  if (status == 9) {
768  throw LSST_EXCEPT(except::DomainError,
769  (boost::format("sky coordinates %s, %s degrees is not valid for this WCS")
770  % sky1.asDegrees() % sky2.asDegrees()
771  ).str()
772  );
773  }
774  if (status > 0) {
775  throw LSST_EXCEPT(except::RuntimeError,
776  (boost::format("Error: wcslib returned a status code of %d at sky %s, %s deg: %s") %
777  status % sky1.asDegrees() % sky2.asDegrees() % wcs_errmsg[status]).str());
778  }
779 
780  // wcslib assumes 1-indexed coords
783 }
784 
786  PTR(afwCoord::Coord) sky = convertCoordToSky(coord);
787  return skyToPixelImpl(sky->getLongitude(), sky->getLatitude());
788 }
789 
790 
792 Wcs::convertCoordToSky(afwCoord::Coord const & coord) const {
793  return coord.convert(_coordSystem, _wcsInfo->equinox);
794 }
795 
797  return skyToPixelImpl(sky1, sky2);
798 }
799 
801  assert(_wcsInfo);
802 
803  PTR(afwCoord::Coord) sky = convertCoordToSky(coord);
804  double skyTmp[2];
805  double imgcrd[2];
806  double phi, theta;
807  double pixTmp[2];
808 
809  /*
810  printf("skyToIWC: _coordSystem = %i\n", (int)_coordSystem);
811  printf("coord (%.3f, %.3f)\n", coord->getLongitude().asDegrees(), coord->getLatitude().asDegrees());
812  printf("->sky (%.3f, %.3f)\n", sky->getLongitude().asDegrees(), sky->getLatitude().asDegrees());
813  */
814 
815  skyTmp[_wcsInfo->lng] = sky->getLongitude().asDegrees();
816  skyTmp[_wcsInfo->lat] = sky->getLatitude() .asDegrees();
817 
818  //Estimate pixel coordinates
819  int stat[1];
820  int status = 0;
821  imgcrd[0] = imgcrd[1] = -1e6;
822  /*
823  printf(" skyTmp[] = (%.3f, %.3f)\n", skyTmp[0], skyTmp[1]);
824  printf(" _wcsInfo->lng,lat = %i, %i\n", _wcsInfo->lng, _wcsInfo->lat);
825  */
826  status = wcss2p(_wcsInfo, 1, 2, skyTmp, &phi, &theta, imgcrd, pixTmp, stat);
827  if (status > 0) {
828  throw LSST_EXCEPT(except::RuntimeError,
829  (boost::format("Error: wcslib returned a status code of %d at sky %s, %s deg: %s") %
830  status % skyTmp[0] % skyTmp[1] % wcs_errmsg[status]).str());
831  }
832  /*
833  printf("->iwc (%.3f, %.3f)\n", imgcrd[0], imgcrd[1]);
834  printf("-> pix (%.2f, %.2f)\n", pixTmp[0], pixTmp[1]);
835  PTR(afwCoord::Coord) crval = getSkyOrigin();
836  printf("(crval is (%.3f, %.3f))\n", crval->getLongitude().asDegrees(), crval->getLatitude().asDegrees());
837  */
838  return GeomPoint(imgcrd[0], imgcrd[1]);
839 }
840 
841 double Wcs::getEquinox() const {
842  if (_wcsInfo == nullptr) {
843  return std::numeric_limits<double>::quiet_NaN();
844  }
845  return _wcsInfo->equinox;
846 }
847 
848 bool Wcs::isSameSkySystem(Wcs const &wcs) const {
849  if (_isIcrs() && wcs._isIcrs()) {
850  return true;
851  }
852  return (getCoordSystem() == wcs.getCoordSystem()) && (getEquinox() == wcs.getEquinox());
853 }
854 
855 /*
856  * Worker routine for pixelToSky
857  */
858 void
859 Wcs::pixelToSkyImpl(double pixel1, double pixel2, afwGeom::Angle skyTmp[2]) const
860 {
861  assert(_wcsInfo);
862 
863  // wcslib assumes 1-indexed coordinates
864  double pixTmp[2] = { pixel1 - lsst::afw::image::PixelZeroPos + lsstToFitsPixels,
865  pixel2 - lsst::afw::image::PixelZeroPos + lsstToFitsPixels};
866  double imgcrd[2];
867  double phi, theta;
868 
869  double sky[2];
870  int status = 0;
871  status = wcsp2s(_wcsInfo, 1, 2, pixTmp, imgcrd, &phi, &theta, sky, &status);
872  if (status > 0) {
873  throw LSST_EXCEPT(except::RuntimeError,
874  (boost::format("Error: wcslib returned a status code of %d at pixel %s, %s: %s") %
875  status % pixel1 % pixel2 % wcs_errmsg[status]).str());
876  }
877  // FIXME -- _wcsInfo.lat, _wcsInfo.lng ?
878  skyTmp[0] = sky[0] * afwGeom::degrees;
879  skyTmp[1] = sky[1] * afwGeom::degrees;
880 }
881 
883  return pixelToSky(pixel.getX(), pixel.getY());
884 }
885 
886 CoordPtr Wcs::pixelToSky(double pixel1, double pixel2) const {
887  assert(_wcsInfo);
888 
889  afwGeom::Angle skyTmp[2];
890  pixelToSkyImpl(pixel1, pixel2, skyTmp);
891  return makeCorrectCoord(skyTmp[0], skyTmp[1]);
892 }
893 
894 void Wcs::pixelToSky(double pixel1, double pixel2, afwGeom::Angle& sky1, afwGeom::Angle& sky2) const {
895  afwGeom::Angle skyTmp[2];
896  // HACK -- we shouldn't need to initialize these -- pixelToSkyImpl() sets them unless an
897  // exception is thrown -- but be safe.
898  skyTmp[0] = 0. * afwGeom::radians;
899  skyTmp[1] = 0. * afwGeom::radians;
900  pixelToSkyImpl(pixel1, pixel2, skyTmp);
901  sky1 = skyTmp[0];
902  sky2 = skyTmp[1];
903 }
904 
908  auto coordSystem = getCoordSystem();
909  if ((coordSystem == afwCoord::ICRS) || (coordSystem == afwCoord::GALACTIC)) {
910  // equinox not relevant
911  if (_skyAxesSwapped) {
912  return afwCoord::makeCoord(coordSystem, sky1, sky0);
913  } else {
914  return afwCoord::makeCoord(coordSystem, sky0, sky1);
915  }
916  } else if ((coordSystem == afwCoord::FK5) || (coordSystem == afwCoord::ECLIPTIC)) {
917  if (_skyAxesSwapped) {
918  return afwCoord::makeCoord(coordSystem, sky1, sky0, _wcsInfo->equinox);
919  } else {
920  return afwCoord::makeCoord(coordSystem, sky0, sky1, _wcsInfo->equinox);
921  }
922  }
923  throw LSST_EXCEPT(except::RuntimeError,
924  (boost::format("Can't create Coord object: Unrecognised coordinate system %s") %
925  coordSystem).str());
926 }
927 
928 
930  lsst::afw::coord::Coord const & coord,
932 ) const {
933  return linearizePixelToSkyInternal(skyToPixel(coord), coord, skyUnit);
934 }
936  GeomPoint const & pix,
938 ) const {
939  return linearizePixelToSkyInternal(pix, *pixelToSky(pix), skyUnit);
940 }
941 
942 /*
943  * Implementation for the overloaded public linearizePixelToSky methods, requiring both a pixel coordinate
944  * and the corresponding sky coordinate.
945  */
947  GeomPoint const & pix00,
948  lsst::afw::coord::Coord const & coord,
950 ) const {
951  //
952  // Figure out the (0, 0), (0, 1), and (1, 0) ra/dec coordinates of the corners of a square drawn in pixel
953  // It'd be better to centre the square at sky00, but that would involve another conversion between sky and
954  // pixel coordinates so I didn't bother
955  //
956  const double side = 10; // length of the square's sides in pixels
957  GeomPoint const sky00 = coord.getPosition(skyUnit);
958  typedef std::pair<lsst::afw::geom::Angle, lsst::afw::geom::Angle> AngleAngle;
959  AngleAngle const dsky10 = coord.getTangentPlaneOffset(*pixelToSky(pix00 + afwGeom::Extent2D(side, 0)));
960  AngleAngle const dsky01 = coord.getTangentPlaneOffset(*pixelToSky(pix00 + afwGeom::Extent2D(0, side)));
961 
962  Eigen::Matrix2d m;
963  m(0, 0) = dsky10.first.asAngularUnits(skyUnit)/side;
964  m(0, 1) = dsky01.first.asAngularUnits(skyUnit)/side;
965  m(1, 0) = dsky10.second.asAngularUnits(skyUnit)/side;
966  m(1, 1) = dsky01.second.asAngularUnits(skyUnit)/side;
967 
968  Eigen::Vector2d sky00v;
969  sky00v << sky00.getX(), sky00.getY();
970  Eigen::Vector2d pix00v;
971  pix00v << pix00.getX(), pix00.getY();
972  //return lsst::afw::geom::AffineTransform(m, lsst::afw::geom::Extent2D(sky00v - m * pix00v));
973  return lsst::afw::geom::AffineTransform(m, (sky00v - m * pix00v));
974 }
975 
977  lsst::afw::coord::Coord const & coord,
979 ) const {
980  return linearizeSkyToPixelInternal(skyToPixel(coord), coord, skyUnit);
981 }
982 
984  GeomPoint const & pix,
986 ) const {
987  return linearizeSkyToPixelInternal(pix, *pixelToSky(pix), skyUnit);
988 }
989 
995  GeomPoint const & pix00,
996  lsst::afw::coord::Coord const & coord,
998 ) const {
999  lsst::afw::geom::AffineTransform inverse = linearizePixelToSkyInternal(pix00, coord, skyUnit);
1000  return inverse.invert();
1001 }
1002 
1004 {
1006 }
1007 
1008 // -------- table-based persistence -------------------------------------------------------------------------
1009 
1010 namespace lsst { namespace afw { namespace image {
1011 
1013 public:
1014 
1015  explicit WcsFactory(std::string const & name) : table::io::PersistableFactory(name) {}
1016 
1017  virtual PTR(table::io::Persistable) read(
1018  InputArchive const & archive,
1019  CatalogVector const & catalogs
1020  ) const;
1021 
1022 };
1023 
1024 namespace {
1025 
1026 // Read-only singleton struct containing the schema and keys that a simple Wcs is mapped
1027 // to in record persistence.
1028 struct WcsPersistenceHelper {
1029  table::Schema schema;
1030  table::PointKey<double> crval;
1031  table::PointKey<double> crpix;
1032  table::Key< table::Array<double> > cd;
1033  table::Key<std::string> ctype1;
1034  table::Key<std::string> ctype2;
1035  table::Key<double> equinox;
1036  table::Key<std::string> radesys;
1037  table::Key<std::string> cunit1;
1038  table::Key<std::string> cunit2;
1039 
1040  static WcsPersistenceHelper const & get() {
1041  static WcsPersistenceHelper instance;
1042  return instance;
1043  };
1044 
1045  // No copying
1046  WcsPersistenceHelper (const WcsPersistenceHelper&) = delete;
1047  WcsPersistenceHelper& operator=(const WcsPersistenceHelper&) = delete;
1048 
1049  // No moving
1050  WcsPersistenceHelper (WcsPersistenceHelper&&) = delete;
1051  WcsPersistenceHelper& operator=(WcsPersistenceHelper&&) = delete;
1052 
1053 private:
1054  WcsPersistenceHelper() :
1055  schema(),
1056  crval(table::PointKey<double>::addFields(schema, "crval", "celestial reference point", "deg")),
1057  crpix(table::PointKey<double>::addFields(schema, "crpix", "pixel reference point", "pixel")),
1058  cd(schema.addField< table::Array<double> >(
1059  "cd", "linear transform matrix, ordered (1_1, 2_1, 1_2, 2_2)", 4)),
1060  ctype1(schema.addField< std::string >("ctype1", "coordinate type", 72)),
1061  ctype2(schema.addField< std::string >("ctype2", "coordinate type", 72)),
1062  equinox(schema.addField< double >("equinox", "equinox of coordinates")),
1063  radesys(schema.addField< std::string >("radesys", "coordinate system for equinox", 72)),
1064  cunit1(schema.addField< std::string >("cunit1", "coordinate units", 72)),
1065  cunit2(schema.addField< std::string >("cunit2", "coordinate units", 72))
1066  {
1067  schema.getCitizen().markPersistent();
1068  }
1069 };
1070 
1071 std::string getWcsPersistenceName() { return "Wcs"; }
1072 
1073 WcsFactory registration(getWcsPersistenceName());
1074 
1075 } // anonymous
1076 
1077 std::string Wcs::getPersistenceName() const { return getWcsPersistenceName(); }
1078 
1079 std::string Wcs::getPythonModule() const { return "lsst.afw.image"; }
1080 
1081 void Wcs::write(OutputArchiveHandle & handle) const {
1082  WcsPersistenceHelper const & keys = WcsPersistenceHelper::get();
1083  afw::table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
1084  PTR(afw::table::BaseRecord) record = catalog.addNew();
1085  record->set(keys.crval, getSkyOrigin()->getPosition(afw::geom::degrees));
1086  record->set(keys.crpix, getPixelOrigin());
1087  Eigen::Matrix2d cdIn = getCDMatrix();
1088  Eigen::Map<Eigen::Matrix2d> cdOut((*record)[keys.cd].getData());
1089  cdOut = cdIn;
1090  record->set(keys.ctype1, std::string(_wcsInfo[0].ctype[0]));
1091  record->set(keys.ctype2, std::string(_wcsInfo[0].ctype[1]));
1092  record->set(keys.equinox, _wcsInfo[0].equinox);
1093  record->set(keys.radesys, std::string(_wcsInfo[0].radesys));
1094  record->set(keys.cunit1, std::string(_wcsInfo[0].cunit[0]));
1095  record->set(keys.cunit2, std::string(_wcsInfo[0].cunit[1]));
1096  handle.saveCatalog(catalog);
1097 }
1098 
1100  if (_wcsInfo[0].naxis != 2) return false;
1101  if (std::strcmp(_wcsInfo[0].cunit[0], "deg") != 0) return false;
1102  if (std::strcmp(_wcsInfo[0].cunit[1], "deg") != 0) return false;
1103 
1104  return true;
1105 }
1106 
1107 bool Wcs::isPersistable() const {
1108  if (!_mayBePersistable()) {
1109  return false;
1110  }
1111  // The current table persistence only works for TAN and TAN-SIP projections
1112  return false;
1113 }
1114 
1116  daf::base::Citizen(typeid(this)),
1117  _wcsInfo(NULL),
1118  _nWcsInfo(0),
1119  _relax(0),
1120  _wcsfixCtrl(0),
1121  _wcshdrCtrl(0),
1122  _nReject(0),
1123  _coordSystem(static_cast<afw::coord::CoordSystem>(-1)) // set by _initWcs
1124 {
1125  WcsPersistenceHelper const & keys = WcsPersistenceHelper::get();
1126  if (!record.getSchema().contains(keys.schema)) {
1127  throw LSST_EXCEPT(
1128  afw::table::io::MalformedArchiveError,
1129  "Incorrect schema for Wcs persistence"
1130  );
1131  }
1132  _setWcslibParams();
1133  Eigen::Matrix2d cd = Eigen::Map<Eigen::Matrix2d const>(record[keys.cd].getData());
1134  initWcsLib(
1135  record.get(keys.crval), record.get(keys.crpix), cd,
1136  record.get(keys.ctype1), record.get(keys.ctype2),
1137  record.get(keys.equinox), record.get(keys.radesys),
1138  record.get(keys.cunit1), record.get(keys.cunit2)
1139  );
1140  _initWcs();
1141 }
1142 
1144 WcsFactory::read(InputArchive const & inputs, CatalogVector const & catalogs) const {
1145  WcsPersistenceHelper const & keys = WcsPersistenceHelper::get();
1146  LSST_ARCHIVE_ASSERT(catalogs.size() >= 1u);
1147  LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
1148  LSST_ARCHIVE_ASSERT(catalogs.front().getSchema() == keys.schema);
1149  PTR(Wcs) result(new Wcs(catalogs.front().front()));
1150  return result;
1151 }
1152 
1153 }}} // namespace lsst::afw::image
1154 
1155 // ----------------------------------------------------------------------------------------------------------
1156 
1157 //
1158 //Mutators
1159 //
1160 
1161 
1167 void Wcs::shiftReferencePixel(double dx, double dy) {
1168  assert(_wcsInfo);
1169  _wcsInfo->crpix[0] += dx;
1170  _wcsInfo->crpix[1] += dy;
1171 
1172  // tells libwcs to invalidate cached data, since transformation has been modified
1173  _wcsInfo->flag = 0;
1174 }
1175 
1176 /************************************************************************************************************/
1177 /*
1178  * Now WCSA, pixel coordinates, but allowing for X0 and Y0
1179  */
1180 namespace lsst {
1181 namespace afw {
1182 namespace image {
1183 
1184 namespace detail {
1185 
1190 createTrivialWcsAsPropertySet(std::string const& wcsName,
1191  int const x0,
1192  int const y0
1193  ) {
1195 
1196  wcsMetaData->set("CRVAL1" + wcsName, x0, "Column pixel of Reference Pixel");
1197  wcsMetaData->set("CRVAL2" + wcsName, y0, "Row pixel of Reference Pixel");
1198  wcsMetaData->set("CRPIX1" + wcsName, 1, "Column Pixel Coordinate of Reference");
1199  wcsMetaData->set("CRPIX2" + wcsName, 1, "Row Pixel Coordinate of Reference");
1200  wcsMetaData->set("CTYPE1" + wcsName, "LINEAR", "Type of projection");
1201  wcsMetaData->set("CTYPE2" + wcsName, "LINEAR", "Type of projection");
1202  wcsMetaData->set("CUNIT1" + wcsName, "PIXEL", "Column unit");
1203  wcsMetaData->set("CUNIT2" + wcsName, "PIXEL", "Row unit");
1204 
1205  return wcsMetaData;
1206 }
1213 afwGeom::Point2I getImageXY0FromMetadata(std::string const& wcsName,
1214  lsst::daf::base::PropertySet *metadata
1215  ) {
1216 
1217  int x0 = 0; // Our value of X0
1218  int y0 = 0; // Our value of Y0
1219 
1220  try {
1221  //
1222  // Only use WCS if CRPIX[12] == 1 and CRVAL[12] is present
1223  //
1224  if (metadata->getAsDouble("CRPIX1" + wcsName) == 1 &&
1225  metadata->getAsDouble("CRPIX2" + wcsName) == 1) {
1226 
1227  x0 = metadata->getAsInt("CRVAL1" + wcsName);
1228  y0 = metadata->getAsInt("CRVAL2" + wcsName);
1229  //
1230  // OK, we've got it. Remove it from the header
1231  //
1232  metadata->remove("CRVAL1" + wcsName);
1233  metadata->remove("CRVAL2" + wcsName);
1234  metadata->remove("CRPIX1" + wcsName);
1235  metadata->remove("CRPIX2" + wcsName);
1236  metadata->remove("CTYPE1" + wcsName);
1237  metadata->remove("CTYPE1" + wcsName);
1238  metadata->remove("CUNIT1" + wcsName);
1239  metadata->remove("CUNIT2" + wcsName);
1240  }
1241  } catch(lsst::pex::exceptions::NotFoundError &) {
1242  ; // OK, not present
1243  }
1244 
1245  return afwGeom::Point2I(x0, y0);
1246 }
1247 
1257  CONST_PTR(Wcs) const& wcs
1258  )
1259 {
1260  PTR(lsst::daf::base::PropertySet) wcsMetadata = wcs->getFitsMetadata();
1261  std::vector<std::string> paramNames = wcsMetadata->paramNames();
1262  paramNames.push_back("CDELT1");
1263  paramNames.push_back("CDELT2");
1264  paramNames.push_back("LTV1");
1265  paramNames.push_back("LTV2");
1266  paramNames.push_back("PC001001");
1267  paramNames.push_back("PC001002");
1268  paramNames.push_back("PC002001");
1269  paramNames.push_back("PC002002");
1270  for (std::vector<std::string>::const_iterator ptr = paramNames.begin(); ptr != paramNames.end(); ++ptr) {
1271  metadata->remove(*ptr);
1272  }
1273 
1274  return 0; // would be ncard if remove returned a status
1275 }
1276 
1277 
1278 }}}}
1279 
1280 
1281 
1282 // -------------------------------------------------------------------------------------------------
1283 //
1284 // XYTransformFromWcsPair
1285 
1286 
1288  : XYTransform(), _dst(dst), _src(src), _isSameSkySystem(dst->isSameSkySystem(*src))
1289 { }
1290 
1291 
1293 {
1294  return std::make_shared<XYTransformFromWcsPair>(_dst->clone(), _src->clone());
1295 }
1296 
1297 
1299 {
1300  if (_isSameSkySystem) {
1301  // high performance branch; no coordinate conversion required
1302  afw::geom::Angle sky0, sky1;
1303  _src->pixelToSky(pixel[0], pixel[1], sky0, sky1);
1304  return _dst->skyToPixel(sky0, sky1);
1305  } else {
1306  PTR(afw::coord::Coord const) coordPtr{_src->pixelToSky(pixel)};
1307  return _dst->skyToPixel(*coordPtr);
1308  }
1309 }
1310 
1312 {
1313  if (_isSameSkySystem) {
1314  // high performance branch; no coordinate conversion required
1315  afw::geom::Angle sky0, sky1;
1316  _dst->pixelToSky(pixel[0], pixel[1], sky0, sky1);
1317  return _src->skyToPixel(sky0, sky1);
1318  } else {
1319  PTR(afw::coord::Coord const) coordPtr{_dst->pixelToSky(pixel)};
1320  return _src->skyToPixel(*coordPtr);
1321  }
1322 }
1323 
1325 {
1326  // just swap src, dst
1327  return std::make_shared<XYTransformFromWcsPair> (_src, _dst);
1328 }
int contains(Schema const &other, int flags=EQUAL_KEYS) const
Test whether the given schema is a subset of this.
const int lsstToFitsPixels
Definition: Wcs.cc:80
void _setWcslibParams()
Definition: Wcs.cc:70
Interface for WcsFormatter class.
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
double getEquinox() const
Definition: Wcs.cc:841
AffineTransform const invert() const
lsst::afw::coord::Coord Coord
Definition: misc.h:35
table::Key< std::string > name
Definition: ApCorrMap.cc:71
bool _isIcrs() const
Definition: Wcs.h:364
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:929
WcsFactory(std::string const &name)
Definition: Wcs.cc:1015
table::Key< table::Array< double > > cd
Definition: Wcs.cc:1032
An object passed to Persistable::write to allow it to persist itself.
AngleUnit const radians
constant with units of radians
Definition: Angle.h:90
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:45
virtual void pixelToSkyImpl(double pixel1, double pixel2, geom::Angle skyTmp[2]) const
Definition: Wcs.cc:859
A custom container class for records, based on std::vector.
Definition: Catalog.h:95
Class for storing ordered metadata with comments.
Definition: PropertyList.h:82
boost::shared_ptr< Wcs const > _src
Definition: Wcs.h:464
afw::table::Schema schema
Definition: GaussianPsf.cc:41
boost::shared_ptr< Wcs const > _dst
Definition: Wcs.h:463
Include files required for standard LSST Exception handling.
A base class for factory classes used to reconstruct objects from records.
Definition: Persistable.h:229
geom::Point2D skyToPixel(geom::Angle sky1, geom::Angle sky2) const
Convert from sky coordinates (e.g.
Definition: Wcs.cc:796
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
virtual bool _isSubset(Wcs const &other) const
Definition: Wcs.cc:534
static lsst::daf::base::PropertyList::Ptr generatePropertySet(lsst::afw::image::Wcs const &wcs)
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
XYTransformFromWcsPair: An XYTransform obtained by putting two Wcs objects &quot;back to back&quot;...
Definition: Wcs.h:449
lsst::afw::geom::Point2D GeomPoint
Definition: Wcs.cc:62
boost::shared_ptr< afw::coord::Coord > 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:907
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:187
boost::shared_ptr< lsst::daf::base::PropertyList > createTrivialWcsAsPropertySet(std::string const &wcsName, int const x0, int const y0)
Define a trivial WCS that maps the lower left corner (LLC) pixel of an image to a given value...
Definition: Wcs.cc:1190
table::PointKey< double > crval
Definition: Wcs.cc:1030
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:375
tbl::Key< int > wcs
virtual geom::AffineTransform linearizeSkyToPixelInternal(geom::Point2D const &pix, coord::Coord const &coord, geom::AngleUnit skyUnit) const
Implementation for the overloaded public linearizeSkyToPixel methods, requiring both a pixel coordina...
Definition: Wcs.cc:994
virtual void write(OutputArchiveHandle &handle) const
Write the object to one or more catalogs.
Definition: Wcs.cc:1081
table::Key< std::string > radesys
Definition: Wcs.cc:1036
geom::Point2D skyToIntermediateWorldCoord(coord::Coord const &coord) const
Convert from sky coordinates (e.g.
Definition: Wcs.cc:800
virtual ~Wcs()
Definition: Wcs.cc:559
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
afw::coord::CoordSystem getCoordSystem() const
Definition: Wcs.h:222
Point< int, 2 > Point2I
Definition: Point.h:285
boost::shared_ptr< coord::Coord > pixelToSky(double pix1, double pix2) const
Convert from pixel position to sky coordinates (e.g.
Definition: Wcs.cc:886
lsst::daf::base::PropertyList PropertyList
Definition: Wcs.cc:60
table::Key< std::string > ctype2
Definition: Wcs.cc:1034
double getAsDouble(std::string const &name) const
Get the last value for any arithmetic property name (possibly hierarchical).
Definition: PropertySet.cc:406
bool _mayBePersistable() const
Perform basic checks on whether *this might be persistable.
Definition: Wcs.cc:1099
boost::shared_ptr< afw::coord::Coord > convertCoordToSky(coord::Coord const &coord) const
Given a Coord (as a shared pointer), return the sky position in the correct coordinate system for thi...
Definition: Wcs.cc:792
A class used to convert scalar POD types such as double to Angle.
Definition: Angle.h:70
int const x0
Definition: saturated.cc:45
Image utility functions.
virtual void shiftReferencePixel(double dx, double dy)
Move the pixel reference position by (dx, dy)
Definition: Wcs.cc:1167
virtual geom::Point2D skyToPixelImpl(geom::Angle sky1, geom::Angle sky2) const
Definition: Wcs.cc:746
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
A base class for objects that can be persisted via afw::table::io Archive classes.
Definition: Persistable.h:72
A 2D linear coordinate transformation.
lsst::daf::base::PropertySet PropertySet
Definition: Wcs.cc:59
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:1079
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:470
virtual Point2D reverseTransform(Point2D const &pixel) const
Definition: Wcs.cc:1311
afw::table::PointKey< int > dimensions
Definition: GaussianPsf.cc:42
metadata import lsst afw display as afwDisplay n
AngleUnit const degrees
Definition: Angle.h:91
A class representing an Angle.
Definition: Angle.h:103
afwGeom::Point2I getImageXY0FromMetadata(std::string const &wcsName, lsst::daf::base::PropertySet *metadata)
Return a Point2I(x0, y0) given a PropertySet containing a suitable WCS (e.g.
Definition: Wcs.cc:1213
An affine coordinate transformation consisting of a linear transformation and an offset.
virtual void rotateImageBy90(int nQuarter, lsst::afw::geom::Extent2I dimensions) const
Rotate image by nQuarter times 90 degrees.
Definition: Wcs.cc:628
table::Key< double > equinox
Definition: Wcs.cc:1035
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:757
bool isSameSkySystem(Wcs const &wcs) const
Return true if a WCS has the same coordinate system and equinox as this one.
Definition: Wcs.cc:848
bool operator==(Wcs const &other) const
Definition: Wcs.cc:500
#define CHECK_NULLS(a, b)
Definition: Wcs.cc:523
Eigen::Matrix2d getCDMatrix() const
Returns the CD matrix.
Definition: Wcs.cc:587
geom::Angle pixelScale() const
Returns the pixel scale [Angle/pixel].
Definition: Wcs.cc:739
const int STRLEN
Definition: Wcs.cc:67
Wcs()
Construct an invalid Wcs given no arguments.
Definition: Wcs.cc:89
lsst::afw::geom::Point2D getPixelOrigin() const
Returns CRPIX (corrected to LSST convention).
Definition: Wcs.cc:579
const int fitsToLsstPixels
Definition: Wcs.cc:81
Formatting utilities.
virtual void flipImage(int flipLR, int flipTB, lsst::afw::geom::Extent2I dimensions) const
Flip CD matrix around the y-axis.
Definition: Wcs.cc:605
int getAsInt(std::string const &name) const
Get the last value for a bool/char/short/int property name (possibly hierarchical).
Definition: PropertySet.cc:333
boost::shared_ptr< lsst::afw::coord::Coord > getSkyOrigin() const
Returns CRVAL. This need not be the centre of the image.
Definition: Wcs.cc:574
double x
Field< T >::Value get(Key< T > const &key) const
Return the value of a field for the given key.
Definition: BaseRecord.h:132
virtual geom::AffineTransform linearizePixelToSkyInternal(geom::Point2D const &pix, coord::Coord const &coord, geom::AngleUnit skyUnit) const
Definition: Wcs.cc:946
void shift(Extent< T, N > const &offset)
Shift the point by the given offset.
Definition: Point.h:115
lsst::afw::image::Wcs Wcs
Definition: Wcs.cc:61
virtual std::string getPersistenceName() const
Return the unique name used to persist this object and look up its factory.
Definition: Wcs.cc:1077
bool _skyAxesSwapped
if true then the sky axes are swapped
Definition: Wcs.h:417
#define LSST_EXCEPT(type,...)
Create an exception with a given type and message and optionally other arguments (dependent on the ty...
Definition: Exception.h:46
A vector of catalogs used by Persistable.
Definition: CatalogVector.h:26
bool isFlipped() const
Does the Wcs follow the convention of North=Up, East=Left?
Definition: Wcs.cc:686
Base class for all records.
Definition: BaseRecord.h:27
tuple m
Definition: lsstimport.py:48
int _wcshdrCtrl
Controls messages to stderr from wcshdr (0 for none); see wcshdr.h for details.
Definition: Wcs.h:414
geom::LinearTransform getLinearTransform() const
Return the linear part of the Wcs, the CD matrix in FITS-speak, as an AffineTransform.
Definition: Wcs.cc:1003
int _relax
Degree of permissiveness for wcspih (0 for strict); see wcshdr.h for details.
Definition: Wcs.h:412
int status
#define PTR(...)
Definition: base.h:41
Schema getSchema() const
Return the Schema that holds this record&#39;s fields and keys.
Definition: BaseRecord.h:57
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:413
boost::shared_ptr< Coord > 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:1220
XYTransformFromWcsPair(boost::shared_ptr< Wcs const > dst, boost::shared_ptr< Wcs const > src)
Definition: Wcs.cc:1287
virtual bool isPersistable() const
Whether the Wcs is persistable using afw::table::io archives.
Definition: Wcs.cc:1107
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:706
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:976
afw::table::Key< double > b
table::Key< std::string > cunit1
Definition: Wcs.cc:1037
Point< double, 2 > Point2D
Definition: Point.h:288
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:117
table::PointKey< int > pixel
boost::shared_ptr< lsst::afw::coord::Coord > CoordPtr
Definition: Wcs.cc:63
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:682
virtual void remove(std::string const &name)
Removes all values for a property name (possibly hierarchical).
Definition: PropertySet.cc:754
std::string formatFitsProperties(boost::shared_ptr< lsst::daf::base::PropertySet const > const &prop)
Definition: Utils.cc:289
double asDegrees() const
Return an Angle&#39;s value as a double in degrees.
Definition: Angle.h:123
double const PI
The ratio of a circle&#39;s circumference to diameter.
Definition: Angle.h:17
A multi-catalog archive object used to load table::io::Persistable objects.
Definition: InputArchive.h:28
#define CONST_PTR(...)
A shared pointer to a const object.
Definition: base.h:47
int stripWcsKeywords(boost::shared_ptr< lsst::daf::base::PropertySet > const &metadata, boost::shared_ptr< Wcs const > const &wcs)
Strip keywords from the input metadata that are related to the generated Wcs.
Definition: Wcs.cc:1256
int const y0
Definition: saturated.cc:45
This is the base class for spherical coordinates.
Definition: Coord.h:69
const double PixelZeroPos
position of center of pixel 0
Definition: ImageUtils.h:42
ImageT val
Definition: CR.cc:159
virtual Point2D forwardTransform(Point2D const &pixel) const
virtuals for forward and reverse transforms
Definition: Wcs.cc:1298
int countFitsHeaderCards(boost::shared_ptr< lsst::daf::base::PropertySet const > const &prop)
Definition: Utils.cc:329
table::Key< std::string > ctype1
Definition: Wcs.cc:1033
struct wcsprm * _wcsInfo
Definition: Wcs.h:410
coord::CoordSystem _coordSystem
Definition: Wcs.h:416
table::Key< std::string > cunit2
Definition: Wcs.cc:1038
table::PointKey< double > crpix
Definition: Wcs.cc:1031
Functions to handle coordinates.