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