LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
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) {
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
541void Fits::setHdu(std::string const &name, HduType hdutype, int hduver) {
542 fits_movnam_hdu(reinterpret_cast<fitsfile *>(fptr), static_cast<int>(hdutype),
543 const_cast<char *>(name.c_str()), hduver, &status);
544 if (behavior & AUTO_CHECK)
545 LSST_FITS_CHECK_STATUS(*this, boost::format("Moving to named HDU %s, type %d, hduver %d") % name %
546 static_cast<int>(hdutype) % hduver);
547}
548
550 int n = 0;
551 fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &n, &status);
552 if (behavior & AUTO_CHECK) {
553 LSST_FITS_CHECK_STATUS(*this, "Getting number of HDUs in file.");
554 }
555 return n;
556}
557
558// ---- Writing and updating header keys --------------------------------------------------------------------
559
560namespace {
561
562// Impl functions in the anonymous namespace do special handling for strings, bools, and IEEE fp values.
563
570std::string nonFiniteDoubleToString(double value) {
571 if (std::isfinite(value)) {
572 return "";
573 }
574 if (std::isnan(value)) {
575 return "NAN";
576 }
577 if (value < 0) {
578 return "-INFINITY";
579 }
580 return "+INFINITY";
581}
582
588double stringToNonFiniteDouble(std::string const &value) {
589 if (value == "NAN") {
591 }
592 if (value == "+INFINITY") {
594 }
595 if (value == "-INFINITY") {
597 }
598 return 0;
599}
600
601template <typename T>
602void updateKeyImpl(Fits &fits, char const *key, T const &value, char const *comment) {
603 fits_update_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<T>::CONSTANT, const_cast<char *>(key),
604 const_cast<T *>(&value), const_cast<char *>(comment), &fits.status);
605}
606
607void updateKeyImpl(Fits &fits, char const *key, std::string const &value, char const *comment) {
608 fits_update_key_longstr(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key),
609 const_cast<char *>(value.c_str()), const_cast<char *>(comment), &fits.status);
610}
611
612void updateKeyImpl(Fits &fits, char const *key, bool const &value, char const *comment) {
613 int v = value;
614 fits_update_key(reinterpret_cast<fitsfile *>(fits.fptr), TLOGICAL, const_cast<char *>(key), &v,
615 const_cast<char *>(comment), &fits.status);
616}
617
618void updateKeyImpl(Fits &fits, char const *key, double const &value, char const *comment) {
619 std::string strValue = nonFiniteDoubleToString(value);
620 if (!strValue.empty()) {
621 updateKeyImpl(fits, key, strValue, comment);
622 } else {
623 fits_update_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
629template <typename T>
630void writeKeyImpl(Fits &fits, char const *key, T const &value, char const *comment) {
631 fits_write_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<T>::CONSTANT, const_cast<char *>(key),
632 const_cast<T *>(&value), const_cast<char *>(comment), &fits.status);
633}
634
635void writeKeyImpl(Fits &fits, char const *key, char const *comment) {
636 // Write a key with an undefined value
637 fits_write_key_null(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key),
638 const_cast<char *>(comment), &fits.status);
639}
640
641void writeKeyImpl(Fits &fits, char const *key, std::string const &value, char const *comment) {
642 if (strncmp(key, "COMMENT", 7) == 0) {
643 fits_write_comment(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(value.c_str()),
644 &fits.status);
645 } else if (strncmp(key, "HISTORY", 7) == 0) {
646 fits_write_history(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(value.c_str()),
647 &fits.status);
648 } else {
649 fits_write_key_longstr(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key),
650 const_cast<char *>(value.c_str()), const_cast<char *>(comment), &fits.status);
651 }
652}
653
654void writeKeyImpl(Fits &fits, char const *key, bool const &value, char const *comment) {
655 int v = value;
656 fits_write_key(reinterpret_cast<fitsfile *>(fits.fptr), TLOGICAL, const_cast<char *>(key), &v,
657 const_cast<char *>(comment), &fits.status);
658}
659
660void writeKeyImpl(Fits &fits, char const *key, double const &value, char const *comment) {
661 std::string strValue = nonFiniteDoubleToString(value);
662 if (!strValue.empty()) {
663 writeKeyImpl(fits, key, strValue, comment);
664 } else {
665 fits_write_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<double>::CONSTANT,
666 const_cast<char *>(key), const_cast<double *>(&value), const_cast<char *>(comment),
667 &fits.status);
668 }
669}
670
671} // namespace
672
673template <typename T>
674void Fits::updateKey(std::string const &key, T const &value, std::string const &comment) {
675 updateKeyImpl(*this, key.c_str(), value, comment.c_str());
676 if (behavior & AUTO_CHECK) {
677 LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s': '%s'") % key % value);
678 }
679}
680
681template <typename T>
682void Fits::writeKey(std::string const &key, T const &value, std::string const &comment) {
683 writeKeyImpl(*this, key.c_str(), value, comment.c_str());
684 if (behavior & AUTO_CHECK) {
685 LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s': '%s'") % key % value);
686 }
687}
688
689template <typename T>
690void Fits::updateKey(std::string const &key, T const &value) {
691 updateKeyImpl(*this, key.c_str(), value, nullptr);
692 if (behavior & AUTO_CHECK) {
693 LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s': '%s'") % key % value);
694 }
695}
696
697template <typename T>
698void Fits::writeKey(std::string const &key, T const &value) {
699 writeKeyImpl(*this, key.c_str(), value, nullptr);
700 if (behavior & AUTO_CHECK) {
701 LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s': '%s'") % key % value);
702 }
703}
704
705template <typename T>
706void Fits::updateColumnKey(std::string const &prefix, int n, T const &value, std::string const &comment) {
707 updateKey((boost::format("%s%d") % prefix % (n + 1)).str(), value, comment);
708 if (behavior & AUTO_CHECK) {
709 LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s%d': '%s'") % prefix % (n + 1) % value);
710 }
711}
712
713template <typename T>
714void Fits::writeColumnKey(std::string const &prefix, int n, T const &value, std::string const &comment) {
715 writeKey((boost::format("%s%d") % prefix % (n + 1)).str(), value, comment);
716 if (behavior & AUTO_CHECK) {
717 LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s%d': '%s'") % prefix % (n + 1) % value);
718 }
719}
720
721template <typename T>
722void Fits::updateColumnKey(std::string const &prefix, int n, T const &value) {
723 updateKey((boost::format("%s%d") % prefix % (n + 1)).str(), value);
724 if (behavior & AUTO_CHECK) {
725 LSST_FITS_CHECK_STATUS(*this, boost::format("Updating key '%s%d': '%s'") % prefix % (n + 1) % value);
726 }
727}
728
729template <typename T>
730void Fits::writeColumnKey(std::string const &prefix, int n, T const &value) {
731 writeKey((boost::format("%s%d") % prefix % (n + 1)).str(), value);
732 if (behavior & AUTO_CHECK) {
733 LSST_FITS_CHECK_STATUS(*this, boost::format("Writing key '%s%d': '%s'") % prefix % (n + 1) % value);
734 }
735}
736
737// ---- Reading header keys ---------------------------------------------------------------------------------
738
739namespace {
740
741template <typename T>
742void readKeyImpl(Fits &fits, char const *key, T &value) {
743 fits_read_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<T>::CONSTANT, const_cast<char *>(key),
744 &value, nullptr, &fits.status);
745}
746
747void readKeyImpl(Fits &fits, char const *key, std::string &value) {
748 char *buf = nullptr;
749 fits_read_key_longstr(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key), &buf, nullptr,
750 &fits.status);
751 if (buf) {
752 value = strip(buf);
753 free(buf);
754 }
755}
756
757void readKeyImpl(Fits &fits, char const *key, double &value) {
758 // We need to check for the possibility that the value is a special string (for NAN, +/-Inf).
759 // If a quote mark (') is present then it's a string.
760
761 char buf[FLEN_VALUE];
762 fits_read_keyword(reinterpret_cast<fitsfile *>(fits.fptr), const_cast<char *>(key), buf, nullptr, &fits.status);
763 if (fits.status != 0) {
764 return;
765 }
766 if (std::string(buf).find('\'') != std::string::npos) {
767 std::string unquoted;
768 readKeyImpl(fits, key, unquoted); // Let someone else remove quotes and whitespace
769 if (fits.status != 0) {
770 return;
771 }
772 value = stringToNonFiniteDouble(unquoted);
773 if (value == 0) {
774 throw LSST_EXCEPT(
775 afw::fits::FitsError,
776 (boost::format("Unrecognised string value for keyword '%s' when parsing as double: %s") %
777 key % unquoted)
778 .str());
779 }
780 } else {
781 fits_read_key(reinterpret_cast<fitsfile *>(fits.fptr), FitsType<double>::CONSTANT,
782 const_cast<char *>(key), &value, nullptr, &fits.status);
783 }
784}
785
786} // namespace
787
788template <typename T>
789void Fits::readKey(std::string const &key, T &value) {
790 readKeyImpl(*this, key.c_str(), value);
791 if (behavior & AUTO_CHECK) {
792 LSST_FITS_CHECK_STATUS(*this, boost::format("Reading key '%s'") % key);
793 }
794}
795
797 char key[81]; // allow for terminating NUL
798 char value[81];
799 char comment[81];
800 int nKeys = 0;
801 fits_get_hdrspace(reinterpret_cast<fitsfile *>(fptr), &nKeys, nullptr, &status);
802 std::string keyStr;
803 std::string valueStr;
804 std::string commentStr;
805 int i = 1;
806 while (i <= nKeys) {
807 fits_read_keyn(reinterpret_cast<fitsfile *>(fptr), i, key, value, comment, &status);
808 // fits_read_keyn does not convert the key case on read, like other fits methods in cfitsio>=3.38
809 // We uppercase to try to be more consistent.
810 std::string upperKey(key);
811 boost::to_upper(upperKey);
812 if (upperKey.compare(key) != 0){
813 LOGLS_DEBUG("lsst.afw.fits",
814 boost::format("In %s, standardizing key '%s' to uppercase '%s' on read.") %
815 BOOST_CURRENT_FUNCTION % key % upperKey);
816 }
817 keyStr = upperKey;
818 valueStr = value;
819 commentStr = comment;
820 ++i;
821 while (valueStr.size() > 2 && valueStr[valueStr.size() - 2] == '&' && i <= nKeys) {
822 // we're using key to hold the entire record here; the actual key is safe in keyStr
823 fits_read_record(reinterpret_cast<fitsfile *>(fptr), i, key, &status);
824 if (strncmp(key, "CONTINUE", 8) != 0) {
825 // require both trailing '&' and CONTINUE to invoke long-string handling
826 break;
827 }
828 std::string card = key;
829 valueStr.erase(valueStr.size() - 2);
830 std::size_t firstQuote = card.find('\'');
831 if (firstQuote == 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 std::size_t lastQuote = card.find('\'', firstQuote + 1);
839 if (lastQuote == std::string::npos) {
840 throw LSST_EXCEPT(
841 FitsError,
843 fptr, status,
844 boost::format("Invalid CONTINUE at header key %d: \"%s\".") % i % card));
845 }
846 valueStr += card.substr(firstQuote + 1, lastQuote - firstQuote);
847 std::size_t slash = card.find('/', lastQuote + 1);
848 if (slash != std::string::npos) {
849 commentStr += strip(card.substr(slash + 1));
850 }
851 ++i;
852 }
853 if (behavior & AUTO_CHECK) {
854 LSST_FITS_CHECK_STATUS(*this, boost::format("Reading key '%s'") % keyStr);
855 }
856 functor(keyStr, valueStr, commentStr);
857 }
858}
859
860// ---- Reading and writing PropertySet/PropertyList --------------------------------------------------------
861
862namespace {
863
864bool isKeyIgnored(std::string const &key, bool write = false) {
865 return ((ignoreKeys.find(key) != ignoreKeys.end()) || ignoreKeyStarts.matches(key) ||
866 (write && ignoreKeyStartsWrite.matches(key)));
867}
868
869class MetadataIterationFunctor : public HeaderIterationFunctor {
870public:
871 void operator()(std::string const &key, std::string const &value, std::string const &comment) override;
872
873 template <typename T>
874 void add(std::string const &key, T value, std::string const &comment) {
875 // PropertyList/Set can not support array items where some elements are
876 // defined and some undefined. If we are adding defined value where
877 // previously we have an undefined value we must use set instead.
878 if (list) {
879 if (list->exists(key) && list->isUndefined(key)) {
880 LOGLS_WARN("lsst.afw.fits",
881 boost::format("In %s, replacing undefined value for key '%s'.") %
882 BOOST_CURRENT_FUNCTION % key);
883 list->set(key, value, comment);
884 } else {
885 list->add(key, value, comment);
886 }
887 } else {
888 if (set->exists(key) && set->isUndefined(key)) {
889 LOGLS_WARN("lsst.afw.fits",
890 boost::format("In %s, replacing undefined value for key '%s'.") %
891 BOOST_CURRENT_FUNCTION % key);
892 set->set(key, value);
893 } else {
894 set->add(key, value);
895 }
896 }
897 }
898
899 void add(std::string const &key, std::string const &comment) {
900 // If this undefined value is adding to a pre-existing key that has
901 // a defined value we must skip the add so as not to break
902 // PropertyList/Set.
903 if (list) {
904 if (list->exists(key) && !list->isUndefined(key)) {
905 // Do nothing. Assume the previously defined value takes
906 // precedence.
907 LOGLS_WARN("lsst.afw.fits",
908 boost::format("In %s, dropping undefined value for key '%s'.") %
909 BOOST_CURRENT_FUNCTION % key);
910 } else {
911 list->add(key, nullptr, comment);
912 }
913 } else {
914 if (set->exists(key) && !set->isUndefined(key)) {
915 // Do nothing. Assume the previously defined value takes
916 // precedence.
917 LOGLS_WARN("lsst.afw.fits",
918 boost::format("In %s, dropping undefined value for key '%s'.") %
919 BOOST_CURRENT_FUNCTION % key);
920 } else {
921 set->add(key, nullptr);
922 }
923 }
924 }
925
926 bool strip;
927 daf::base::PropertySet *set;
928 daf::base::PropertyList *list;
929};
930
931void MetadataIterationFunctor::operator()(std::string const &key, std::string const &value,
932 std::string const &comment) {
933 static std::regex const boolRegex("[tTfF]");
934 static std::regex const intRegex("[+-]?[0-9]+");
935 static std::regex const doubleRegex("[+-]?([0-9]*\\.?[0-9]+|[0-9]+\\.?[0-9]*)([eE][+-]?[0-9]+)?");
936 static std::regex const fitsStringRegex("'(.*?) *'");
937 // regex for two-line comment added to all FITS headers by CFITSIO
938 static std::regex const fitsDefinitionCommentRegex(
939 " *(FITS \\(Flexible Image Transport System\\)|and Astrophysics', volume 376, page 359).*");
940 std::smatch matchStrings;
941
942 if (strip && isKeyIgnored(key)) {
943 return;
944 }
945
946 std::istringstream converter(value);
947 if (std::regex_match(value, boolRegex)) {
948 // convert the string to an bool
949 add(key, bool(value == "T" || value == "t"), comment);
950 } else if (std::regex_match(value, intRegex)) {
951 // convert the string to an int
953 converter >> val;
954 if (val < (1LL << 31) && val > -(1LL << 31)) {
955 add(key, static_cast<int>(val), comment);
956 } else {
957 add(key, val, comment);
958 }
959 } else if (std::regex_match(value, doubleRegex)) {
960 // convert the string to a double
961 double val;
962 converter >> val;
963 add(key, val, comment);
964 } else if (std::regex_match(value, matchStrings, fitsStringRegex)) {
965 std::string const str = matchStrings[1].str(); // strip off the enclosing single quotes
966 double val = stringToNonFiniteDouble(str);
967 if (val != 0.0) {
968 add(key, val, comment);
969 } else {
970 add(key, str, comment);
971 }
972 } else if (key == "HISTORY") {
973 add(key, comment, "");
974 } else if (key == "COMMENT" && !(strip && std::regex_match(comment, fitsDefinitionCommentRegex))) {
975 add(key, comment, "");
976 } else if (key.empty() && value.empty()) {
977 // This is a blank keyword comment. Since comments do not retain
978 // their position on read there is nothing to be gained by storing
979 // this in the PropertyList as a blank keyword. Therefore store
980 // them with the other comments.
981 add("COMMENT", comment, "");
982 } else if (value.empty()) {
983 // do nothing for empty values that are comments
984 // Otherwise write null value to PropertySet
985 if (key != "COMMENT") {
986 add(key, comment);
987 }
988 } else {
989 throw LSST_EXCEPT(
990 afw::fits::FitsError,
991 (boost::format("Could not parse header value for key '%s': '%s'") % key % value).str());
992 }
993}
994
995void writeKeyFromProperty(Fits &fits, daf::base::PropertySet const &metadata, std::string const &key,
996 char const *comment = nullptr) {
997 std::string upperKey(key);
998 boost::to_upper(upperKey);
999 if (upperKey.compare(key) != 0){
1000 LOGLS_WARN("lsst.afw.fits",
1001 boost::format("In %s, key '%s' may be standardized to uppercase '%s' on write.") %
1002 BOOST_CURRENT_FUNCTION % key % upperKey);
1003 }
1004 std::type_info const &valueType = metadata.typeOf(key);
1005 if (valueType == typeid(bool)) {
1006 if (metadata.isArray(key)) {
1007 std::vector<bool> tmp = metadata.getArray<bool>(key);
1008 // work around unfortunate specialness of std::vector<bool>
1009 for (std::size_t i = 0; i != tmp.size(); ++i) {
1010 writeKeyImpl(fits, key.c_str(), static_cast<bool>(tmp[i]), comment);
1011 }
1012 } else {
1013 writeKeyImpl(fits, key.c_str(), metadata.get<bool>(key), comment);
1014 }
1015 } else if (valueType == typeid(std::uint8_t)) {
1016 if (metadata.isArray(key)) {
1017 std::vector<std::uint8_t> tmp = metadata.getArray<std::uint8_t>(key);
1018 for (std::size_t i = 0; i != tmp.size(); ++i) {
1019 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1020 }
1021 } else {
1022 writeKeyImpl(fits, key.c_str(), metadata.get<std::uint8_t>(key), comment);
1023 }
1024 } else if (valueType == typeid(int)) {
1025 if (metadata.isArray(key)) {
1026 std::vector<int> tmp = metadata.getArray<int>(key);
1027 for (std::size_t i = 0; i != tmp.size(); ++i) {
1028 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1029 }
1030 } else {
1031 writeKeyImpl(fits, key.c_str(), metadata.get<int>(key), comment);
1032 }
1033 } else if (valueType == typeid(long)) {
1034 if (metadata.isArray(key)) {
1035 std::vector<long> tmp = metadata.getArray<long>(key);
1036 for (std::size_t i = 0; i != tmp.size(); ++i) {
1037 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1038 }
1039 } else {
1040 writeKeyImpl(fits, key.c_str(), metadata.get<long>(key), comment);
1041 }
1042 } else if (valueType == typeid(long long)) {
1043 if (metadata.isArray(key)) {
1044 std::vector<long long> tmp = metadata.getArray<long long>(key);
1045 for (std::size_t i = 0; i != tmp.size(); ++i) {
1046 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1047 }
1048 } else {
1049 writeKeyImpl(fits, key.c_str(), metadata.get<long long>(key), comment);
1050 }
1051 } else if (valueType == typeid(std::int64_t)) {
1052 if (metadata.isArray(key)) {
1053 std::vector<std::int64_t> tmp = metadata.getArray<std::int64_t>(key);
1054 for (std::size_t i = 0; i != tmp.size(); ++i) {
1055 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1056 }
1057 } else {
1058 writeKeyImpl(fits, key.c_str(), metadata.get<std::int64_t>(key), comment);
1059 }
1060 } else if (valueType == typeid(double)) {
1061 if (metadata.isArray(key)) {
1062 std::vector<double> tmp = metadata.getArray<double>(key);
1063 for (std::size_t i = 0; i != tmp.size(); ++i) {
1064 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1065 }
1066 } else {
1067 writeKeyImpl(fits, key.c_str(), metadata.get<double>(key), comment);
1068 }
1069 } else if (valueType == typeid(std::string)) {
1070 if (metadata.isArray(key)) {
1071 std::vector<std::string> tmp = metadata.getArray<std::string>(key);
1072 for (std::size_t i = 0; i != tmp.size(); ++i) {
1073 writeKeyImpl(fits, key.c_str(), tmp[i], comment);
1074 }
1075 } else {
1076 writeKeyImpl(fits, key.c_str(), metadata.get<std::string>(key), comment);
1077 }
1078 } else if (valueType == typeid(std::nullptr_t)) {
1079 if (metadata.isArray(key)) {
1080 // Write multiple undefined values for the same key
1081 auto tmp = metadata.getArray<std::nullptr_t>(key);
1082 for (std::size_t i = 0; i != tmp.size(); ++i) {
1083 writeKeyImpl(fits, key.c_str(), comment);
1084 }
1085 } else {
1086 writeKeyImpl(fits, key.c_str(), comment);
1087 }
1088 } else {
1089 // FIXME: inherited this error handling from fitsIo.cc; need a better option.
1090 LOGLS_WARN("lsst.afw.fits.writeKeyFromProperty",
1091 makeErrorMessage(fits.fptr, fits.status,
1092 boost::format("In %s, unknown type '%s' for key '%s'.") %
1093 BOOST_CURRENT_FUNCTION % valueType.name() % key));
1094 }
1095 if (fits.behavior & Fits::AUTO_CHECK) {
1096 LSST_FITS_CHECK_STATUS(fits, boost::format("Writing key '%s'") % key);
1097 }
1098}
1099
1100} // namespace
1101
1103 MetadataIterationFunctor f;
1104 f.strip = strip;
1105 f.set = &metadata;
1106 f.list = dynamic_cast<daf::base::PropertyList *>(&metadata);
1107 forEachKey(f);
1108}
1109
1111 using NameList = std::vector<std::string>;
1112 daf::base::PropertyList const *pl = dynamic_cast<daf::base::PropertyList const *>(&metadata);
1113 NameList paramNames;
1114 if (pl) {
1115 paramNames = pl->getOrderedNames();
1116 } else {
1117 paramNames = metadata.paramNames(false);
1118 }
1119 for (auto const &paramName : paramNames) {
1120 if (!isKeyIgnored(paramName, true)) {
1121 if (pl) {
1122 writeKeyFromProperty(*this, metadata, paramName, pl->getComment(paramName).c_str());
1123 } else {
1124 writeKeyFromProperty(*this, metadata, paramName);
1125 }
1126 }
1127 }
1128}
1129
1130// ---- Manipulating tables ---------------------------------------------------------------------------------
1131
1133 char *ttype = nullptr;
1134 char *tform = nullptr;
1135 fits_create_tbl(reinterpret_cast<fitsfile *>(fptr), BINARY_TBL, 0, 0, &ttype, &tform, nullptr, nullptr, &status);
1136 if (behavior & AUTO_CHECK) {
1137 LSST_FITS_CHECK_STATUS(*this, "Creating binary table");
1138 }
1139}
1140
1141template <typename T>
1142int Fits::addColumn(std::string const &ttype, int size) {
1143 int nCols = 0;
1144 fits_get_num_cols(reinterpret_cast<fitsfile *>(fptr), &nCols, &status);
1145 std::string tform = makeColumnFormat<T>(size);
1146 fits_insert_col(reinterpret_cast<fitsfile *>(fptr), nCols + 1, const_cast<char *>(ttype.c_str()),
1147 const_cast<char *>(tform.c_str()), &status);
1148 if (behavior & AUTO_CHECK) {
1149 LSST_FITS_CHECK_STATUS(*this, boost::format("Adding column '%s' with size %d") % ttype % size);
1150 }
1151 return nCols;
1152}
1153
1154template <typename T>
1155int Fits::addColumn(std::string const &ttype, int size, std::string const &comment) {
1156 int nCols = addColumn<T>(ttype, size);
1157 updateColumnKey("TTYPE", nCols, ttype, comment);
1158 if (behavior & AUTO_CHECK) {
1159 LSST_FITS_CHECK_STATUS(*this, boost::format("Adding column '%s' with size %d") % ttype % size);
1160 }
1161 return nCols;
1162}
1163
1165 long first = 0;
1166 fits_get_num_rows(reinterpret_cast<fitsfile *>(fptr), &first, &status);
1167 fits_insert_rows(reinterpret_cast<fitsfile *>(fptr), first, nRows, &status);
1168 if (behavior & AUTO_CHECK) {
1169 LSST_FITS_CHECK_STATUS(*this, boost::format("Adding %d rows to binary table") % nRows);
1170 }
1171 return first;
1172}
1173
1175 long r = 0;
1176 fits_get_num_rows(reinterpret_cast<fitsfile *>(fptr), &r, &status);
1177 if (behavior & AUTO_CHECK) {
1178 LSST_FITS_CHECK_STATUS(*this, "Checking how many rows are in table");
1179 }
1180 return r;
1181}
1182
1183template <typename T>
1184void Fits::writeTableArray(std::size_t row, int col, int nElements, T const *value) {
1185 fits_write_col(reinterpret_cast<fitsfile *>(fptr), FitsTableType<T>::CONSTANT, col + 1, row + 1, 1,
1186 nElements, const_cast<T *>(value), &status);
1187 if (behavior & AUTO_CHECK) {
1188 LSST_FITS_CHECK_STATUS(*this, boost::format("Writing %d-element array at table cell (%d, %d)") %
1189 nElements % row % col);
1190 }
1191}
1192
1194 // cfitsio doesn't let us specify the size of a string, it just looks for null terminator.
1195 // Using std::string::c_str() guarantees that we have one. But we can't store arbitrary
1196 // data in a string field because cfitsio will also chop off anything after the first null
1197 // terminator.
1198 char const *tmp = value.c_str();
1199 fits_write_col(reinterpret_cast<fitsfile *>(fptr), TSTRING, col + 1, row + 1, 1, 1,
1200 const_cast<char const **>(&tmp), &status);
1201 if (behavior & AUTO_CHECK) {
1202 LSST_FITS_CHECK_STATUS(*this, boost::format("Writing value at table cell (%d, %d)") % row % col);
1203 }
1204}
1205
1206template <typename T>
1207void Fits::readTableArray(std::size_t row, int col, int nElements, T *value) {
1208 int anynul = false;
1209 fits_read_col(reinterpret_cast<fitsfile *>(fptr), FitsTableType<T>::CONSTANT, col + 1, row + 1, 1,
1210 nElements, nullptr, value, &anynul, &status);
1211 if (behavior & AUTO_CHECK) {
1212 LSST_FITS_CHECK_STATUS(*this, boost::format("Reading value at table cell (%d, %d)") % row % col);
1213 }
1214}
1215
1216void Fits::readTableScalar(std::size_t row, int col, std::string &value, bool isVariableLength) {
1217 int anynul = false;
1218 long size = isVariableLength ? getTableArraySize(row, col) : getTableArraySize(col);
1219 // We can't directly write into a std::string until C++17.
1220 std::vector<char> buf(size + 1, 0);
1221 // cfitsio wants a char** because they imagine we might want an array of strings,
1222 // but we only want one element.
1223 char *tmp = &buf.front();
1224 fits_read_col(reinterpret_cast<fitsfile *>(fptr), TSTRING, col + 1, row + 1, 1, 1, nullptr, &tmp, &anynul,
1225 &status);
1226 if (behavior & AUTO_CHECK) {
1227 LSST_FITS_CHECK_STATUS(*this, boost::format("Reading value at table cell (%d, %d)") % row % col);
1228 }
1229 value = std::string(tmp);
1230}
1231
1233 int typecode = 0;
1234 long result = 0;
1235 long width = 0;
1236 fits_get_coltype(reinterpret_cast<fitsfile *>(fptr), col + 1, &typecode, &result, &width, &status);
1237 if (behavior & AUTO_CHECK) {
1238 LSST_FITS_CHECK_STATUS(*this, boost::format("Looking up array size for column %d") % col);
1239 }
1240 return result;
1241}
1242
1244 long result = 0;
1245 long offset = 0;
1246 fits_read_descript(reinterpret_cast<fitsfile *>(fptr), col + 1, row + 1, &result, &offset, &status);
1247 if (behavior & AUTO_CHECK) {
1248 LSST_FITS_CHECK_STATUS(*this, boost::format("Looking up array size for cell (%d, %d)") % row % col);
1249 }
1250 return result;
1251}
1252
1253// ---- Manipulating images ---------------------------------------------------------------------------------
1254
1256 long naxes = 0;
1257 fits_create_img(reinterpret_cast<fitsfile *>(fptr), 8, 0, &naxes, &status);
1258 if (behavior & AUTO_CHECK) {
1259 LSST_FITS_CHECK_STATUS(*this, "Creating empty image HDU");
1260 }
1261}
1262
1263void Fits::createImageImpl(int bitpix, int naxis, long const *naxes) {
1264 fits_create_img(reinterpret_cast<fitsfile *>(fptr), bitpix, naxis, const_cast<long *>(naxes), &status);
1265 if (behavior & AUTO_CHECK) {
1266 LSST_FITS_CHECK_STATUS(*this, "Creating new image HDU");
1267 }
1268}
1269
1270template <typename T>
1271void Fits::writeImageImpl(T const *data, int nElements) {
1272 fits_write_img(reinterpret_cast<fitsfile *>(fptr), FitsType<T>::CONSTANT, 1, nElements,
1273 const_cast<T *>(data), &status);
1274 if (behavior & AUTO_CHECK) {
1275 LSST_FITS_CHECK_STATUS(*this, "Writing image");
1276 }
1277}
1278
1279namespace {
1280
1285struct ImageCompressionContext {
1286public:
1287 ImageCompressionContext(Fits &fits_, ImageCompressionOptions const &useThis)
1288 : fits(fits_), old(fits.getImageCompression()) {
1289 fits.setImageCompression(useThis);
1290 }
1291 ~ImageCompressionContext() {
1292 int status = 0;
1293 std::swap(status, fits.status);
1294 try {
1295 fits.setImageCompression(old);
1296 } catch (...) {
1297 LOGLS_WARN("lsst.afw.fits",
1298 makeErrorMessage(fits.fptr, fits.status, "Failed to restore compression settings"));
1299 }
1300 std::swap(status, fits.status);
1301 }
1302
1303private:
1304 Fits &fits; // FITS file we're working on
1305 ImageCompressionOptions old; // Former compression options, to be restored
1306};
1307
1308} // anonymous namespace
1309
1310template <typename T>
1312 daf::base::PropertySet const * header,
1313 image::Mask<image::MaskPixel> const * mask) {
1314 auto fits = reinterpret_cast<fitsfile *>(fptr);
1315 ImageCompressionOptions const &compression =
1316 image.getBBox().getArea() > 0
1317 ? options.compression
1319 ImageCompressionOptions::NONE); // cfitsio can't compress empty images
1320 ImageCompressionContext comp(*this, compression); // RAII
1321 if (behavior & AUTO_CHECK) {
1322 LSST_FITS_CHECK_STATUS(*this, "Activating compression for write image");
1323 }
1324
1325 ImageScale scale = options.scaling.determine(image, mask);
1326
1327 // We need a place to put the image+header, and CFITSIO needs to know the dimenions.
1328 ndarray::Vector<long, 2> dims(image.getArray().getShape().reverse());
1329 createImageImpl(scale.bitpix == 0 ? detail::Bitpix<T>::value : scale.bitpix, 2, dims.elems);
1330
1331 // Write the header
1333 geom::createTrivialWcsMetadata(image::detail::wcsNameForXY0, image.getXY0());
1335 if (header) {
1336 fullMetadata = header->deepCopy();
1337 fullMetadata->combine(*wcsMetadata);
1338 } else {
1339 fullMetadata = wcsMetadata;
1340 }
1341 writeMetadata(*fullMetadata);
1342
1343 // Scale the image how we want it on disk
1344 ndarray::Array<T const, 2, 2> array = makeContiguousArray(image.getArray());
1345 auto pixels = scale.toFits(array, compression.quantizeLevel != 0, options.scaling.fuzz,
1346 options.compression.tiles, options.scaling.seed);
1347
1348 // We only want cfitsio to do the scale and zero for unsigned 64-bit integer types. For those,
1349 // "double bzero" has sufficient precision to represent the appropriate value. We'll let
1350 // cfitsio handle it itself.
1351 // In all other cases, we will convert the image to use the appropriate scale and zero
1352 // (because we want to fuzz the numbers in the quantisation), so we don't want cfitsio
1353 // rescaling.
1355 fits_set_bscale(fits, 1.0, 0.0, &status);
1356 if (behavior & AUTO_CHECK) {
1357 LSST_FITS_CHECK_STATUS(*this, "Setting bscale,bzero");
1358 }
1359 }
1360
1361 // Write the pixels
1362 int const fitsType = scale.bitpix == 0 ? FitsType<T>::CONSTANT : fitsTypeForBitpix(scale.bitpix);
1363 fits_write_img(fits, fitsType, 1, pixels->getNumElements(), const_cast<void *>(pixels->getData()),
1364 &status);
1365 if (behavior & AUTO_CHECK) {
1366 LSST_FITS_CHECK_STATUS(*this, "Writing image");
1367 }
1368
1369 // Now write the headers we didn't want cfitsio to know about when we were writing the pixels
1370 // (because we don't want it using them to modify the pixels, and we don't want it overwriting
1371 // these values).
1373 std::isfinite(scale.bzero) && std::isfinite(scale.bscale) && (scale.bscale != 0.0)) {
1375 if (scale.bzero != 0.0) {
1376 fits_write_key_lng(fits, "BZERO", static_cast<long>(scale.bzero),
1377 "Scaling: MEMORY = BZERO + BSCALE * DISK", &status);
1378 }
1379 if (scale.bscale != 1.0) {
1380 fits_write_key_lng(fits, "BSCALE", static_cast<long>(scale.bscale),
1381 "Scaling: MEMORY = BZERO + BSCALE * DISK", &status);
1382 }
1383 } else {
1384 fits_write_key_dbl(fits, "BZERO", scale.bzero, 12, "Scaling: MEMORY = BZERO + BSCALE * DISK",
1385 &status);
1386 fits_write_key_dbl(fits, "BSCALE", scale.bscale, 12, "Scaling: MEMORY = BZERO + BSCALE * DISK",
1387 &status);
1388 }
1389 if (behavior & AUTO_CHECK) {
1390 LSST_FITS_CHECK_STATUS(*this, "Writing BSCALE,BZERO");
1391 }
1392 }
1393
1394 if (scale.bitpix > 0 && !std::numeric_limits<T>::is_integer) {
1395 fits_write_key_lng(fits, "BLANK", scale.blank, "Value for undefined pixels", &status);
1396 fits_write_key_lng(fits, "ZDITHER0", options.scaling.seed, "Dithering seed", &status);
1397 fits_write_key_str(fits, "ZQUANTIZ", "SUBTRACTIVE_DITHER_1", "Dithering algorithm", &status);
1398 if (behavior & AUTO_CHECK) {
1399 LSST_FITS_CHECK_STATUS(*this, "Writing [Z]BLANK");
1400 }
1401 }
1402
1403 // cfitsio says this is deprecated, but Pan-STARRS found that it was sometimes necessary, writing:
1404 // "This forces a re-scan of the header to ensure everything's kosher.
1405 // Without this, compressed HDUs have been written out with PCOUNT=0 and TFORM1 not correctly set."
1406 fits_set_hdustruc(fits, &status);
1407 if (behavior & AUTO_CHECK) {
1408 LSST_FITS_CHECK_STATUS(*this, "Finalizing header");
1409 }
1410}
1411
1412template <typename T>
1416 writeImage(image, options, header.get(), mask.get());
1417}
1418
1419namespace {
1420
1422template <typename T, class Enable = void>
1423struct NullValue {
1424 static T constexpr value = 0;
1425};
1426
1428template <typename T>
1429struct NullValue<T, typename std::enable_if<std::numeric_limits<T>::has_quiet_NaN>::type> {
1430 static T constexpr value = std::numeric_limits<T>::quiet_NaN();
1431};
1432
1433} // namespace
1434
1435template <typename T>
1436void Fits::readImageImpl(int nAxis, T *data, long *begin, long *end, long *increment) {
1437 T null = NullValue<T>::value;
1438 int anyNulls = 0;
1439 fits_read_subset(reinterpret_cast<fitsfile *>(fptr), FitsType<T>::CONSTANT, begin, end, increment,
1440 reinterpret_cast<void *>(&null), data, &anyNulls, &status);
1441 if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Reading image");
1442}
1443
1445 int nAxis = 0;
1446 fits_get_img_dim(reinterpret_cast<fitsfile *>(fptr), &nAxis, &status);
1447 if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting NAXIS");
1448 return nAxis;
1449}
1450
1451void Fits::getImageShapeImpl(int maxDim, long *nAxes) {
1452 fits_get_img_size(reinterpret_cast<fitsfile *>(fptr), maxDim, nAxes, &status);
1453 if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting NAXES");
1454}
1455
1456template <typename T>
1458 int imageType = 0;
1459 fits_get_img_equivtype(reinterpret_cast<fitsfile *>(fptr), &imageType, &status);
1460 if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting image type");
1462 if (imageType < 0) {
1463 return false; // can't represent floating-point with integer
1464 }
1466 if (isFitsImageTypeSigned(imageType)) {
1467 return FitsBitPix<T>::CONSTANT >= imageType;
1468 } else {
1469 // need extra bits to safely convert unsigned to signed
1470 return FitsBitPix<T>::CONSTANT > imageType;
1471 }
1472 } else {
1473 if (!isFitsImageTypeSigned(imageType)) {
1474 return FitsBitPix<T>::CONSTANT >= imageType;
1475 } else if (imageType == LONGLONG_IMG) {
1476 // workaround for CFITSIO not recognizing uint64 as
1477 // unsigned
1478 return FitsBitPix<T>::CONSTANT >= imageType;
1479 } else {
1480 return false;
1481 }
1482 }
1483 }
1484 // we allow all conversions to float and double, even if they lose precision
1485 return true;
1486}
1487
1489 int bitpix = 0;
1490 fits_get_img_equivtype(reinterpret_cast<fitsfile *>(fptr), &bitpix, &status);
1491 if (behavior & AUTO_CHECK) LSST_FITS_CHECK_STATUS(*this, "Getting image type");
1492 // FITS' 'BITPIX' key is the number of bits in a pixel, but negative for
1493 // floats. But the above CFITSIO routine adds support for unsigned
1494 // integers by checking BZERO for an offset as well. So the 'bitpix' value
1495 // we get back from that should be the raw value for signed integers and
1496 // floats, but may be something else (still positive) for unsigned, and
1497 // hence we'll compare to some FITSIO constants to be safe looking at
1498 // integers.
1499 if (bitpix < 0) {
1500 return "float" + std::to_string(-bitpix);
1501 }
1502 switch (bitpix) {
1503 case BYTE_IMG: return "uint8";
1504 case SBYTE_IMG: return "int8";
1505 case SHORT_IMG: return "int16";
1506 case USHORT_IMG: return "uint16";
1507 case LONG_IMG: return "int32";
1508 case ULONG_IMG: return "uint32";
1509 case LONGLONG_IMG: return "int64";
1510 }
1511 throw LSST_EXCEPT(
1512 FitsError,
1513 (boost::format("Unrecognized BITPIX value: %d") % bitpix).str()
1514 );
1515}
1516
1518 auto fits = reinterpret_cast<fitsfile *>(fptr);
1519 int compType = 0; // cfitsio compression type
1520 fits_get_compression_type(fits, &compType, &status);
1521 if (behavior & AUTO_CHECK) {
1522 LSST_FITS_CHECK_STATUS(*this, "Getting compression type");
1523 }
1524
1525 ImageCompressionOptions::Tiles tiles = ndarray::allocate(MAX_COMPRESS_DIM);
1526 fits_get_tile_dim(fits, tiles.getNumElements(), tiles.getData(), &status);
1527 if (behavior & AUTO_CHECK) {
1528 LSST_FITS_CHECK_STATUS(*this, "Getting tile dimensions");
1529 }
1530
1531 float quantizeLevel;
1532 fits_get_quantize_level(fits, &quantizeLevel, &status);
1533 if (behavior & AUTO_CHECK) {
1534 LSST_FITS_CHECK_STATUS(*this, "Getting quantizeLevel");
1535 }
1536
1537 return ImageCompressionOptions(compressionAlgorithmFromCfitsio(compType), tiles, quantizeLevel);
1538}
1539
1541 auto fits = reinterpret_cast<fitsfile *>(fptr);
1542 fits_unset_compression_request(fits, &status); // wipe the slate and start over
1544 allowImageCompression ? comp.algorithm : ImageCompressionOptions::NONE;
1545 fits_set_compression_type(fits, compressionAlgorithmToCfitsio(algorithm), &status);
1546 if (behavior & AUTO_CHECK) {
1547 LSST_FITS_CHECK_STATUS(*this, "Setting compression type");
1548 }
1549
1550 if (algorithm == ImageCompressionOptions::NONE) {
1551 // Nothing else worth doing
1552 return;
1553 }
1554
1555 fits_set_tile_dim(fits, comp.tiles.getNumElements(), comp.tiles.getData(), &status);
1556 if (behavior & AUTO_CHECK) {
1557 LSST_FITS_CHECK_STATUS(*this, "Setting tile dimensions");
1558 }
1559
1561 fits_set_quantize_level(fits, comp.quantizeLevel, &status);
1562 if (behavior & AUTO_CHECK) {
1563 LSST_FITS_CHECK_STATUS(*this, "Setting quantization level");
1564 }
1565 }
1566}
1567
1568void setAllowImageCompression(bool allow) { allowImageCompression = allow; }
1569
1570bool getAllowImageCompression() { return allowImageCompression; }
1571
1572// ---- Manipulating files ----------------------------------------------------------------------------------
1573
1574Fits::Fits(std::string const &filename, std::string const &mode, int behavior_)
1575 : fptr(nullptr), status(0), behavior(behavior_) {
1576 if (mode == "r" || mode == "rb") {
1577 fits_open_file(reinterpret_cast<fitsfile **>(&fptr), const_cast<char *>(filename.c_str()), READONLY,
1578 &status);
1579 } else if (mode == "w" || mode == "wb") {
1580 std::filesystem::remove(filename); // cfitsio doesn't like over-writing files
1581 fits_create_file(reinterpret_cast<fitsfile **>(&fptr), const_cast<char *>(filename.c_str()), &status);
1582 } else if (mode == "a" || mode == "ab") {
1583 fits_open_file(reinterpret_cast<fitsfile **>(&fptr), const_cast<char *>(filename.c_str()), READWRITE,
1584 &status);
1585 int nHdu = 0;
1586 fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &nHdu, &status);
1587 fits_movabs_hdu(reinterpret_cast<fitsfile *>(fptr), nHdu, nullptr, &status);
1588 if ((behavior & AUTO_CHECK) && (behavior & AUTO_CLOSE) && (status) && (fptr)) {
1589 // We're about to throw an exception, and the destructor won't get called
1590 // because we're in the constructor, so cleanup here first.
1591 int tmpStatus = 0;
1592 fits_close_file(reinterpret_cast<fitsfile *>(fptr), &tmpStatus);
1593 }
1594 } else {
1595 throw LSST_EXCEPT(
1596 FitsError,
1597 (boost::format("Invalid mode '%s' given when opening file '%s'") % mode % filename).str());
1598 }
1599 if (behavior & AUTO_CHECK) {
1600 LSST_FITS_CHECK_STATUS(*this, boost::format("Opening file '%s' with mode '%s'") % filename % mode);
1601 }
1602}
1603
1604Fits::Fits(MemFileManager &manager, std::string const &mode, int behavior_)
1605 : fptr(nullptr), status(0), behavior(behavior_) {
1606 using Reallocator = void *(*)(void *, std::size_t);
1607 // It's a shame this logic is essentially a duplicate of above, but the innards are different enough
1608 // we can't really reuse it.
1609 if (mode == "r" || mode == "rb") {
1610 fits_open_memfile(reinterpret_cast<fitsfile **>(&fptr), "unused", READONLY, &manager._ptr,
1611 &manager._len, 0, nullptr, // no reallocator or deltasize necessary for READONLY
1612 &status);
1613 } else if (mode == "w" || mode == "wb") {
1614 Reallocator reallocator = nullptr;
1615 if (manager._managed) reallocator = &std::realloc;
1616 fits_create_memfile(reinterpret_cast<fitsfile **>(&fptr), &manager._ptr, &manager._len, 0,
1617 reallocator, // use default deltasize
1618 &status);
1619 } else if (mode == "a" || mode == "ab") {
1620 Reallocator reallocator = nullptr;
1621 if (manager._managed) reallocator = &std::realloc;
1622 fits_open_memfile(reinterpret_cast<fitsfile **>(&fptr), "unused", READWRITE, &manager._ptr,
1623 &manager._len, 0, reallocator, &status);
1624 int nHdu = 0;
1625 fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &nHdu, &status);
1626 fits_movabs_hdu(reinterpret_cast<fitsfile *>(fptr), nHdu, nullptr, &status);
1627 if ((behavior & AUTO_CHECK) && (behavior & AUTO_CLOSE) && (status) && (fptr)) {
1628 // We're about to throw an exception, and the destructor won't get called
1629 // because we're in the constructor, so cleanup here first.
1630 int tmpStatus = 0;
1631 fits_close_file(reinterpret_cast<fitsfile *>(fptr), &tmpStatus);
1632 }
1633 } else {
1634 throw LSST_EXCEPT(FitsError,
1635 (boost::format("Invalid mode '%s' given when opening memory file at '%s'") % mode %
1636 manager._ptr)
1637 .str());
1638 }
1639 if (behavior & AUTO_CHECK) {
1641 *this, boost::format("Opening memory file at '%s' with mode '%s'") % manager._ptr % mode);
1642 }
1643}
1644
1646 fits_close_file(reinterpret_cast<fitsfile *>(fptr), &status);
1647 fptr = nullptr;
1648}
1649
1651 daf::base::PropertyList const & first,
1652 daf::base::PropertyList const & second) {
1653 auto combined = std::make_shared<daf::base::PropertyList>();
1654 bool const asScalar = true;
1655 for (auto const &name : first.getOrderedNames()) {
1656 auto const iscv = isCommentIsValid(first, name);
1657 if (iscv.isComment) {
1658 if (iscv.isValid) {
1659 combined->add<std::string>(name, first.getArray<std::string>(name));
1660 }
1661 } else {
1662 combined->copy(name, first, name, asScalar);
1663 }
1664 }
1665 for (auto const &name : second.getOrderedNames()) {
1666 auto const iscv = isCommentIsValid(second, name);
1667 if (iscv.isComment) {
1668 if (iscv.isValid) {
1669 combined->add<std::string>(name, second.getArray<std::string>(name));
1670 }
1671 } else {
1672 // `copy` will replace an item, even if has a different type, so no need to call `remove`
1673 combined->copy(name, second, name, asScalar);
1674 }
1675 }
1676 return combined;
1677}
1678
1682 if (!first) {
1683 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, "First argument may not be null/None.");
1684 }
1685 if (!second) {
1686 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, "Second argument may not be null/None.");
1687 }
1688 return combineMetadata(*first, *second);
1689}
1690
1692
1693namespace detail {
1694template <typename T, typename... Args>
1695dafPlistPtr _readMetadata(T &&fitsparm, bool strip, Args... args) {
1697 fp.setHdu(args...);
1698 return readMetadata(fp, strip);
1699}
1700
1701} // namespace detail
1702
1703dafPlistPtr readMetadata(std::string const &fileName, int hdu, bool strip) {
1704 return detail::_readMetadata(fileName, strip, hdu);
1705}
1706
1707dafPlistPtr readMetadata(std::string const &fileName, std::string const &hduname, HduType type, int hduver,
1708 bool strip) {
1709 return detail::_readMetadata(fileName, strip, hduname, type, hduver);
1710}
1711
1713 return detail::_readMetadata(manager, strip, hdu);
1714}
1715
1716dafPlistPtr readMetadata(MemFileManager &manager, std::string const &hduname, HduType type, int hduver,
1717 bool strip) {
1718 return detail::_readMetadata(manager, strip, hduname, type, hduver);
1719}
1720
1722 auto metadata = std::make_shared<lsst::daf::base::PropertyList>();
1723 fitsfile.readMetadata(*metadata, strip);
1724 // if INHERIT=T, we want to also include header entries from the primary HDU
1725 int oldHdu = fitsfile.getHdu();
1726 if (oldHdu != 0 && metadata->exists("INHERIT")) {
1727 bool inherit = false;
1728 if (metadata->typeOf("INHERIT") == typeid(std::string)) {
1729 inherit = (metadata->get<std::string>("INHERIT") == "T");
1730 } else {
1731 inherit = metadata->get<bool>("INHERIT");
1732 }
1733 if (strip) metadata->remove("INHERIT");
1734 if (inherit) {
1735 HduMoveGuard guard(fitsfile, 0);
1736 // Combine the metadata from the primary HDU with the metadata from the specified HDU,
1737 // with non-comment values from the specified HDU superseding those in the primary HDU
1738 // and comments from the specified HDU appended to comments from the primary HDU
1739 auto primaryHduMetadata = std::make_shared<daf::base::PropertyList>();
1740 fitsfile.readMetadata(*primaryHduMetadata, strip);
1741 metadata = combineMetadata(*primaryHduMetadata, *metadata);
1742 } else {
1743 // Purge invalid values
1744 auto const emptyMetadata = std::make_shared<lsst::daf::base::PropertyList>();
1745 metadata = combineMetadata(*metadata, *emptyMetadata);
1746 }
1747 }
1748 return metadata;
1749}
1750
1751
1752HduMoveGuard::HduMoveGuard(Fits & fits, int hdu, bool relative) :
1753 _fits(fits),
1754 _oldHdu(_fits.getHdu()),
1755 _enabled(true)
1756{
1757 _fits.setHdu(hdu, relative);
1758}
1759
1761 if (!_enabled) {
1762 return;
1763 }
1764 int status = 0;
1765 std::swap(status, _fits.status); // unset error indicator, but remember the old status
1766 try {
1767 _fits.setHdu(_oldHdu);
1768 } catch (...) {
1769 LOGL_WARN(
1770 "afw.fits",
1771 makeErrorMessage(_fits.fptr, _fits.status, "Failed to move back to HDU %d").c_str(),
1772 _oldHdu
1773 );
1774 }
1775 std::swap(status, _fits.status); // reset the old status
1776}
1777
1779 auto fits = reinterpret_cast<fitsfile *>(fptr);
1780 if (getHdu() != 0 || countHdus() == 1) {
1781 return false; // Can't possibly be the PHU leading a compressed image
1782 }
1783 // Check NAXIS = 0
1784 int naxis;
1785 fits_get_img_dim(fits, &naxis, &status);
1786 if (behavior & AUTO_CHECK) {
1787 LSST_FITS_CHECK_STATUS(*this, "Checking NAXIS of PHU");
1788 }
1789 if (naxis != 0) {
1790 return false;
1791 }
1792 // Check first extension (and move back there when we're done if we're not compressed)
1793 HduMoveGuard move(*this, 1);
1794 bool isCompressed = fits_is_compressed_image(fits, &status);
1795 if (behavior & AUTO_CHECK) {
1796 LSST_FITS_CHECK_STATUS(*this, "Checking compression");
1797 }
1798 if (isCompressed) {
1799 move.disable();
1800 }
1801 return isCompressed;
1802}
1803
1805 : compression(fits::compressionAlgorithmFromString(config.get<std::string>("compression.algorithm")),
1806 std::vector<long>{config.getAsInt64("compression.columns"),
1807 config.getAsInt64("compression.rows")},
1808 config.getAsDouble("compression.quantizeLevel")),
1809 scaling(fits::scalingAlgorithmFromString(config.get<std::string>("scaling.algorithm")),
1810 config.getAsInt("scaling.bitpix"),
1811 config.exists("scaling.maskPlanes") ? config.getArray<std::string>("scaling.maskPlanes")
1813 config.getAsInt("scaling.seed"), config.getAsDouble("scaling.quantizeLevel"),
1814 config.getAsDouble("scaling.quantizePad"), config.get<bool>("scaling.fuzz"),
1815 config.getAsDouble("scaling.bscale"), config.getAsDouble("scaling.bzero")) {}
1816
1817namespace {
1818
1819template <typename T>
1820void validateEntry(daf::base::PropertySet &output, daf::base::PropertySet const &input,
1821 std::string const &name, T defaultValue) {
1822 output.add(name, input.get<T>(name, defaultValue));
1823}
1824
1825template <typename T>
1826void validateEntry(daf::base::PropertySet &output, daf::base::PropertySet const &input,
1827 std::string const &name, std::vector<T> defaultValue) {
1828 output.add(name, input.exists(name) ? input.getArray<T>(name) : defaultValue);
1829}
1830
1831} // anonymous namespace
1832
1834 auto validated = std::make_shared<daf::base::PropertySet>();
1835
1836 validateEntry(*validated, config, "compression.algorithm", std::string("NONE"));
1837 validateEntry(*validated, config, "compression.columns", 0);
1838 validateEntry(*validated, config, "compression.rows", 1);
1839 validateEntry(*validated, config, "compression.quantizeLevel", 0.0);
1840
1841 validateEntry(*validated, config, "scaling.algorithm", std::string("NONE"));
1842 validateEntry(*validated, config, "scaling.bitpix", 0);
1843 validateEntry(*validated, config, "scaling.maskPlanes", std::vector<std::string>{"NO_DATA"});
1844 validateEntry(*validated, config, "scaling.seed", 1);
1845 validateEntry(*validated, config, "scaling.quantizeLevel", 5.0);
1846 validateEntry(*validated, config, "scaling.quantizePad", 10.0);
1847 validateEntry(*validated, config, "scaling.fuzz", true);
1848 validateEntry(*validated, config, "scaling.bscale", 1.0);
1849 validateEntry(*validated, config, "scaling.bzero", 0.0);
1850
1851 // Check for additional entries that we don't support (e.g., from typos)
1852 for (auto const &name : config.names(false)) {
1853 if (!validated->exists(name)) {
1855 os << "Invalid image write option: " << name;
1857 }
1858 }
1859
1860 return validated;
1861}
1862
1863#define INSTANTIATE_KEY_OPS(r, data, T) \
1864 template void Fits::updateKey(std::string const &, T const &, std::string const &); \
1865 template void Fits::writeKey(std::string const &, T const &, std::string const &); \
1866 template void Fits::updateKey(std::string const &, T const &); \
1867 template void Fits::writeKey(std::string const &, T const &); \
1868 template void Fits::updateColumnKey(std::string const &, int, T const &, std::string const &); \
1869 template void Fits::writeColumnKey(std::string const &, int, T const &, std::string const &); \
1870 template void Fits::updateColumnKey(std::string const &, int, T const &); \
1871 template void Fits::writeColumnKey(std::string const &, int, T const &); \
1872 template void Fits::readKey(std::string const &, T &);
1873
1874#define INSTANTIATE_IMAGE_OPS(r, data, T) \
1875 template void Fits::writeImageImpl(T const *, int); \
1876 template void Fits::writeImage(image::ImageBase<T> const &, ImageWriteOptions const &, \
1877 daf::base::PropertySet const *, \
1878 image::Mask<image::MaskPixel> const *); \
1879 template void Fits::writeImage(image::ImageBase<T> const &, ImageWriteOptions const &, \
1880 std::shared_ptr<daf::base::PropertySet const>, \
1881 std::shared_ptr<image::Mask<image::MaskPixel> const>); \
1882 template void Fits::readImageImpl(int, T *, long *, long *, long *); \
1883 template bool Fits::checkImageType<T>(); \
1884 template int getBitPix<T>();
1885
1886#define INSTANTIATE_TABLE_OPS(r, data, T) \
1887 template int Fits::addColumn<T>(std::string const &ttype, int size); \
1888 template int Fits::addColumn<T>(std::string const &ttype, int size, std::string const &comment);
1889#define INSTANTIATE_TABLE_ARRAY_OPS(r, data, T) \
1890 template void Fits::writeTableArray(std::size_t row, int col, int nElements, T const *value); \
1891 template void Fits::readTableArray(std::size_t row, int col, int nElements, T *value);
1892
1893// ----------------------------------------------------------------------------------------------------------
1894// ---- Explicit instantiation ------------------------------------------------------------------------------
1895// ----------------------------------------------------------------------------------------------------------
1896
1897#define KEY_TYPES \
1898 (bool)(unsigned char)(short)(unsigned short)(int)(unsigned int)(long)(unsigned long)(LONGLONG)( \
1899 float)(double)(std::complex<float>)(std::complex<double>)(std::string)
1900
1901#define COLUMN_TYPES \
1902 (bool)(std::string)(std::int8_t)(std::uint8_t)(std::int16_t)(std::uint16_t)(std::int32_t)(std::uint32_t) \
1903 (std::int64_t)(float)(double)(lsst::geom::Angle)(std::complex<float>)(std::complex<double>)
1904
1905#define COLUMN_ARRAY_TYPES \
1906 (bool)(char)(std::uint8_t)(std::int16_t)(std::uint16_t)(std::int32_t)(std::uint32_t)(std::int64_t)( \
1907 float)(double)(lsst::geom::Angle)(std::complex<float>)(std::complex<double>)
1908
1909#define IMAGE_TYPES \
1910 (unsigned char)(short)(unsigned short)(int)(unsigned int)(std::int64_t)(std::uint64_t)(float)(double)
1911
1912BOOST_PP_SEQ_FOR_EACH(INSTANTIATE_KEY_OPS, _, KEY_TYPES)
1913BOOST_PP_SEQ_FOR_EACH(INSTANTIATE_TABLE_OPS, _, COLUMN_TYPES)
1914BOOST_PP_SEQ_FOR_EACH(INSTANTIATE_TABLE_ARRAY_OPS, _, COLUMN_ARRAY_TYPES)
1915BOOST_PP_SEQ_FOR_EACH(INSTANTIATE_IMAGE_OPS, _, IMAGE_TYPES)
1916} // namespace fits
1917} // namespace afw
1918} // namespace lsst
py::object result
Definition: _schema.cc:429
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,...)
Create an exception with a given type.
Definition: Exception.h:48
afw::table::Key< afw::table::Array< MaskPixelT > > mask
LSST DM logging module built on log4cxx.
#define LOGLS_WARN(logger, message)
Log a warn-level message using an iostream-based interface.
Definition: Log.h:659
#define LOGL_WARN(logger, message...)
Log a warn-level message using a varargs/printf style interface.
Definition: Log.h:547
#define LOGLS_DEBUG(logger, message)
Log a debug-level message using an iostream-based interface.
Definition: Log.h:619
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:308
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:682
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:714
void closeFile()
Close a FITS file.
Definition: fits.cc:1645
void writeImage(ndarray::Array< T const, N, C > const &array)
Write an ndarray::Array to a FITS image HDU.
Definition: fits.h:498
int getImageDim()
Return the number of dimensions in the current HDU.
Definition: fits.cc:1444
std::size_t countRows()
Return the number of row in a table.
Definition: fits.cc:1174
ImageCompressionOptions getImageCompression()
Return the current image compression settings.
Definition: fits.cc:1517
void createEmpty()
Create an empty image HDU with NAXIS=0 at the end of the file.
Definition: fits.cc:1255
void readTableArray(std::size_t row, int col, int nElements, T *value)
Read an array value from a binary table.
Definition: fits.cc:1207
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:706
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:1132
Fits()
Default constructor; set all data members to 0.
Definition: fits.h:637
void readTableScalar(std::size_t row, int col, T &value)
Read an array scalar from a binary table.
Definition: fits.h:623
bool checkCompressedImagePhu()
Go to the first image header in the FITS file.
Definition: fits.cc:1778
int countHdus()
Return the number of HDUs in the file.
Definition: fits.cc:549
void writeTableScalar(std::size_t row, int col, T value)
Write a scalar value to a binary table.
Definition: fits.h:611
void forEachKey(HeaderIterationFunctor &functor)
Call a polymorphic functor for every key in the header.
Definition: fits.cc:796
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:1155
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:674
void setImageCompression(ImageCompressionOptions const &options)
Set compression options for writing FITS images.
Definition: fits.cc:1540
void readMetadata(daf::base::PropertySet &metadata, bool strip=false)
Read a FITS header into a PropertySet or PropertyList.
Definition: fits.cc:1102
void writeTableArray(std::size_t row, int col, int nElements, T const *value)
Write an array value to a binary table.
Definition: fits.cc:1184
void readKey(std::string const &key, T &value)
Read a FITS header key into the given reference.
Definition: fits.cc:789
std::string getImageDType()
Return the numpy dtype equivalent of the image pixel type (e.g.
Definition: fits.cc:1488
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:1110
long getTableArraySize(int col)
Return the size of an array column.
Definition: fits.cc:1232
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:1164
bool checkImageType()
Return true if the current HDU is compatible with the given pixel type.
Definition: fits.cc:1457
RAII scoped guard for moving the HDU in a Fits object.
Definition: fits.h:792
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:125
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
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
std::string const & getComment(std::string const &name) const
Get the comment for a string property name (possibly hierarchical).
Definition: PropertyList.cc:78
std::vector< std::string > getOrderedNames() const
Get the list of property names, in the order they were added.
Definition: PropertyList.cc:82
Class for storing generic metadata.
Definition: PropertySet.h:66
int64_t getAsInt64(std::string const &name) const
Get the last value for a bool/char/short/int/int64_t property name (possibly hierarchical).
std::vector< std::string > names(bool topLevelOnly=true) const
Get the names in the PropertySet, optionally including those in subproperties.
T get(std::string const &name) const
Get the last value for a property name (possibly hierarchical).
void add(std::string const &name, T const &value)
Append a single value to the vector of values for a property name (possibly hierarchical).
virtual std::shared_ptr< PropertySet > deepCopy() const
Make a deep copy of the PropertySet and all of its contents.
std::vector< std::string > paramNames(bool topLevelOnly=true) const
A variant of names that excludes the names of subproperties.
A class representing an angle.
Definition: Angle.h:128
Reports invalid arguments.
Definition: Runtime.h:66
Reports errors that are due to events beyond the control of the program.
Definition: Runtime.h:104
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:1886
daf::base::PropertyList * list
Definition: fits.cc:928
#define INSTANTIATE_IMAGE_OPS(r, data, T)
Definition: fits.cc:1874
#define COLUMN_ARRAY_TYPES
Definition: fits.cc:1905
daf::base::PropertySet * set
Definition: fits.cc:927
#define INSTANTIATE_KEY_OPS(r, data, T)
Definition: fits.cc:1863
bool isValid
Definition: fits.cc:400
bool isComment
Definition: fits.cc:399
#define KEY_TYPES
Definition: fits.cc:1897
#define COLUMN_TYPES
Definition: fits.cc:1901
#define INSTANTIATE_TABLE_ARRAY_OPS(r, data, T)
Definition: fits.cc:1889
bool strip
Definition: fits.cc:926
#define IMAGE_TYPES
Definition: fits.cc:1909
#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)
dafPlistPtr _readMetadata(T &&fitsparm, bool strip, Args... args)
Definition: fits.cc:1695
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:1650
const int DEFAULT_HDU
Specify that the default HDU should be read.
Definition: fitsDefaults.h:18
std::shared_ptr< daf::base::PropertyList > readMetadata(std::string const &fileName, int hdu=DEFAULT_HDU, bool strip=false)
Read FITS header.
Definition: fits.cc:1703
int getBitPix()
Return the cfitsio integer BITPIX code for the given data type.
Definition: fits.cc:497
ImageScalingOptions::ScalingAlgorithm scalingAlgorithmFromString(std::string const &name)
Interpret scaling algorithm expressed in string.
void setAllowImageCompression(bool allow)
Definition: fits.cc:1568
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:1570
int compressionAlgorithmToCfitsio(ImageCompressionOptions::CompressionAlgorithm algorithm)
Convert ImageCompressionOptions::CompressionAlgorithm to cfitsio.
HduType
an enum representing the various types of FITS HDU that are available in cfitsio library
Definition: fits.h:290
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:212
std::string const wcsNameForXY0
Definition: ImageBase.h:70
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)
ImageT val
Definition: CR.cc:146
int row
Definition: CR.cc:145
int col
Definition: CR.cc:144
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:223
ImageCompressionOptions compression
Options controlling compression.
Definition: fits.h:224
ImageScalingOptions scaling
Options controlling scaling.
Definition: fits.h:225
static std::shared_ptr< daf::base::PropertySet > validate(daf::base::PropertySet const &config)
Validate a PropertySet.
Definition: fits.cc:1833
ImageWriteOptions(image::Image< T > const &image)
Construct with default options for images.
Definition: fits.h:229
FITS BITPIX header value by C++ type.
T substr(T... args)
T swap(T... args)
T to_string(T... args)