LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
fits.cc
Go to the documentation of this file.
1 // -*- lsst-c++ -*-
2 
3 #include <cstdint>
4 #include <cstdio>
5 #include <complex>
6 #include <cmath>
7 #include <sstream>
8 #include <unordered_set>
9 #include <unordered_map>
10 
11 #include "fitsio.h"
12 extern "C" {
13 #include "fitsio2.h"
14 }
15 
16 #include "boost/regex.hpp"
17 #include "boost/filesystem.hpp"
18 #include "boost/preprocessor/seq/for_each.hpp"
19 #include "boost/format.hpp"
20 
21 #include "lsst/pex/exceptions.h"
22 #include "lsst/log/Log.h"
23 #include "lsst/afw/fits.h"
24 #include "lsst/geom/Angle.h"
25 #include "lsst/afw/geom/wcsUtils.h"
27 
28 namespace lsst {
29 namespace afw {
30 namespace fits {
31 
32 // ----------------------------------------------------------------------------------------------------------
33 // ---- Miscellaneous utilities -----------------------------------------------------------------------------
34 // ----------------------------------------------------------------------------------------------------------
35 
36 namespace {
37 
38 /*
39  * Format a PropertySet into a FITS header string using simplifying assumptions.
40  *
41  * See @ref makeLimitedFitsHeader for details.
42  *
43  * @param[in] paramNames Names of properties to format
44  * @param[in] metadata Metadata to format
45  * @return a FITS header string (exactly 80 characters per entry, no line terminators)
46  */
47 std::string makeLimitedFitsHeaderImpl(std::vector<std::string> const &paramNames,
48  daf::base::PropertySet const &metadata) {
50  for (auto const &fullName : paramNames) {
51  std::size_t lastPeriod = fullName.rfind(char('.'));
52  auto name = (lastPeriod == std::string::npos) ? fullName : fullName.substr(lastPeriod + 1);
53  std::type_info const &type = metadata.typeOf(name);
54 
55  std::string out = "";
56  out.reserve(80);
57  if (name.size() > 8) {
58  continue; // The name is too long for a FITS keyword; skip this item
59  }
60  out = (boost::format("%-8s= ") % name).str();
61 
62  if (type == typeid(bool)) {
63  out += metadata.get<bool>(name) ? "T" : "F";
64  } else if (type == typeid(std::uint8_t)) {
65  out += (boost::format("%20d") % static_cast<int>(metadata.get<std::uint8_t>(name))).str();
66  } else if (type == typeid(int)) {
67  out += (boost::format("%20d") % metadata.get<int>(name)).str();
68  } else if (type == typeid(double)) {
69  // use G because FITS wants uppercase E for exponents
70  out += (boost::format("%#20.17G") % metadata.get<double>(name)).str();
71  } else if (type == typeid(float)) {
72  out += (boost::format("%#20.15G") % metadata.get<float>(name)).str();
73  } else if (type == typeid(std::string)) {
74  out += "'" + metadata.get<std::string>(name) + "'";
75  if (out.size() > 80) {
76  continue; // Formatted data is too long; skip this item
77  }
78  }
79 
80  int const len = out.size();
81  if (len < 80) {
82  out += std::string(80 - len, ' ');
83  } else if (len > 80) {
84  // non-string item has a formatted value that is too long; this should never happen
85  throw LSST_EXCEPT(pex::exceptions::LogicError,
86  "Formatted data too long: " + std::to_string(len) + " > 80: \"" + out + "\"");
87  }
88 
89  result << out;
90  }
91 
92  return result.str();
93 }
94 
103 class StringStartSet {
104 public:
106  StringStartSet(std::initializer_list<std::string> const &input) : _minSize(-1) {
107  for (auto const &word : input) {
108  std::size_t const size = word.size();
109  if (size < _minSize) {
110  _minSize = size;
111  }
112  }
113  for (auto const &word : input) {
114  std::string const start = startString(word);
115  assert(_words.count(start) == 0); // This should be the only word that starts this way
116  _words[start] = word;
117  }
118  }
119 
121  bool matches(std::string const &key) const {
122  auto const iter = _words.find(startString(key));
123  if (iter == _words.end()) {
124  return false;
125  }
126  // Check that the full word matches too
127  std::string const &word = iter->second;
128  return key.compare(0, word.size(), word) == 0;
129  }
130 
131 private:
133 
135  std::string startString(std::string const &word) const { return word.substr(0, _minSize); }
136 
137  std::size_t _minSize; // Minimum length of provided words
138  Map _words; // Start of words --> full word
139 };
140 
146 static std::unordered_set<std::string> const ignoreKeys = {
147  // FITS core keywords
148  "SIMPLE", "BITPIX", "NAXIS", "EXTEND", "GCOUNT", "PCOUNT", "XTENSION", "TFIELDS", "BSCALE", "BZERO",
149  // FITS compression keywords
150  "ZBITPIX", "ZIMAGE", "ZCMPTYPE", "ZSIMPLE", "ZEXTEND", "ZBLANK", "ZDATASUM", "ZHECKSUM",
151  // Not essential, but will prevent fitsverify warnings
152  "DATASUM", "CHECKSUM"};
153 
159 StringStartSet const ignoreKeyStarts{// FITS core keywords
160  "NAXIS", "TZERO", "TSCAL",
161  // FITS compression keywords
162  "ZNAXIS", "ZTILE", "ZNAME", "ZVAL"};
163 
169 StringStartSet const ignoreKeyStartsWrite{"TFORM", "TTYPE"};
170 
171 // Strip leading and trailing single quotes and whitespace from a string.
172 std::string strip(std::string const &s) {
173  if (s.empty()) return s;
174  std::size_t i1 = s.find_first_not_of(" '");
175  std::size_t i2 = s.find_last_not_of(" '");
176  return s.substr(i1, (i1 == std::string::npos) ? 0 : 1 + i2 - i1);
177 }
178 
179 // ---- FITS binary table format codes for various C++ types. -----------------------------------------------
180 
181 char getFormatCode(bool *) { return 'X'; }
182 char getFormatCode(std::string *) { return 'A'; }
183 char getFormatCode(std::int8_t *) { return 'S'; }
184 char getFormatCode(std::uint8_t *) { return 'B'; }
185 char getFormatCode(std::int16_t *) { return 'I'; }
186 char getFormatCode(std::uint16_t *) { return 'U'; }
187 char getFormatCode(std::int32_t *) { return 'J'; }
188 char getFormatCode(std::uint32_t *) { return 'V'; }
189 char getFormatCode(std::int64_t *) { return 'K'; }
190 char getFormatCode(float *) { return 'E'; }
191 char getFormatCode(double *) { return 'D'; }
192 char getFormatCode(std::complex<float> *) { return 'C'; }
193 char getFormatCode(std::complex<double> *) { return 'M'; }
194 char getFormatCode(lsst::geom::Angle *) { return 'D'; }
195 
196 // ---- Create a TFORM value for the given type and size ----------------------------------------------------
197 
198 template <typename T>
199 std::string makeColumnFormat(int size = 1) {
200  if (size > 0) {
201  return (boost::format("%d%c") % size % getFormatCode((T *)0)).str();
202  } else if (size < 0) {
203  // variable length, max size given as -size
204  return (boost::format("1Q%c(%d)") % getFormatCode((T *)0) % (-size)).str();
205  } else {
206  // variable length, max size unknown
207  return (boost::format("1Q%c") % getFormatCode((T *)0)).str();
208  }
209 }
210 
211 // ---- Traits class to get cfitsio type constants from templates -------------------------------------------
212 
213 template <typename T>
214 struct FitsType;
215 
216 template <>
217 struct FitsType<bool> {
218  static int const CONSTANT = TLOGICAL;
219 };
220 template <>
221 struct FitsType<char> {
222  static int const CONSTANT = TSTRING;
223 };
224 template <>
225 struct FitsType<signed char> {
226  static int const CONSTANT = TSBYTE;
227 };
228 template <>
229 struct FitsType<unsigned char> {
230  static int const CONSTANT = TBYTE;
231 };
232 template <>
233 struct FitsType<short> {
234  static int const CONSTANT = TSHORT;
235 };
236 template <>
237 struct FitsType<unsigned short> {
238  static int const CONSTANT = TUSHORT;
239 };
240 template <>
241 struct FitsType<int> {
242  static int const CONSTANT = TINT;
243 };
244 template <>
245 struct FitsType<unsigned int> {
246  static int const CONSTANT = TUINT;
247 };
248 template <>
249 struct FitsType<long> {
250  static int const CONSTANT = TLONG;
251 };
252 template <>
253 struct FitsType<unsigned long> {
254  static int const CONSTANT = TULONG;
255 };
256 template <>
257 struct FitsType<long long> {
258  static int const CONSTANT = TLONGLONG;
259 };
260 template <>
261 struct FitsType<unsigned long long> {
262  static int const CONSTANT = TLONGLONG;
263 };
264 template <>
265 struct FitsType<float> {
266  static int const CONSTANT = TFLOAT;
267 };
268 template <>
269 struct FitsType<double> {
270  static int const CONSTANT = TDOUBLE;
271 };
272 template <>
273 struct FitsType<lsst::geom::Angle> {
274  static int const CONSTANT = TDOUBLE;
275 };
276 template <>
277 struct FitsType<std::complex<float> > {
278  static int const CONSTANT = TCOMPLEX;
279 };
280 template <>
281 struct FitsType<std::complex<double> > {
282  static int const CONSTANT = TDBLCOMPLEX;
283 };
284 
285 // We use TBIT when writing booleans to table cells, but TLOGICAL in headers.
286 template <typename T>
287 struct FitsTableType : public FitsType<T> {};
288 template <>
289 struct FitsTableType<bool> {
290  static int const CONSTANT = TBIT;
291 };
292 
293 template <typename T>
294 struct FitsBitPix;
295 
296 template <>
297 struct FitsBitPix<unsigned char> {
298  static int const CONSTANT = BYTE_IMG;
299 };
300 template <>
301 struct FitsBitPix<short> {
302  static int const CONSTANT = SHORT_IMG;
303 };
304 template <>
305 struct FitsBitPix<unsigned short> {
306  static int const CONSTANT = USHORT_IMG;
307 };
308 template <>
309 struct FitsBitPix<int> {
310  static int const CONSTANT = LONG_IMG;
311 }; // not a typo!
312 template <>
313 struct FitsBitPix<unsigned int> {
314  static int const CONSTANT = ULONG_IMG;
315 };
316 template <>
317 struct FitsBitPix<std::int64_t> {
318  static int const CONSTANT = LONGLONG_IMG;
319 };
320 template <>
321 struct FitsBitPix<std::uint64_t> {
322  static int const CONSTANT = LONGLONG_IMG;
323 };
324 template <>
325 struct FitsBitPix<float> {
326  static int const CONSTANT = FLOAT_IMG;
327 };
328 template <>
329 struct FitsBitPix<double> {
330  static int const CONSTANT = DOUBLE_IMG;
331 };
332 
333 bool isFitsImageTypeSigned(int constant) {
334  switch (constant) {
335  case BYTE_IMG: return false;
336  case SHORT_IMG: return true;
337  case USHORT_IMG: return false;
338  case LONG_IMG: return true;
339  case ULONG_IMG: return false;
340  case LONGLONG_IMG: return true;
341  case FLOAT_IMG: return true;
342  case DOUBLE_IMG: return true;
343  }
344  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, "Invalid constant.");
345 }
346 
347 static bool allowImageCompression = true;
348 
349 int fitsTypeForBitpix(int bitpix) {
350  switch (bitpix) {
351  case 8:
352  return TBYTE;
353  case 16:
354  return TSHORT;
355  case 32:
356  return TINT;
357  case 64:
358  return TLONGLONG;
359  case -32:
360  return TFLOAT;
361  case -64:
362  return TDOUBLE;
363  default:
365  os << "Invalid bitpix value: " << bitpix;
366  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, os.str());
367  }
368 }
369 
370 /*
371  * Information about one item of metadata: is a comment? is valid?
372  *
373  * See isCommentIsValid for more information.
374  */
375 struct ItemInfo {
376  ItemInfo(bool isComment, bool isValid) : isComment(isComment), isValid(isValid) {}
377  bool isComment;
378  bool isValid;
379 };
380 
381 /*
382  * Is an item a commnt (or history) and is it usable in a FITS header?
383  *
384  * For an item to be valid:
385  * - If name is COMMENT or HISTORY then the item must be of type std::string
386  * - All other items are always valid
387  */
388 ItemInfo isCommentIsValid(daf::base::PropertyList const &pl, std::string const &name) {
389  if (!pl.exists(name)) {
390  return ItemInfo(false, false);
391  }
392  std::type_info const &type = pl.typeOf(name);
393  if ((name == "COMMENT") || (name == "HISTORY")) {
394  return ItemInfo(true, type == typeid(std::string));
395  }
396  return ItemInfo(false, true);
397 }
398 
399 } // namespace
400 
401 // ----------------------------------------------------------------------------------------------------------
402 // ---- Implementations for stuff in fits.h -----------------------------------------------------------------
403 // ----------------------------------------------------------------------------------------------------------
404 
405 std::string makeErrorMessage(std::string const &fileName, int status, std::string const &msg) {
407  os << "cfitsio error";
408  if (fileName != "") {
409  os << " (" << fileName << ")";
410  }
411  if (status != 0) {
412  char fitsErrMsg[FLEN_ERRMSG];
413  fits_get_errstatus(status, fitsErrMsg);
414  os << ": " << fitsErrMsg << " (" << status << ")";
415  }
416  if (msg != "") {
417  os << " : " << msg;
418  }
419  os << "\ncfitsio error stack:\n";
420  char cfitsioMsg[FLEN_ERRMSG];
421  while (fits_read_errmsg(cfitsioMsg) != 0) {
422  os << " " << cfitsioMsg << "\n";
423  }
424  return os.str();
425 }
426 
427 std::string makeErrorMessage(void *fptr, int status, std::string const &msg) {
428  std::string fileName = "";
429  fitsfile *fd = reinterpret_cast<fitsfile *>(fptr);
430  if (fd != 0 && fd->Fptr != 0 && fd->Fptr->filename != 0) {
431  fileName = fd->Fptr->filename;
432  }
433  return makeErrorMessage(fileName, status, msg);
434 }
435 
437  std::set<std::string> const &excludeNames) {
438  daf::base::PropertyList const *pl = dynamic_cast<daf::base::PropertyList const *>(&metadata);
439  std::vector<std::string> allParamNames;
440  if (pl) {
441  allParamNames = pl->getOrderedNames();
442  } else {
443  allParamNames = metadata.paramNames(false);
444  }
445  std::vector<std::string> desiredParamNames;
446  for (auto const &name : allParamNames) {
447  if (excludeNames.count(name) == 0) {
448  desiredParamNames.push_back(name);
449  }
450  }
451  return makeLimitedFitsHeaderImpl(desiredParamNames, metadata);
452 }
453 
455  if (_managed) std::free(_ptr);
456  _ptr = 0;
457  _len = 0;
458  _managed = true;
459 }
460 
462  reset();
463  _ptr = std::malloc(len);
464  _len = len;
465  _managed = true;
466 }
467 
468 template <typename T>
469 int getBitPix() {
470  return FitsBitPix<T>::CONSTANT;
471 }
472 
473 // ----------------------------------------------------------------------------------------------------------
474 // ---- Implementations for Fits class ----------------------------------------------------------------------
475 // ----------------------------------------------------------------------------------------------------------
476 
478  std::string fileName = "<unknown>";
479  fitsfile *fd = reinterpret_cast<fitsfile *>(fptr);
480  if (fd != 0 && fd->Fptr != 0 && fd->Fptr->filename != 0) {
481  fileName = fd->Fptr->filename;
482  }
483  return fileName;
484 }
485 
487  int n = 1;
488  fits_get_hdu_num(reinterpret_cast<fitsfile *>(fptr), &n);
489  return n - 1;
490 }
491 
492 void Fits::setHdu(int hdu, bool relative) {
493  if (relative) {
494  fits_movrel_hdu(reinterpret_cast<fitsfile *>(fptr), hdu, 0, &status);
495  if (behavior & AUTO_CHECK) {
496  LSST_FITS_CHECK_STATUS(*this, boost::format("Incrementing HDU by %d") % hdu);
497  }
498  } else {
499  if (hdu != DEFAULT_HDU) {
500  fits_movabs_hdu(reinterpret_cast<fitsfile *>(fptr), hdu + 1, 0, &status);
501  }
502  if (hdu == DEFAULT_HDU && getHdu() == 0 && getImageDim() == 0) {
503  // want a silent failure here
504  int tmpStatus = status;
505  fits_movrel_hdu(reinterpret_cast<fitsfile *>(fptr), 1, 0, &tmpStatus);
506  }
507  if (behavior & AUTO_CHECK) {
508  LSST_FITS_CHECK_STATUS(*this, boost::format("Moving to HDU %d") % hdu);
509  }
510  }
511 }
512 
514  int n = 0;
515  fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &n, &status);
516  if (behavior & AUTO_CHECK) {
517  LSST_FITS_CHECK_STATUS(*this, "Getting number of HDUs in file.");
518  }
519  return n;
520 }
521 
522 // ---- Writing and updating header keys --------------------------------------------------------------------
523 
524 namespace {
525 
526 // Impl functions in the anonymous namespace do special handling for strings, bools, and IEEE fp values.
527 
534 std::string nonFiniteDoubleToString(double value) {
535  if (std::isfinite(value)) {
536  return "";
537  }
538  if (std::isnan(value)) {
539  return "NAN";
540  }
541  if (value < 0) {
542  return "-INFINITY";
543  }
544  return "+INFINITY";
545 }
546 
552 double stringToNonFiniteDouble(std::string const &value) {
553  if (value == "NAN") {
555  }
556  if (value == "+INFINITY") {
558  }
559  if (value == "-INFINITY") {
561  }
562  return 0;
563 }
564 
565 template <typename T>
566 void updateKeyImpl(Fits &fits, char const *key, T const &value, char const *comment) {
567  fits_update_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<T>::CONSTANT, const_cast<char *>(key),
568  const_cast<T *>(&value), const_cast<char *>(comment), &fits.status);
569 }
570 
571 void updateKeyImpl(Fits &fits, char const *key, std::string const &value, char const *comment) {
572  fits_update_key_longstr(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key),
573  const_cast<char *>(value.c_str()), const_cast<char *>(comment), &fits.status);
574 }
575 
576 void updateKeyImpl(Fits &fits, char const *key, bool const &value, char const *comment) {
577  int v = value;
578  fits_update_key(reinterpret_cast<fitsfile *>(fits.fptr), TLOGICAL, const_cast<char *>(key), &v,
579  const_cast<char *>(comment), &fits.status);
580 }
581 
582 void updateKeyImpl(Fits &fits, char const *key, double const &value, char const *comment) {
583  std::string strValue = nonFiniteDoubleToString(value);
584  if (!strValue.empty()) {
585  updateKeyImpl(fits, key, strValue, comment);
586  } else {
587  fits_update_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<double>::CONSTANT,
588  const_cast<char *>(key), const_cast<double *>(&value), const_cast<char *>(comment),
589  &fits.status);
590  }
591 }
592 
593 template <typename T>
594 void writeKeyImpl(Fits &fits, char const *key, T const &value, char const *comment) {
595  fits_write_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<T>::CONSTANT, const_cast<char *>(key),
596  const_cast<T *>(&value), const_cast<char *>(comment), &fits.status);
597 }
598 
599 void writeKeyImpl(Fits &fits, char const *key, std::string const &value, char const *comment) {
600  if (strncmp(key, "COMMENT", 7) == 0) {
601  fits_write_comment(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(value.c_str()),
602  &fits.status);
603  } else if (strncmp(key, "HISTORY", 7) == 0) {
604  fits_write_history(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(value.c_str()),
605  &fits.status);
606  } else {
607  fits_write_key_longstr(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key),
608  const_cast<char *>(value.c_str()), const_cast<char *>(comment), &fits.status);
609  }
610 }
611 
612 void writeKeyImpl(Fits &fits, char const *key, bool const &value, char const *comment) {
613  int v = value;
614  fits_write_key(reinterpret_cast<fitsfile *>(fits.fptr), TLOGICAL, const_cast<char *>(key), &v,
615  const_cast<char *>(comment), &fits.status);
616 }
617 
618 void writeKeyImpl(Fits &fits, char const *key, double const &value, char const *comment) {
619  std::string strValue = nonFiniteDoubleToString(value);
620  if (!strValue.empty()) {
621  writeKeyImpl(fits, key, strValue, comment);
622  } else {
623  fits_write_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<double>::CONSTANT,
624  const_cast<char *>(key), const_cast<double *>(&value), const_cast<char *>(comment),
625  &fits.status);
626  }
627 }
628 
629 } // namespace
630 
631 template <typename T>
632 void Fits::updateKey(std::string const &key, T const &value, std::string const &comment) {
633  updateKeyImpl(*this, key.c_str(), value, comment.c_str());
634  if (behavior & AUTO_CHECK) {
635  LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s': '%s'") % key % value);
636  }
637 }
638 
639 template <typename T>
640 void Fits::writeKey(std::string const &key, T const &value, std::string const &comment) {
641  writeKeyImpl(*this, key.c_str(), value, comment.c_str());
642  if (behavior & AUTO_CHECK) {
643  LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s': '%s'") % key % value);
644  }
645 }
646 
647 template <typename T>
648 void Fits::updateKey(std::string const &key, T const &value) {
649  updateKeyImpl(*this, key.c_str(), value, 0);
650  if (behavior & AUTO_CHECK) {
651  LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s': '%s'") % key % value);
652  }
653 }
654 
655 template <typename T>
656 void Fits::writeKey(std::string const &key, T const &value) {
657  writeKeyImpl(*this, key.c_str(), value, 0);
658  if (behavior & AUTO_CHECK) {
659  LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s': '%s'") % key % value);
660  }
661 }
662 
663 template <typename T>
664 void Fits::updateColumnKey(std::string const &prefix, int n, T const &value, std::string const &comment) {
665  updateKey((boost::format("%s%d") % prefix % (n + 1)).str(), value, comment);
666  if (behavior & AUTO_CHECK) {
667  LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s%d': '%s'") % prefix % (n + 1) % value);
668  }
669 }
670 
671 template <typename T>
672 void Fits::writeColumnKey(std::string const &prefix, int n, T const &value, std::string const &comment) {
673  writeKey((boost::format("%s%d") % prefix % (n + 1)).str(), value, comment);
674  if (behavior & AUTO_CHECK) {
675  LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s%d': '%s'") % prefix % (n + 1) % value);
676  }
677 }
678 
679 template <typename T>
680 void Fits::updateColumnKey(std::string const &prefix, int n, T const &value) {
681  updateKey((boost::format("%s%d") % prefix % (n + 1)).str(), value);
682  if (behavior & AUTO_CHECK) {
683  LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s%d': '%s'") % prefix % (n + 1) % value);
684  }
685 }
686 
687 template <typename T>
688 void Fits::writeColumnKey(std::string const &prefix, int n, T const &value) {
689  writeKey((boost::format("%s%d") % prefix % (n + 1)).str(), value);
690  if (behavior & AUTO_CHECK) {
691  LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s%d': '%s'") % prefix % (n + 1) % value);
692  }
693 }
694 
695 // ---- Reading header keys ---------------------------------------------------------------------------------
696 
697 namespace {
698 
699 template <typename T>
700 void readKeyImpl(Fits &fits, char const *key, T &value) {
701  fits_read_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<T>::CONSTANT, const_cast<char *>(key),
702  &value, 0, &fits.status);
703 }
704 
705 void readKeyImpl(Fits &fits, char const *key, std::string &value) {
706  char *buf = 0;
707  fits_read_key_longstr(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key), &buf, 0,
708  &fits.status);
709  if (buf) {
710  value = strip(buf);
711  free(buf);
712  }
713 }
714 
715 void readKeyImpl(Fits &fits, char const *key, double &value) {
716  // We need to check for the possibility that the value is a special string (for NAN, +/-Inf).
717  // If a quote mark (') is present then it's a string.
718 
719  char buf[FLEN_VALUE];
720  fits_read_keyword(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key), buf, 0, &fits.status);
721  if (fits.status != 0) {
722  return;
723  }
724  if (std::string(buf).find('\'') != std::string::npos) {
725  std::string unquoted;
726  readKeyImpl(fits, key, unquoted); // Let someone else remove quotes and whitespace
727  if (fits.status != 0) {
728  return;
729  }
730  value = stringToNonFiniteDouble(unquoted);
731  if (value == 0) {
732  throw LSST_EXCEPT(
734  (boost::format("Unrecognised string value for keyword '%s' when parsing as double: %s") %
735  key % unquoted)
736  .str());
737  }
738  } else {
739  fits_read_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<double>::CONSTANT,
740  const_cast<char *>(key), &value, 0, &fits.status);
741  }
742 }
743 
744 } // namespace
745 
746 template <typename T>
747 void Fits::readKey(std::string const &key, T &value) {
748  readKeyImpl(*this, key.c_str(), value);
749  if (behavior & AUTO_CHECK) {
750  LSST_FITS_CHECK_STATUS(*this, boost::format("Reading key '%s'") % key);
751  }
752 }
753 
755  char key[81]; // allow for terminating NUL
756  char value[81];
757  char comment[81];
758  int nKeys = 0;
759  fits_get_hdrspace(reinterpret_cast<fitsfile *>(fptr), &nKeys, 0, &status);
760  std::string keyStr;
761  std::string valueStr;
762  std::string commentStr;
763  int i = 1;
764  while (i <= nKeys) {
765  fits_read_keyn(reinterpret_cast<fitsfile *>(fptr), i, key, value, comment, &status);
766  keyStr = key;
767  valueStr = value;
768  commentStr = comment;
769  ++i;
770  while (valueStr.size() > 2 && valueStr[valueStr.size() - 2] == '&' && i <= nKeys) {
771  // we're using key to hold the entire record here; the actual key is safe in keyStr
772  fits_read_record(reinterpret_cast<fitsfile *>(fptr), i, key, &status);
773  if (strncmp(key, "CONTINUE", 8) != 0) {
774  // require both trailing '&' and CONTINUE to invoke long-string handling
775  break;
776  }
777  std::string card = key;
778  valueStr.erase(valueStr.size() - 2);
779  std::size_t firstQuote = card.find('\'');
780  if (firstQuote == std::string::npos) {
781  throw LSST_EXCEPT(
782  FitsError,
784  fptr, status,
785  boost::format("Invalid CONTINUE at header key %d: \"%s\".") % i % card));
786  }
787  std::size_t lastQuote = card.find('\'', firstQuote + 1);
788  if (lastQuote == std::string::npos) {
789  throw LSST_EXCEPT(
790  FitsError,
792  fptr, status,
793  boost::format("Invalid CONTINUE at header key %d: \"%s\".") % i % card));
794  }
795  valueStr += card.substr(firstQuote + 1, lastQuote - firstQuote);
796  std::size_t slash = card.find('/', lastQuote + 1);
797  if (slash != std::string::npos) {
798  commentStr += strip(card.substr(slash + 1));
799  }
800  ++i;
801  }
802  if (behavior & AUTO_CHECK) {
803  LSST_FITS_CHECK_STATUS(*this, boost::format("Reading key '%s'") % keyStr);
804  }
805  functor(keyStr, valueStr, commentStr);
806  }
807 }
808 
809 // ---- Reading and writing PropertySet/PropertyList --------------------------------------------------------
810 
811 namespace {
812 
813 bool isKeyIgnored(std::string const &key, bool write = false) {
814  return ((ignoreKeys.find(key) != ignoreKeys.end()) || ignoreKeyStarts.matches(key) ||
815  (write && ignoreKeyStartsWrite.matches(key)));
816 }
817 
818 class MetadataIterationFunctor : public HeaderIterationFunctor {
819 public:
820  void operator()(std::string const &key, std::string const &value, std::string const &comment) override;
821 
822  template <typename T>
823  void add(std::string const &key, T value, std::string const &comment) {
824  if (list) {
825  list->add(key, value, comment);
826  } else {
827  set->add(key, value);
828  }
829  }
830 
831  bool strip;
832  daf::base::PropertySet *set;
833  daf::base::PropertyList *list;
834 };
835 
836 void MetadataIterationFunctor::operator()(std::string const &key, std::string const &value,
837  std::string const &comment) {
838  static boost::regex const boolRegex("[tTfF]");
839  static boost::regex const intRegex("[+-]?[0-9]+");
840  static boost::regex const doubleRegex("[+-]?([0-9]*\\.?[0-9]+|[0-9]+\\.?[0-9]*)([eE][+-]?[0-9]+)?");
841  static boost::regex const fitsStringRegex("'(.*?) *'");
842  // regex for two-line comment added to all FITS headers by CFITSIO
843  static boost::regex const fitsDefinitionCommentRegex(
844  " *(FITS \\(Flexible Image Transport System\\)|and Astrophysics', volume 376, page 359).*");
845  boost::smatch matchStrings;
846 
847  if (strip && isKeyIgnored(key)) {
848  return;
849  }
850 
851  std::istringstream converter(value);
852  if (boost::regex_match(value, boolRegex)) {
853  // convert the string to an bool
854  add(key, bool(value == "T" || value == "t"), comment);
855  } else if (boost::regex_match(value, intRegex)) {
856  // convert the string to an int
858  converter >> val;
859  if (val < (1LL << 31) && val > -(1LL << 31)) {
860  add(key, static_cast<int>(val), comment);
861  } else {
862  add(key, val, comment);
863  }
864  } else if (boost::regex_match(value, doubleRegex)) {
865  // convert the string to a double
866  double val;
867  converter >> val;
868  add(key, val, comment);
869  } else if (boost::regex_match(value, matchStrings, fitsStringRegex)) {
870  std::string const str = matchStrings[1].str(); // strip off the enclosing single quotes
871  double val = stringToNonFiniteDouble(str);
872  if (val != 0.0) {
873  add(key, val, comment);
874  } else {
875  add(key, str, comment);
876  }
877  } else if (key == "HISTORY") {
878  add(key, comment, "");
879  } else if (key == "COMMENT" && !(strip && boost::regex_match(comment, fitsDefinitionCommentRegex))) {
880  add(key, comment, "");
881  } else if (value.empty()) {
882  // do nothing for empty values
883  } else {
884  throw LSST_EXCEPT(
885  afw::fits::FitsError,
886  (boost::format("Could not parse header value for key '%s': '%s'") % key % value).str());
887  }
888 }
889 
890 void writeKeyFromProperty(Fits &fits, daf::base::PropertySet const &metadata, std::string const &key,
891  char const *comment = 0) {
892  std::type_info const &valueType = metadata.typeOf(key);
893  if (valueType == typeid(bool)) {
894  if (metadata.isArray(key)) {
895  std::vector<bool> tmp = metadata.getArray<bool>(key);
896  // work around unfortunate specialness of std::vector<bool>
897  for (std::size_t i = 0; i != tmp.size(); ++i) {
898  writeKeyImpl(fits, key.c_str(), static_cast<bool>(tmp[i]), comment);
899  }
900  } else {
901  writeKeyImpl(fits, key.c_str(), metadata.get<bool>(key), comment);
902  }
903  } else if (valueType == typeid(std::uint8_t)) {
904  if (metadata.isArray(key)) {
905  std::vector<std::uint8_t> tmp = metadata.getArray<std::uint8_t>(key);
906  for (std::size_t i = 0; i != tmp.size(); ++i) {
907  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
908  }
909  } else {
910  writeKeyImpl(fits, key.c_str(), metadata.get<std::uint8_t>(key), comment);
911  }
912  } else if (valueType == typeid(int)) {
913  if (metadata.isArray(key)) {
914  std::vector<int> tmp = metadata.getArray<int>(key);
915  for (std::size_t i = 0; i != tmp.size(); ++i) {
916  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
917  }
918  } else {
919  writeKeyImpl(fits, key.c_str(), metadata.get<int>(key), comment);
920  }
921  } else if (valueType == typeid(long)) {
922  if (metadata.isArray(key)) {
923  std::vector<long> tmp = metadata.getArray<long>(key);
924  for (std::size_t i = 0; i != tmp.size(); ++i) {
925  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
926  }
927  } else {
928  writeKeyImpl(fits, key.c_str(), metadata.get<long>(key), comment);
929  }
930  } else if (valueType == typeid(long long)) {
931  if (metadata.isArray(key)) {
932  std::vector<long long> tmp = metadata.getArray<long long>(key);
933  for (std::size_t i = 0; i != tmp.size(); ++i) {
934  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
935  }
936  } else {
937  writeKeyImpl(fits, key.c_str(), metadata.get<long long>(key), comment);
938  }
939  } else if (valueType == typeid(std::int64_t)) {
940  if (metadata.isArray(key)) {
941  std::vector<std::int64_t> tmp = metadata.getArray<std::int64_t>(key);
942  for (std::size_t i = 0; i != tmp.size(); ++i) {
943  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
944  }
945  } else {
946  writeKeyImpl(fits, key.c_str(), metadata.get<std::int64_t>(key), comment);
947  }
948  } else if (valueType == typeid(double)) {
949  if (metadata.isArray(key)) {
950  std::vector<double> tmp = metadata.getArray<double>(key);
951  for (std::size_t i = 0; i != tmp.size(); ++i) {
952  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
953  }
954  } else {
955  writeKeyImpl(fits, key.c_str(), metadata.get<double>(key), comment);
956  }
957  } else if (valueType == typeid(std::string)) {
958  if (metadata.isArray(key)) {
959  std::vector<std::string> tmp = metadata.getArray<std::string>(key);
960  for (std::size_t i = 0; i != tmp.size(); ++i) {
961  writeKeyImpl(fits, key.c_str(), tmp[i], comment);
962  }
963  } else {
964  writeKeyImpl(fits, key.c_str(), metadata.get<std::string>(key), comment);
965  }
966  } else {
967  // FIXME: inherited this error handling from fitsIo.cc; need a better option.
968  LOGLS_WARN("afw.writeKeyFromProperty",
969  makeErrorMessage(fits.fptr, fits.status,
970  boost::format("In %s, unknown type '%s' for key '%s'.") %
971  BOOST_CURRENT_FUNCTION % valueType.name() % key));
972  }
973  if (fits.behavior & Fits::AUTO_CHECK) {
974  LSST_FITS_CHECK_STATUS(fits, boost::format("Writing key '%s'") % key);
975  }
976 }
977 
978 } // namespace
979 
981  MetadataIterationFunctor f;
982  f.strip = strip;
983  f.set = &metadata;
984  f.list = dynamic_cast<daf::base::PropertyList *>(&metadata);
985  forEachKey(f);
986 }
987 
989  typedef std::vector<std::string> NameList;
990  daf::base::PropertyList const *pl = dynamic_cast<daf::base::PropertyList const *>(&metadata);
991  NameList paramNames;
992  if (pl) {
993  paramNames = pl->getOrderedNames();
994  } else {
995  paramNames = metadata.paramNames(false);
996  }
997  for (NameList::const_iterator i = paramNames.begin(); i != paramNames.end(); ++i) {
998  if (!isKeyIgnored(*i, true)) {
999  if (pl) {
1000  writeKeyFromProperty(*this, metadata, *i, pl->getComment(*i).c_str());
1001  } else {
1002  writeKeyFromProperty(*this, metadata, *i);
1003  }
1004  }
1005  }
1006 }
1007 
1008 // ---- Manipulating tables ---------------------------------------------------------------------------------
1009 
1011  char *ttype = 0;
1012  char *tform = 0;
1013  fits_create_tbl(reinterpret_cast<fitsfile *>(fptr), BINARY_TBL, 0, 0, &ttype, &tform, 0, 0, &status);
1014  if (behavior & AUTO_CHECK) {
1015  LSST_FITS_CHECK_STATUS(*this, "Creating binary table");
1016  }
1017 }
1018 
1019 template <typename T>
1020 int Fits::addColumn(std::string const &ttype, int size) {
1021  int nCols = 0;
1022  fits_get_num_cols(reinterpret_cast<fitsfile *>(fptr), &nCols, &status);
1023  std::string tform = makeColumnFormat<T>(size);
1024  fits_insert_col(reinterpret_cast<fitsfile *>(fptr), nCols + 1, const_cast<char *>(ttype.c_str()),
1025  const_cast<char *>(tform.c_str()), &status);
1026  if (behavior & AUTO_CHECK) {
1027  LSST_FITS_CHECK_STATUS(*this, boost::format("Adding column '%s' with size %d") % ttype % size);
1028  }
1029  return nCols;
1030 }
1031 
1032 template <typename T>
1033 int Fits::addColumn(std::string const &ttype, int size, std::string const &comment) {
1034  int nCols = addColumn<T>(ttype, size);
1035  updateColumnKey("TTYPE", nCols, ttype, comment);
1036  if (behavior & AUTO_CHECK) {
1037  LSST_FITS_CHECK_STATUS(*this, boost::format("Adding column '%s' with size %d") % ttype % size);
1038  }
1039  return nCols;
1040 }
1041 
1043  long first = 0;
1044  fits_get_num_rows(reinterpret_cast<fitsfile *>(fptr), &first, &status);
1045  fits_insert_rows(reinterpret_cast<fitsfile *>(fptr), first, nRows, &status);
1046  if (behavior & AUTO_CHECK) {
1047  LSST_FITS_CHECK_STATUS(*this, boost::format("Adding %d rows to binary table") % nRows);
1048  }
1049  return first;
1050 }
1051 
1053  long r = 0;
1054  fits_get_num_rows(reinterpret_cast<fitsfile *>(fptr), &r, &status);
1055  if (behavior & AUTO_CHECK) {
1056  LSST_FITS_CHECK_STATUS(*this, "Checking how many rows are in table");
1057  }
1058  return r;
1059 }
1060 
1061 template <typename T>
1062 void Fits::writeTableArray(std::size_t row, int col, int nElements, T const *value) {
1063  fits_write_col(reinterpret_cast<fitsfile *>(fptr), FitsTableType<T>::CONSTANT, col + 1, row + 1, 1,
1064  nElements, const_cast<T *>(value), &status);
1065  if (behavior & AUTO_CHECK) {
1066  LSST_FITS_CHECK_STATUS(*this, boost::format("Writing %d-element array at table cell (%d, %d)") %
1067  nElements % row % col);
1068  }
1069 }
1070 
1072  // cfitsio doesn't let us specify the size of a string, it just looks for null terminator.
1073  // Using std::string::c_str() guarantees that we have one. But we can't store arbitrary
1074  // data in a string field because cfitsio will also chop off anything after the first null
1075  // terminator.
1076  char const *tmp = value.c_str();
1077  fits_write_col(reinterpret_cast<fitsfile *>(fptr), TSTRING, col + 1, row + 1, 1, 1,
1078  const_cast<char const **>(&tmp), &status);
1079  if (behavior & AUTO_CHECK) {
1080  LSST_FITS_CHECK_STATUS(*this, boost::format("Writing value at table cell (%d, %d)") % row % col);
1081  }
1082 }
1083 
1084 template <typename T>
1085 void Fits::readTableArray(std::size_t row, int col, int nElements, T *value) {
1086  int anynul = false;
1087  fits_read_col(reinterpret_cast<fitsfile *>(fptr), FitsTableType<T>::CONSTANT, col + 1, row + 1, 1,
1088  nElements, 0, value, &anynul, &status);
1089  if (behavior & AUTO_CHECK) {
1090  LSST_FITS_CHECK_STATUS(*this, boost::format("Reading value at table cell (%d, %d)") % row % col);
1091  }
1092 }
1093 
1094 void Fits::readTableScalar(std::size_t row, int col, std::string &value, bool isVariableLength) {
1095  int anynul = false;
1096  long size = isVariableLength ? getTableArraySize(row, col) : getTableArraySize(col);
1097  // We can't directly write into a std::string until C++17.
1098  std::vector<char> buf(size + 1, 0);
1099  // cfitsio wants a char** because they imagine we might want an array of strings,
1100  // but we only want one element.
1101  char *tmp = &buf.front();
1102  fits_read_col(reinterpret_cast<fitsfile *>(fptr), TSTRING, col + 1, row + 1, 1, 1, 0, &tmp, &anynul,
1103  &status);
1104  if (behavior & AUTO_CHECK) {
1105  LSST_FITS_CHECK_STATUS(*this, boost::format("Reading value at table cell (%d, %d)") % row % col);
1106  }
1107  value = std::string(tmp);
1108 }
1109 
1111  int typecode = 0;
1112  long result = 0;
1113  long width = 0;
1114  fits_get_coltype(reinterpret_cast<fitsfile *>(fptr), col + 1, &typecode, &result, &width, &status);
1115  if (behavior & AUTO_CHECK) {
1116  LSST_FITS_CHECK_STATUS(*this, boost::format("Looking up array size for column %d") % col);
1117  }
1118  return result;
1119 }
1120 
1122  long result = 0;
1123  long offset = 0;
1124  fits_read_descript(reinterpret_cast<fitsfile *>(fptr), col + 1, row + 1, &result, &offset, &status);
1125  if (behavior & AUTO_CHECK) {
1126  LSST_FITS_CHECK_STATUS(*this, boost::format("Looking up array size for cell (%d, %d)") % row % col);
1127  }
1128  return result;
1129 }
1130 
1131 // ---- Manipulating images ---------------------------------------------------------------------------------
1132 
1134  long naxes = 0;
1135  fits_create_img(reinterpret_cast<fitsfile *>(fptr), 8, 0, &naxes, &status);
1136  if (behavior & AUTO_CHECK) {
1137  LSST_FITS_CHECK_STATUS(*this, "Creating empty image HDU");
1138  }
1139 }
1140 
1141 void Fits::createImageImpl(int bitpix, int naxis, long const *naxes) {
1142  fits_create_img(reinterpret_cast<fitsfile *>(fptr), bitpix, naxis, const_cast<long *>(naxes), &status);
1143  if (behavior & AUTO_CHECK) {
1144  LSST_FITS_CHECK_STATUS(*this, "Creating new image HDU");
1145  }
1146 }
1147 
1148 template <typename T>
1149 void Fits::writeImageImpl(T const *data, int nElements) {
1150  fits_write_img(reinterpret_cast<fitsfile *>(fptr), FitsType<T>::CONSTANT, 1, nElements,
1151  const_cast<T *>(data), &status);
1152  if (behavior & AUTO_CHECK) {
1153  LSST_FITS_CHECK_STATUS(*this, "Writing image");
1154  }
1155 }
1156 
1157 namespace {
1158 
1163 struct ImageCompressionContext {
1164 public:
1165  ImageCompressionContext(Fits &fits_, ImageCompressionOptions const &useThis)
1166  : fits(fits_), old(fits.getImageCompression()) {
1167  fits.setImageCompression(useThis);
1168  }
1169  ~ImageCompressionContext() {
1170  int status = 0;
1171  std::swap(status, fits.status);
1172  try {
1173  fits.setImageCompression(old);
1174  } catch (...) {
1175  LOGLS_WARN("afw.fits",
1176  makeErrorMessage(fits.fptr, fits.status, "Failed to restore compression settings"));
1177  }
1178  std::swap(status, fits.status);
1179  }
1180 
1181 private:
1182  Fits &fits; // FITS file we're working on
1183  ImageCompressionOptions old; // Former compression options, to be restored
1184 };
1185 
1186 } // anonymous namespace
1187 
1188 template <typename T>
1192  auto fits = reinterpret_cast<fitsfile *>(fptr);
1193  ImageCompressionOptions const &compression =
1194  image.getBBox().getArea() > 0
1195  ? options.compression
1197  ImageCompressionOptions::NONE); // cfitsio can't compress empty images
1198  ImageCompressionContext comp(*this, compression); // RAII
1199  if (behavior & AUTO_CHECK) {
1200  LSST_FITS_CHECK_STATUS(*this, "Activating compression for write image");
1201  }
1202 
1203  ImageScale scale = options.scaling.determine(image, mask);
1204 
1205  // We need a place to put the image+header, and CFITSIO needs to know the dimenions.
1206  ndarray::Vector<long, 2> dims(image.getArray().getShape().reverse());
1207  createImageImpl(scale.bitpix == 0 ? detail::Bitpix<T>::value : scale.bitpix, 2, dims.elems);
1208 
1209  // Write the header
1212  if (header) {
1213  std::shared_ptr<daf::base::PropertySet> copy = header->deepCopy();
1214  copy->combine(wcsMetadata);
1215  header = copy;
1216  } else {
1217  header = wcsMetadata;
1218  }
1219  writeMetadata(*header);
1220 
1221  // Scale the image how we want it on disk
1222  ndarray::Array<T const, 2, 2> array = makeContiguousArray(image.getArray());
1223  auto pixels = scale.toFits(array, compression.quantizeLevel != 0, options.scaling.fuzz,
1224  options.compression.tiles, options.scaling.seed);
1225 
1226  // We only want cfitsio to do the scale and zero for unsigned 64-bit integer types. For those,
1227  // "double bzero" has sufficient precision to represent the appropriate value. We'll let
1228  // cfitsio handle it itself.
1229  // In all other cases, we will convert the image to use the appropriate scale and zero
1230  // (because we want to fuzz the numbers in the quantisation), so we don't want cfitsio
1231  // rescaling.
1233  fits_set_bscale(fits, 1.0, 0.0, &status);
1234  if (behavior & AUTO_CHECK) {
1235  LSST_FITS_CHECK_STATUS(*this, "Setting bscale,bzero");
1236  }
1237  }
1238 
1239  // Write the pixels
1240  int const fitsType = scale.bitpix == 0 ? FitsType<T>::CONSTANT : fitsTypeForBitpix(scale.bitpix);
1241  fits_write_img(fits, fitsType, 1, pixels->getNumElements(), const_cast<void *>(pixels->getData()),
1242  &status);
1243  if (behavior & AUTO_CHECK) {
1244  LSST_FITS_CHECK_STATUS(*this, "Writing image");
1245  }
1246 
1247  // Now write the headers we didn't want cfitsio to know about when we were writing the pixels
1248  // (because we don't want it using them to modify the pixels, and we don't want it overwriting
1249  // these values).
1251  std::isfinite(scale.bzero) && std::isfinite(scale.bscale) && (scale.bscale != 0.0)) {
1253  if (scale.bzero != 0.0) {
1254  fits_write_key_lng(fits, "BZERO", static_cast<long>(scale.bzero),
1255  "Scaling: MEMORY = BZERO + BSCALE * DISK", &status);
1256  }
1257  if (scale.bscale != 1.0) {
1258  fits_write_key_lng(fits, "BSCALE", static_cast<long>(scale.bscale),
1259  "Scaling: MEMORY = BZERO + BSCALE * DISK", &status);
1260  }
1261  } else {
1262  fits_write_key_dbl(fits, "BZERO", scale.bzero, 12, "Scaling: MEMORY = BZERO + BSCALE * DISK",
1263  &status);
1264  fits_write_key_dbl(fits, "BSCALE", scale.bscale, 12, "Scaling: MEMORY = BZERO + BSCALE * DISK",
1265  &status);
1266  }
1267  if (behavior & AUTO_CHECK) {
1268  LSST_FITS_CHECK_STATUS(*this, "Writing BSCALE,BZERO");
1269  }
1270  }
1271 
1272  if (scale.bitpix > 0 && !std::numeric_limits<T>::is_integer) {
1273  fits_write_key_lng(fits, "BLANK", scale.blank, "Value for undefined pixels", &status);
1274  fits_write_key_lng(fits, "ZDITHER0", options.scaling.seed, "Dithering seed", &status);
1275  fits_write_key_str(fits, "ZQUANTIZ", "SUBTRACTIVE_DITHER_1", "Dithering algorithm", &status);
1276  if (behavior & AUTO_CHECK) {
1277  LSST_FITS_CHECK_STATUS(*this, "Writing [Z]BLANK");
1278  }
1279  }
1280 
1281  // cfitsio says this is deprecated, but Pan-STARRS found that it was sometimes necessary, writing:
1282  // "This forces a re-scan of the header to ensure everything's kosher.
1283  // Without this, compressed HDUs have been written out with PCOUNT=0 and TFORM1 not correctly set."
1284  fits_set_hdustruc(fits, &status);
1285  if (behavior & AUTO_CHECK) {
1286  LSST_FITS_CHECK_STATUS(*this, "Finalizing header");
1287  }
1288 }
1289 
1290 namespace {
1291 
1293 template <typename T, class Enable = void>
1294 struct NullValue {
1295  static T constexpr value = 0;
1296 };
1297 
1299 template <typename T>
1300 struct NullValue<T, typename std::enable_if<std::numeric_limits<T>::has_quiet_NaN>::type> {
1301  static T constexpr value = std::numeric_limits<T>::quiet_NaN();
1302 };
1303 
1304 } // namespace
1305 
1306 template <typename T>
1307 void Fits::readImageImpl(int nAxis, T *data, long *begin, long *end, long *increment) {
1308  T null = NullValue<T>::value;
1309  int anyNulls = 0;
1310  fits_read_subset(reinterpret_cast<fitsfile *>(fptr), FitsType<T>::CONSTANT, begin, end, increment,
1311  reinterpret_cast<void *>(&null), data, &anyNulls, &status);
1312  if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Reading image");
1313 }
1314 
1316  int nAxis = 0;
1317  fits_get_img_dim(reinterpret_cast<fitsfile *>(fptr), &nAxis, &status);
1318  if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting NAXIS");
1319  return nAxis;
1320 }
1321 
1322 void Fits::getImageShapeImpl(int maxDim, long *nAxes) {
1323  fits_get_img_size(reinterpret_cast<fitsfile *>(fptr), maxDim, nAxes, &status);
1324  if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting NAXES");
1325 }
1326 
1327 template <typename T>
1329  int imageType = 0;
1330  fits_get_img_equivtype(reinterpret_cast<fitsfile *>(fptr), &imageType, &status);
1331  if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting image type");
1333  if (imageType < 0) {
1334  return false; // can't represent floating-point with integer
1335  }
1337  if (isFitsImageTypeSigned(imageType)) {
1338  return FitsBitPix<T>::CONSTANT >= imageType;
1339  } else {
1340  // need extra bits to safely convert unsigned to signed
1341  return FitsBitPix<T>::CONSTANT > imageType;
1342  }
1343  } else {
1344  if (!isFitsImageTypeSigned(imageType)) {
1345  return FitsBitPix<T>::CONSTANT >= imageType;
1346  } else if (imageType == LONGLONG_IMG) {
1347  // workaround for CFITSIO not recognizing uint64 as
1348  // unsigned
1349  return FitsBitPix<T>::CONSTANT >= imageType;
1350  } else {
1351  return false;
1352  }
1353  }
1354  }
1355  // we allow all conversions to float and double, even if they lose precision
1356  return true;
1357 }
1358 
1360  int bitpix = 0;
1361  fits_get_img_equivtype(reinterpret_cast<fitsfile *>(fptr), &bitpix, &status);
1362  if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting image type");
1363  // FITS' 'BITPIX' key is the number of bits in a pixel, but negative for
1364  // floats. But the above CFITSIO routine adds support for unsigned
1365  // integers by checking BZERO for an offset as well. So the 'bitpix' value
1366  // we get back from that should be the raw value for signed integers and
1367  // floats, but may be something else (still positive) for unsigned, and
1368  // hence we'll compare to some FITSIO constants to be safe looking at
1369  // integers.
1370  if (bitpix < 0) {
1371  return "float" + std::to_string(-bitpix);
1372  }
1373  switch (bitpix) {
1374  case BYTE_IMG: return "uint8";
1375  case SBYTE_IMG: return "int8";
1376  case SHORT_IMG: return "int16";
1377  case USHORT_IMG: return "uint16";
1378  case LONG_IMG: return "int32";
1379  case ULONG_IMG: return "uint32";
1380  case LONGLONG_IMG: return "int64";
1381  }
1382  throw LSST_EXCEPT(
1383  FitsError,
1384  (boost::format("Unrecognized BITPIX value: %d") % bitpix).str()
1385  );
1386 }
1387 
1389  auto fits = reinterpret_cast<fitsfile *>(fptr);
1390  int compType = 0; // cfitsio compression type
1391  fits_get_compression_type(fits, &compType, &status);
1392  if (behavior & AUTO_CHECK) {
1393  LSST_FITS_CHECK_STATUS(*this, "Getting compression type");
1394  }
1395 
1396  ImageCompressionOptions::Tiles tiles = ndarray::allocate(MAX_COMPRESS_DIM);
1397  fits_get_tile_dim(fits, tiles.getNumElements(), tiles.getData(), &status);
1398  if (behavior & AUTO_CHECK) {
1399  LSST_FITS_CHECK_STATUS(*this, "Getting tile dimensions");
1400  }
1401 
1402  float quantizeLevel;
1403  fits_get_quantize_level(fits, &quantizeLevel, &status);
1404  if (behavior & AUTO_CHECK) {
1405  LSST_FITS_CHECK_STATUS(*this, "Getting quantizeLevel");
1406  }
1407 
1408  return ImageCompressionOptions(compressionAlgorithmFromCfitsio(compType), tiles, quantizeLevel);
1409 }
1410 
1412  auto fits = reinterpret_cast<fitsfile *>(fptr);
1413  fits_unset_compression_request(fits, &status); // wipe the slate and start over
1415  allowImageCompression ? comp.algorithm : ImageCompressionOptions::NONE;
1416  fits_set_compression_type(fits, compressionAlgorithmToCfitsio(algorithm), &status);
1417  if (behavior & AUTO_CHECK) {
1418  LSST_FITS_CHECK_STATUS(*this, "Setting compression type");
1419  }
1420 
1421  if (algorithm == ImageCompressionOptions::NONE) {
1422  // Nothing else worth doing
1423  return;
1424  }
1425 
1426  fits_set_tile_dim(fits, comp.tiles.getNumElements(), comp.tiles.getData(), &status);
1427  if (behavior & AUTO_CHECK) {
1428  LSST_FITS_CHECK_STATUS(*this, "Setting tile dimensions");
1429  }
1430 
1432  fits_set_quantize_level(fits, comp.quantizeLevel, &status);
1433  if (behavior & AUTO_CHECK) {
1434  LSST_FITS_CHECK_STATUS(*this, "Setting quantization level");
1435  }
1436  }
1437 }
1438 
1439 void setAllowImageCompression(bool allow) { allowImageCompression = allow; }
1440 
1441 bool getAllowImageCompression() { return allowImageCompression; }
1442 
1443 // ---- Manipulating files ----------------------------------------------------------------------------------
1444 
1445 Fits::Fits(std::string const &filename, std::string const &mode, int behavior_)
1446  : fptr(0), status(0), behavior(behavior_) {
1447  if (mode == "r" || mode == "rb") {
1448  fits_open_file(reinterpret_cast<fitsfile **>(&fptr), const_cast<char *>(filename.c_str()), READONLY,
1449  &status);
1450  } else if (mode == "w" || mode == "wb") {
1451  boost::filesystem::remove(filename); // cfitsio doesn't like over-writing files
1452  fits_create_file(reinterpret_cast<fitsfile **>(&fptr), const_cast<char *>(filename.c_str()), &status);
1453  } else if (mode == "a" || mode == "ab") {
1454  fits_open_file(reinterpret_cast<fitsfile **>(&fptr), const_cast<char *>(filename.c_str()), READWRITE,
1455  &status);
1456  int nHdu = 0;
1457  fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &nHdu, &status);
1458  fits_movabs_hdu(reinterpret_cast<fitsfile *>(fptr), nHdu, NULL, &status);
1459  if ((behavior & AUTO_CHECK) && (behavior & AUTO_CLOSE) && (status) && (fptr)) {
1460  // We're about to throw an exception, and the destructor won't get called
1461  // because we're in the constructor, so cleanup here first.
1462  int tmpStatus = 0;
1463  fits_close_file(reinterpret_cast<fitsfile *>(fptr), &tmpStatus);
1464  }
1465  } else {
1466  throw LSST_EXCEPT(
1467  FitsError,
1468  (boost::format("Invalid mode '%s' given when opening file '%s'") % mode % filename).str());
1469  }
1470  if (behavior & AUTO_CHECK) {
1471  LSST_FITS_CHECK_STATUS(*this, boost::format("Opening file '%s' with mode '%s'") % filename % mode);
1472  }
1473 }
1474 
1475 Fits::Fits(MemFileManager &manager, std::string const &mode, int behavior_)
1476  : fptr(0), status(0), behavior(behavior_) {
1477  typedef void *(*Reallocator)(void *, std::size_t);
1478  // It's a shame this logic is essentially a duplicate of above, but the innards are different enough
1479  // we can't really reuse it.
1480  if (mode == "r" || mode == "rb") {
1481  fits_open_memfile(reinterpret_cast<fitsfile **>(&fptr), "unused", READONLY, &manager._ptr,
1482  &manager._len, 0, 0, // no reallocator or deltasize necessary for READONLY
1483  &status);
1484  } else if (mode == "w" || mode == "wb") {
1485  Reallocator reallocator = 0;
1486  if (manager._managed) reallocator = &std::realloc;
1487  fits_create_memfile(reinterpret_cast<fitsfile **>(&fptr), &manager._ptr, &manager._len, 0,
1488  reallocator, // use default deltasize
1489  &status);
1490  } else if (mode == "a" || mode == "ab") {
1491  Reallocator reallocator = 0;
1492  if (manager._managed) reallocator = &std::realloc;
1493  fits_open_memfile(reinterpret_cast<fitsfile **>(&fptr), "unused", READWRITE, &manager._ptr,
1494  &manager._len, 0, reallocator, &status);
1495  int nHdu = 0;
1496  fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &nHdu, &status);
1497  fits_movabs_hdu(reinterpret_cast<fitsfile *>(fptr), nHdu, NULL, &status);
1498  if ((behavior & AUTO_CHECK) && (behavior & AUTO_CLOSE) && (status) && (fptr)) {
1499  // We're about to throw an exception, and the destructor won't get called
1500  // because we're in the constructor, so cleanup here first.
1501  int tmpStatus = 0;
1502  fits_close_file(reinterpret_cast<fitsfile *>(fptr), &tmpStatus);
1503  }
1504  } else {
1505  throw LSST_EXCEPT(FitsError,
1506  (boost::format("Invalid mode '%s' given when opening memory file at '%s'") % mode %
1507  manager._ptr)
1508  .str());
1509  }
1510  if (behavior & AUTO_CHECK) {
1512  *this, boost::format("Opening memory file at '%s' with mode '%s'") % manager._ptr % mode);
1513  }
1514 }
1515 
1517  fits_close_file(reinterpret_cast<fitsfile *>(fptr), &status);
1518  fptr = nullptr;
1519 }
1520 
1524  auto combined = std::make_shared<daf::base::PropertyList>();
1525  bool const asScalar = true;
1526  for (auto const &name : first->getOrderedNames()) {
1527  auto const iscv = isCommentIsValid(*first, name);
1528  if (iscv.isComment) {
1529  if (iscv.isValid) {
1530  combined->add<std::string>(name, first->getArray<std::string>(name));
1531  }
1532  } else {
1533  combined->copy(name, first, name, asScalar);
1534  }
1535  }
1536  for (auto const &name : second->getOrderedNames()) {
1537  auto const iscv = isCommentIsValid(*second, name);
1538  if (iscv.isComment) {
1539  if (iscv.isValid) {
1540  combined->add<std::string>(name, second->getArray<std::string>(name));
1541  }
1542  } else {
1543  // `copy` will replace an item, even if has a different type, so no need to call `remove`
1544  combined->copy(name, second, name, asScalar);
1545  }
1546  }
1547  return combined;
1548 }
1549 
1552  fp.setHdu(hdu);
1553  return readMetadata(fp, strip);
1554 }
1555 
1558  fp.setHdu(hdu);
1559  return readMetadata(fp, strip);
1560 }
1561 
1563  auto metadata = std::make_shared<lsst::daf::base::PropertyList>();
1564  fitsfile.readMetadata(*metadata, strip);
1565  // if INHERIT=T, we want to also include header entries from the primary HDU
1566  int oldHdu = fitsfile.getHdu();
1567  if (oldHdu != 0 && metadata->exists("INHERIT")) {
1568  bool inherit = false;
1569  if (metadata->typeOf("INHERIT") == typeid(std::string)) {
1570  inherit = (metadata->get<std::string>("INHERIT") == "T");
1571  } else {
1572  inherit = metadata->get<bool>("INHERIT");
1573  }
1574  if (strip) metadata->remove("INHERIT");
1575  if (inherit) {
1576  HduMoveGuard guard(fitsfile, 0);
1577  // Combine the metadata from the primary HDU with the metadata from the specified HDU,
1578  // with non-comment values from the specified HDU superseding those in the primary HDU
1579  // and comments from the specified HDU appended to comments from the primary HDU
1580  auto primaryHduMetadata = std::make_shared<daf::base::PropertyList>();
1581  fitsfile.readMetadata(*primaryHduMetadata, strip);
1582  metadata = combineMetadata(primaryHduMetadata, metadata);
1583  } else {
1584  // Purge invalid values
1585  auto const emptyMetadata = std::make_shared<lsst::daf::base::PropertyList>();
1586  metadata = combineMetadata(metadata, emptyMetadata);
1587  }
1588  }
1589  return metadata;
1590 }
1591 
1592 
1593 HduMoveGuard::HduMoveGuard(Fits & fits, int hdu, bool relative) :
1594  _fits(fits),
1595  _oldHdu(_fits.getHdu()),
1596  _enabled(true)
1597 {
1598  _fits.setHdu(hdu, relative);
1599 }
1600 
1602  if (!_enabled) {
1603  return;
1604  }
1605  int status = 0;
1606  std::swap(status, _fits.status); // unset error indicator, but remember the old status
1607  try {
1608  _fits.setHdu(_oldHdu);
1609  } catch (...) {
1610  LOGL_WARN(
1611  "afw.fits",
1612  makeErrorMessage(_fits.fptr, _fits.status, "Failed to move back to HDU %d").c_str(),
1613  _oldHdu
1614  );
1615  }
1616  std::swap(status, _fits.status); // reset the old status
1617 }
1618 
1620  auto fits = reinterpret_cast<fitsfile *>(fptr);
1621  if (getHdu() != 0 || countHdus() == 1) {
1622  return false; // Can't possibly be the PHU leading a compressed image
1623  }
1624  // Check NAXIS = 0
1625  int naxis;
1626  fits_get_img_dim(fits, &naxis, &status);
1627  if (behavior & AUTO_CHECK) {
1628  LSST_FITS_CHECK_STATUS(*this, "Checking NAXIS of PHU");
1629  }
1630  if (naxis != 0) {
1631  return false;
1632  }
1633  // Check first extension (and move back there when we're done if we're not compressed)
1634  HduMoveGuard move(*this, 1);
1635  bool isCompressed = fits_is_compressed_image(fits, &status);
1636  if (behavior & AUTO_CHECK) {
1637  LSST_FITS_CHECK_STATUS(*this, "Checking compression");
1638  }
1639  if (isCompressed) {
1640  move.disable();
1641  }
1642  return isCompressed;
1643 }
1644 
1646  : compression(fits::compressionAlgorithmFromString(config.get<std::string>("compression.algorithm")),
1647  std::vector<long>{config.getAsInt64("compression.columns"),
1648  config.getAsInt64("compression.rows")},
1649  config.getAsDouble("compression.quantizeLevel")),
1650  scaling(fits::scalingAlgorithmFromString(config.get<std::string>("scaling.algorithm")),
1651  config.getAsInt("scaling.bitpix"),
1652  config.exists("scaling.maskPlanes") ? config.getArray<std::string>("scaling.maskPlanes")
1654  config.getAsInt("scaling.seed"), config.getAsDouble("scaling.quantizeLevel"),
1655  config.getAsDouble("scaling.quantizePad"), config.get<bool>("scaling.fuzz"),
1656  config.getAsDouble("scaling.bscale"), config.getAsDouble("scaling.bzero")) {}
1657 
1658 namespace {
1659 
1660 template <typename T>
1661 void validateEntry(daf::base::PropertySet &output, daf::base::PropertySet const &input,
1662  std::string const &name, T defaultValue) {
1663  output.add(name, input.get<T>(name, defaultValue));
1664 }
1665 
1666 template <typename T>
1667 void validateEntry(daf::base::PropertySet &output, daf::base::PropertySet const &input,
1668  std::string const &name, std::vector<T> defaultValue) {
1669  output.add(name, input.exists(name) ? input.getArray<T>(name) : defaultValue);
1670 }
1671 
1672 } // anonymous namespace
1673 
1675  auto validated = std::make_shared<daf::base::PropertySet>();
1676 
1677  validateEntry(*validated, config, "compression.algorithm", std::string("NONE"));
1678  validateEntry(*validated, config, "compression.columns", 0);
1679  validateEntry(*validated, config, "compression.rows", 1);
1680  validateEntry(*validated, config, "compression.quantizeLevel", 0.0);
1681 
1682  validateEntry(*validated, config, "scaling.algorithm", std::string("NONE"));
1683  validateEntry(*validated, config, "scaling.bitpix", 0);
1684  validateEntry(*validated, config, "scaling.maskPlanes", std::vector<std::string>{"NO_DATA"});
1685  validateEntry(*validated, config, "scaling.seed", 1);
1686  validateEntry(*validated, config, "scaling.quantizeLevel", 5.0);
1687  validateEntry(*validated, config, "scaling.quantizePad", 10.0);
1688  validateEntry(*validated, config, "scaling.fuzz", true);
1689  validateEntry(*validated, config, "scaling.bscale", 1.0);
1690  validateEntry(*validated, config, "scaling.bzero", 0.0);
1691 
1692  // Check for additional entries that we don't support (e.g., from typos)
1693  for (auto const &name : config.names(false)) {
1694  if (!validated->exists(name)) {
1696  os << "Invalid image write option: " << name;
1698  }
1699  }
1700 
1701  return validated;
1702 }
1703 
1704 #define INSTANTIATE_KEY_OPS(r, data, T) \
1705  template void Fits::updateKey(std::string const &, T const &, std::string const &); \
1706  template void Fits::writeKey(std::string const &, T const &, std::string const &); \
1707  template void Fits::updateKey(std::string const &, T const &); \
1708  template void Fits::writeKey(std::string const &, T const &); \
1709  template void Fits::updateColumnKey(std::string const &, int, T const &, std::string const &); \
1710  template void Fits::writeColumnKey(std::string const &, int, T const &, std::string const &); \
1711  template void Fits::updateColumnKey(std::string const &, int, T const &); \
1712  template void Fits::writeColumnKey(std::string const &, int, T const &); \
1713  template void Fits::readKey(std::string const &, T &);
1714 
1715 #define INSTANTIATE_IMAGE_OPS(r, data, T) \
1716  template void Fits::writeImageImpl(T const *, int); \
1717  template void Fits::writeImage(image::ImageBase<T> const &, ImageWriteOptions const &, \
1718  std::shared_ptr<daf::base::PropertySet const>, \
1719  std::shared_ptr<image::Mask<image::MaskPixel> const>); \
1720  template void Fits::readImageImpl(int, T *, long *, long *, long *); \
1721  template bool Fits::checkImageType<T>(); \
1722  template int getBitPix<T>();
1723 
1724 #define INSTANTIATE_TABLE_OPS(r, data, T) \
1725  template int Fits::addColumn<T>(std::string const &ttype, int size); \
1726  template int Fits::addColumn<T>(std::string const &ttype, int size, std::string const &comment);
1727 #define INSTANTIATE_TABLE_ARRAY_OPS(r, data, T) \
1728  template void Fits::writeTableArray(std::size_t row, int col, int nElements, T const *value); \
1729  template void Fits::readTableArray(std::size_t row, int col, int nElements, T *value);
1730 
1731 // ----------------------------------------------------------------------------------------------------------
1732 // ---- Explicit instantiation ------------------------------------------------------------------------------
1733 // ----------------------------------------------------------------------------------------------------------
1734 
1735 #define KEY_TYPES \
1736  (bool)(unsigned char)(short)(unsigned short)(int)(unsigned int)(long)(unsigned long)(LONGLONG)( \
1737  float)(double)(std::complex<float>)(std::complex<double>)(std::string)
1738 
1739 #define COLUMN_TYPES \
1740  (bool)(std::string)(std::int8_t)(std::uint8_t)(std::int16_t)(std::uint16_t)(std::int32_t)(std::uint32_t) \
1741  (std::int64_t)(float)(double)(lsst::geom::Angle)(std::complex<float>)(std::complex<double>)
1742 
1743 #define COLUMN_ARRAY_TYPES \
1744  (bool)(char)(std::uint8_t)(std::int16_t)(std::uint16_t)(std::int32_t)(std::uint32_t)(std::int64_t)( \
1745  float)(double)(lsst::geom::Angle)(std::complex<float>)(std::complex<double>)
1746 
1747 #define IMAGE_TYPES \
1748  (unsigned char)(short)(unsigned short)(int)(unsigned int)(std::int64_t)(std::uint64_t)(float)(double)
1749 
1754 } // namespace fits
1755 } // namespace afw
1756 } // namespace lsst
#define LOGLS_WARN(logger, message)
Log a warn-level message using an iostream-based interface.
Definition: Log.h:633
int col
Definition: CR.cc:144
int getImageDim()
Return the number of dimensions in the current HDU.
Definition: fits.cc:1315
std::size_t countRows()
Return the number of row in a table.
Definition: fits.cc:1052
#define INSTANTIATE_TABLE_OPS(r, data, T)
Definition: fits.cc:1724
std::shared_ptr< daf::base::PropertyList > combineMetadata(std::shared_ptr< const daf::base::PropertyList > first, std::shared_ptr< const daf::base::PropertyList > second)
Combine two sets of metadata in a FITS-appropriate fashion.
Definition: fits.cc:1521
std::string getFileName() const
Return the file name associated with the FITS object or "<unknown>" if there is none.
Definition: fits.cc:477
void forEachKey(HeaderIterationFunctor &functor)
Call a polymorphic functor for every key in the header.
Definition: fits.cc:754
T empty(T... args)
char * data
Definition: BaseTable.cc:205
ImageCompressionOptions getImageCompression()
Return the current image compression settings.
Definition: fits.cc:1388
ImageCompressionOptions compression
Options controlling compression.
Definition: fits.h:219
T find_first_not_of(T... args)
void writeMetadata(daf::base::PropertySet const &metadata)
Read a FITS header into a PropertySet or PropertyList.
Definition: fits.cc:988
#define LSST_FITS_CHECK_STATUS(fitsObj,...)
Throw a FitsError exception if the status of the given Fits object is nonzero.
Definition: fits.h:110
std::vector< T > getArray(std::string const &name) const
Get the vector of values for a property name (possibly hierarchical).
int compressionAlgorithmToCfitsio(ImageCompressionOptions::CompressionAlgorithm algorithm)
Convert ImageCompressionOptions::CompressionAlgorithm to cfitsio.
T swap(T... args)
T front(T... args)
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
T get(std::string const &name) const
Get the last value for a property name (possibly hierarchical).
long blank
Value for integer images indicating non-finite values.
void readKey(std::string const &key, T &value)
Read a FITS header key into the given reference.
Definition: fits.cc:747
int bitpix
Bits per pixel; negative means floating-point: 8,16,32,64,-32,-64.
void updateColumnKey(std::string const &prefix, int n, T const &value, std::string const &comment)
Update a key of the form XXXXXnnn, where XXXXX is the prefix and nnn is a column number.
Definition: fits.cc:664
Options for writing an image to FITS.
Definition: fits.h:218
std::string const & getComment(std::string const &name) const
Get the comment for a string property name (possibly hierarchical).
Definition: PropertyList.cc:77
def scale(algorithm, min, max=None, frame=None)
Definition: ds9.py:109
T to_string(T... args)
int getBitPix()
Return the cfitsio integer BITPIX code for the given data type.
Definition: fits.cc:469
py::object result
Definition: schema.cc:284
void readTableScalar(std::size_t row, int col, T &value)
Read an array scalar from a binary table.
Definition: fits.h:594
bool checkImageType()
Return true if the current HDU is compatible with the given pixel type.
Definition: fits.cc:1328
STL namespace.
ImageT val
Definition: CR.cc:146
STL class.
void writeColumnKey(std::string const &prefix, int n, T const &value, std::string const &comment)
Write a key of the form XXXXXnnn, where XXXXX is the prefix and nnn is a column number.
Definition: fits.cc:672
T end(T... args)
#define INSTANTIATE_TABLE_ARRAY_OPS(r, data, T)
Definition: fits.cc:1727
tuple options
Definition: lsstimport.py:47
std::vector< std::string > paramNames(bool topLevelOnly=true) const
A variant of names that excludes the names of subproperties.
std::string prefix
Definition: SchemaMapper.cc:79
CompressionAlgorithm
Compression algorithms.
T free(T... args)
A simple struct that combines the two arguments that must be passed to most cfitsio routines and cont...
Definition: fits.h:296
The base class for all image classed (Image, Mask, MaskedImage, ...)
Definition: ImageBase.h:103
long getTableArraySize(int col)
Return the size of an array column.
Definition: fits.cc:1110
FITS BITPIX header value by C++ type.
void closeFile()
Close a FITS file.
Definition: fits.cc:1516
A class representing an angle.
Definition: Angle.h:127
int seed
Seed for random number generator when fuzzing.
Fits * fits
Definition: FitsWriter.cc:90
void createEmpty()
Create an empty image HDU with NAXIS=0 at the end of the file.
Definition: fits.cc:1133
std::string getImageDType()
Return the numpy dtype equivalent of the image pixel type (e.g.
Definition: fits.cc:1359
void updateKey(std::string const &key, T const &value, std::string const &comment)
Set a FITS header key, editing if it already exists and appending it if not.
Definition: fits.cc:632
STL class.
LSST DM logging module built on log4cxx.
#define KEY_TYPES
Definition: fits.cc:1735
ndarray::Array< T const, N, N > const makeContiguousArray(ndarray::Array< T, N, C > const &array)
Construct a contiguous ndarray.
Definition: fits.h:207
An exception thrown when problems are found when reading or writing FITS files.
Definition: fits.h:35
Tiles tiles
Tile size; a dimension with 0 means infinite (e.g., to specify one row: 0,1)
T push_back(T... args)
Options for tile compression of image pixels.
A base class for image defects.
table::Key< int > type
Definition: Detector.cc:164
lsst::geom::Box2I getBBox(ImageOrigin origin=PARENT) const
Definition: ImageBase.h:463
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:78
int64_t getAsInt64(std::string const &name) const
Get the last value for a bool/char/short/int/int64_t property name (possibly hierarchical).
Lifetime-management for memory that goes into FITS memory files.
Definition: fits.h:120
double bzero
Zero-point to apply when reading from FITS.
std::size_t addRows(std::size_t nRows)
Append rows to a table, and return the index of the first new row.
Definition: fits.cc:1042
bool isValid
Definition: fits.cc:378
std::shared_ptr< daf::base::PropertyList > createTrivialWcsMetadata(std::string const &wcsName, lsst::geom::Point2I const &xy0)
Definition: wcsUtils.cc:51
ImageCompressionOptions::CompressionAlgorithm compressionAlgorithmFromString(std::string const &name)
Interpret compression algorithm expressed in string.
int getArea() const
Definition: Box.h:176
Fits()
Default constructor; set all data members to 0.
Definition: fits.h:608
T erase(T... args)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
T str(T... args)
T isfinite(T... args)
bool exists(std::string const &name) const
Determine if a name (possibly hierarchical) exists.
#define IMAGE_TYPES
Definition: fits.cc:1747
ndarray::Array< long, 1, 1 > Tiles
void createTable()
Create a new binary table extension.
Definition: fits.cc:1010
#define LOGL_WARN(logger, message...)
Log a warn-level message using a varargs/printf style interface.
Definition: Log.h:521
double bscale
Scale to apply when reading from FITS.
#define COLUMN_TYPES
Definition: fits.cc:1739
T infinity(T... args)
solver_t * s
void setHdu(int hdu, bool relative=false)
Set the current HDU.
Definition: fits.cc:492
T count(T... args)
T find_last_not_of(T... args)
#define INSTANTIATE_IMAGE_OPS(r, data, T)
Definition: fits.cc:1715
BOOST_PP_SEQ_FOR_EACH(INSTANTIATE_COLUMNVIEW_SCALAR, _, BOOST_PP_TUPLE_TO_SEQ(AFW_TABLE_SCALAR_FIELD_TYPE_N, AFW_TABLE_SCALAR_FIELD_TYPE_TUPLE)) BOOST_PP_SEQ_FOR_EACH(INSTANTIATE_COLUMNVIEW_ARRAY
lsst::geom::Point2I getXY0() const
Return the image&#39;s origin.
Definition: ImageBase.h:341
T find(T... args)
T size(T... args)
STL class.
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
afw::table::Key< afw::table::Array< MaskPixelT > > mask
T realloc(T... args)
T name(T... args)
#define INSTANTIATE_KEY_OPS(r, data, T)
Definition: fits.cc:1704
T malloc(T... args)
ImageScalingOptions scaling
Options controlling scaling.
Definition: fits.h:220
static std::shared_ptr< daf::base::PropertySet > validate(daf::base::PropertySet const &config)
Validate a PropertySet.
Definition: fits.cc:1674
void disable()
Disable the guard, leaving the HDU at its current state at destruction.
Definition: fits.h:750
Key< U > key
Definition: Schema.cc:281
bool checkCompressedImagePhu()
Go to the first image header in the FITS file.
Definition: fits.cc:1619
void setAllowImageCompression(bool allow)
Definition: fits.cc:1439
Class for storing generic metadata.
Definition: PropertySet.h:68
T c_str(T... args)
float quantizeLevel
quantization level: 0.0 = none requires use of GZIP or GZIP_SHUFFLE
void writeTableArray(std::size_t row, int col, int nElements, T const *value)
Write an array value to a binary table.
Definition: fits.cc:1062
void reset()
Return the manager to the same state it would be if default-constructed.
Definition: fits.cc:454
CompressionAlgorithm algorithm
Compresion algorithm to use.
void readTableArray(std::size_t row, int col, int nElements, T *value)
Read an array value from a binary table.
Definition: fits.cc:1085
T isnan(T... args)
T substr(T... args)
ImageCompressionOptions::CompressionAlgorithm compressionAlgorithmFromCfitsio(int cfitsio)
Convert compression algorithm from cfitsio to ImageCompressionOptions::CompressionAlgorithm.
ImageScale determine(image::ImageBase< T > const &image, std::shared_ptr< image::Mask< image::MaskPixel > const > mask=nullptr) const
Determine the scaling for a particular image.
void setImageCompression(ImageCompressionOptions const &options)
Set compression options for writing FITS images.
Definition: fits.cc:1411
lsst::geom::Angle Angle
Definition: misc.h:33
#define COLUMN_ARRAY_TYPES
Definition: fits.cc:1743
T quiet_NaN(T... args)
void writeKey(std::string const &key, T const &value, std::string const &comment)
Add a FITS header key to the bottom of the header.
Definition: fits.cc:640
bool fuzz
Fuzz the values when quantising floating-point values?
std::vector< std::string > names(bool topLevelOnly=true) const
Get the names in the PropertySet, optionally including those in subproperties.
bool getAllowImageCompression()
Definition: fits.cc:1441
int getHdu()
Return the current HDU (0-indexed; 0 is the Primary HDU).
Definition: fits.cc:486
void add(std::string const &name, T const &value)
Append a single value to the vector of values for a property name (possibly hierarchical).
void writeImage(ndarray::Array< T const, N, C > const &array)
Write an ndarray::Array to a FITS image HDU.
Definition: fits.h:476
std::vector< std::string > getOrderedNames() const
Get the list of property names, in the order they were added.
Definition: PropertyList.cc:81
int addColumn(std::string const &ttype, int size, std::string const &comment)
Add a column to a table.
Definition: fits.cc:1033
daf::base::PropertyList * list
Definition: fits.cc:833
std::string makeErrorMessage(std::string const &fileName="", int status=0, std::string const &msg="")
Return an error message reflecting FITS I/O errors.
Definition: fits.cc:405
RAII scoped guard for moving the HDU in a Fits object.
Definition: fits.h:723
ImageScalingOptions::ScalingAlgorithm scalingAlgorithmFromString(std::string const &name)
Interpret scaling algorithm expressed in string.
std::string const wcsNameForXY0
Definition: ImageBase.h:71
Base class for polymorphic functors used to iterator over FITS key headers.
Definition: fits.h:48
int end
bool strip
Definition: fits.cc:831
void writeTableScalar(std::size_t row, int col, T value)
Write a scalar value to a binary table.
Definition: fits.h:582
void readMetadata(daf::base::PropertySet &metadata, bool strip=false)
Read a FITS header into a PropertySet or PropertyList.
Definition: fits.cc:980
std::string makeLimitedFitsHeader(lsst::daf::base::PropertySet const &metadata, std::set< std::string > const &excludeNames={})
Format a PropertySet into an FITS header string in a simplistic fashion.
Definition: fits.cc:436
T compare(T... args)
std::ostream * os
Definition: Schema.cc:746
bool isComment
Definition: fits.cc:377
T reserve(T... args)
Reports errors that are due to events beyond the control of the program.
Definition: Runtime.h:104
const int DEFAULT_HDU
Specify that the default HDU should be read.
Definition: fitsDefaults.h:18
std::shared_ptr< detail::PixelArrayBase > toFits(ndarray::Array< T const, 2, 2 > const &image, bool forceNonfiniteRemoval, bool fuzz=true, ndarray::Array< long, 1 > const &tiles=ndarray::Array< long, 1, 1 >(), int seed=1) const
Convert to an array of pixel values to write to FITS.
int row
Definition: CR.cc:145
ImageWriteOptions(image::Image< T > const &image)
Construct with default options for images.
Definition: fits.h:224
int countHdus()
Return the number of HDUs in the file.
Definition: fits.cc:513
Scale to apply to image.