RTC Toolkit 6.0.0-pre2
Loading...
Searching...
No Matches
fitsIoFunctions.hpp
Go to the documentation of this file.
1
12
13#ifndef RTCTK_COMPONENTFRAMEWORK_FITSIOFUNCTIONS_HPP
14#define RTCTK_COMPONENTFRAMEWORK_FITSIOFUNCTIONS_HPP
15
20
21#include <fitsio.h>
22
23#include <fmt/format.h>
24
25#include <any>
26#include <array>
27#include <cassert>
28#include <filesystem>
29#include <limits>
30#include <string>
31#include <type_traits>
32#include <typeinfo>
33#include <unistd.h>
34
35namespace {
36
44template <typename T>
45void IdentifyCfitsioTypes(int& bitpix, int& datatype) {
46 if constexpr (std::is_integral_v<T>) {
47 if constexpr (std::is_same_v<T, bool>) {
48 bitpix = SBYTE_IMG;
49 datatype = TSBYTE;
50 } else if constexpr (sizeof(T) == 1) {
51 if constexpr (std::is_signed_v<T>) {
52 bitpix = SBYTE_IMG;
53 datatype = TSBYTE;
54 } else {
55 bitpix = BYTE_IMG;
56 datatype = TBYTE;
57 }
58 } else if constexpr (sizeof(T) == 2) {
59 if constexpr (std::is_signed_v<T>) {
60 bitpix = SHORT_IMG;
61 datatype = TSHORT;
62 } else {
63 bitpix = USHORT_IMG;
64 datatype = TUSHORT;
65 }
66 } else if constexpr (sizeof(T) == 4) {
67 if constexpr (std::is_signed_v<T>) {
68 bitpix = LONG_IMG;
69 if constexpr (sizeof(int) == 4) {
70 datatype = TINT;
71 } else if constexpr (sizeof(long) == 4) {
72 datatype = TLONG;
73 } else {
74 static_assert(sizeof(int) == 4 or sizeof(long) == 4,
75 "Require either int or long type to be 32 bits wide.");
76 }
77 } else {
78 bitpix = ULONG_IMG;
79 if constexpr (sizeof(unsigned int) == 4) {
80 datatype = TUINT;
81 } else if constexpr (sizeof(unsigned long) == 4) {
82 datatype = TULONG;
83 } else {
84 static_assert(
85 sizeof(unsigned int) == 4 or sizeof(unsigned long) == 4,
86 "Require either unsigned int or unsigned long type to be 32 bits wide.");
87 }
88 }
89 } else if constexpr (sizeof(T) == 8) {
90 if constexpr (std::is_signed_v<T>) {
91 bitpix = LONGLONG_IMG;
92 datatype = TLONGLONG;
93 } else {
94#ifdef ULONGLONG_IMG
95 bitpix = ULONGLONG_IMG;
96 datatype = TULONGLONG;
97#else
98 // If we are using an older Cfitsio library that does not have ULONGLONG_IMG defined
99 // then simply alias to using the signed version for the image code and data type.
100 bitpix = LONGLONG_IMG;
101 datatype = TLONGLONG;
102#endif
103 }
104 } else {
105 static_assert(
106 sizeof(T) == 1 or sizeof(T) == 2 or sizeof(T) == 4 or sizeof(T) == 8,
107 "The integer type used as the template parameter must be one of 8, 16, 32, or 64"
108 " bits wide.");
109 }
110 }
111 if constexpr (std::is_floating_point_v<T>) {
112 if constexpr (sizeof(T) == 4) {
113 bitpix = FLOAT_IMG;
114 datatype = TFLOAT;
115 } else if constexpr (sizeof(T) == 8) {
116 bitpix = DOUBLE_IMG;
117 datatype = TDOUBLE;
118 } else {
119 static_assert(
120 sizeof(T) == 4 or sizeof(T) == 8,
121 "The floating-point type used as the template parameter must be float or double.");
122 }
123 } else {
124 static_assert(std::is_arithmetic_v<T>,
125 "The template parameter used must be an integer or floating-point type.");
126 }
127}
128
129} // namespace
130
132
139std::string GetCfitsioErrorMsg(int status);
140
148std::string CfitsioImageTypeToString(int bitpix);
149
157std::string CfitsioDataTypeToString(int datatype);
158
159// Some versions of GCC do not recognise that returning const std::type_info& does not result in
160// a dangling reference. Therefore this is a false-positive warning.
161#if defined(__GNUC__)
162#pragma GCC diagnostic push
163#pragma GCC diagnostic ignored "-Wdangling-reference"
164#endif
165
178const std::type_info& GetFitsImageType(const std::string& filename);
179
180#if defined(__GNUC__)
181#pragma GCC diagnostic pop
182#endif
183
196std::vector<uint64_t> GetFitsImageShape(const std::string& filename);
197
209std::size_t GetFitsImageSize(const std::string& filename);
210
224template <typename T>
225void WriteDataToFits(const std::string& filename, const T& buffer) {
226 static_assert(
228 "The buffer argument must be a matrix or a vector (or a corresponding span).");
229
230 // Determine the image dimensions and total number of elements.
231 std::vector<long> dims;
232 long n_elements = buffer.size();
233
235 dims.push_back(static_cast<long>(buffer.GetNcols()));
236 dims.push_back(static_cast<long>(buffer.GetNrows()));
237 } else { // vector/span
238 dims.push_back(static_cast<long>(buffer.size()));
239 }
240
241 using U = typename T::value_type;
242 // Workout the appropriate Cfitsio image type code and data type code to use, based on the type
243 // of the buffer elements, i.e. the type of U.
244 int bitpix = 0, datatype = 0;
245 IdentifyCfitsioTypes<U>(bitpix, datatype);
246
247 std::string tmpfilename = filename + ".new-" + std::to_string(getpid());
248
249 // Create the new FITS file and open it for modifications.
250 fitsfile* fptr = nullptr;
251 int status = 0; // It is important to initialise this to zero before each Cfitsio API call.
252 if (fits_create_file(&fptr, tmpfilename.c_str(), &status) != 0) {
253 std::string msg = fmt::format(
254 "Failed to create FITS file '{}'. {}", tmpfilename, GetCfitsioErrorMsg(status));
255 CII_THROW(RtctkException, msg);
256 }
257
258 try {
259 // Create the image header in the HDU.
260 status = 0;
261 if (fits_create_img(fptr, bitpix, dims.size(), dims.data(), &status) != 0) {
262 std::string msg = fmt::format("Failed to create image in FITS file '{}'. {}",
263 tmpfilename,
264 GetCfitsioErrorMsg(status));
265 CII_THROW(RtctkException, msg);
266 }
267
268 // Set the pixel start location to 1 for all dimensions. Cfitsio counts from 1, not 0.
269 std::vector<long> fpixel(dims.size(), 1);
270
271 // check bool by provided type in template
272 if constexpr (std::is_same_v<U, bool>) {
273 // For boolean element types we need to set the BOOLITEM keyword to indicate the special
274 // data type handling.
275 std::string value;
276 value.reserve(FLEN_CARD); // Must use maximum keyword card size according to Cfitsio.
277 value = "true\0";
278 assert(value.capacity() == FLEN_CARD);
279 if (fits_write_key(fptr, TSTRING, "BOOLITEM", value.data(), nullptr, &status) != 0) {
280 std::string msg = fmt::format("Failed to write keyword BOOLITEM to '{}'. {}",
281 filename,
282 GetCfitsioErrorMsg(status));
283 CII_THROW(RtctkException, msg);
284 }
285
286 // For booleans we need to convert these into a type that can be handled by Cfitsio.
287 std::vector<int8_t> tmp_buffer;
288 tmp_buffer.resize(buffer.size());
289 std::transform(
290 buffer.begin(), buffer.end(), tmp_buffer.begin(), [](bool element) -> int8_t {
291 return element == true ? 1 : 0;
292 });
293
294 // We are forced to use const_cast, since the fits_write_pix function does not accept
295 // const void*.
296 void* array_ptr = const_cast<void*>(reinterpret_cast<const void*>(tmp_buffer.data()));
297 status = 0;
298 if (fits_write_pix(fptr, datatype, fpixel.data(), n_elements, array_ptr, &status) !=
299 0) {
300 std::string msg = fmt::format("Failed to write buffer to FITS file '{}'. {}",
301 tmpfilename,
302 GetCfitsioErrorMsg(status));
303 CII_THROW(RtctkException, msg);
304 }
305 } else {
306 // For all non-boolean element types we can write the buffer directly as pixel data.
307 void* array_ptr = const_cast<void*>(reinterpret_cast<const void*>(buffer.data()));
308 status = 0;
309 if (fits_write_pix(fptr, datatype, fpixel.data(), n_elements, array_ptr, &status) !=
310 0) {
311 std::string msg = fmt::format("Failed to write buffer to FITS file '{}'. {}",
312 tmpfilename,
313 GetCfitsioErrorMsg(status));
314 CII_THROW(RtctkException, msg);
315 }
316 }
317 } catch (...) {
318 // Attempt to close the FITS file if an exception was thrown. But do not throw an additional
319 // exception if closing the file failed. Just log this error and rethrow the original
320 // exception.
321 status = 0;
322 if (fits_close_file(fptr, &status) != 0) {
323 LOG4CPLUS_ERROR(GetLogger("rtctk"),
324 fmt::format("Failed to close file '{}' while handling an exception. {}",
325 tmpfilename,
326 GetCfitsioErrorMsg(status)));
327 }
328 throw;
329 }
330
331 status = 0;
332 if (fits_close_file(fptr, &status) != 0) {
333 std::string msg = fmt::format(
334 "Failed to close FITS file '{}'. {}", tmpfilename, GetCfitsioErrorMsg(status));
335 CII_THROW(RtctkException, msg);
336 }
337
338 // Rename the temporary file to its final value. Updating the file in this manner will make sure
339 // that different processes or threads will see changes to the file in an atomic manner. Either
340 // the old file will be visible, or the new file, but not a partially modified file.
341 try {
342 std::filesystem::rename(tmpfilename, filename);
343 } catch (const std::exception& error) {
344 CII_THROW_WITH_NESTED(RtctkException,
345 error,
346 fmt::format("Failed to rename '{}' to '{}'.", tmpfilename, filename));
347 }
348}
349
365template <typename T>
366void ReadDataFromFits(const std::string& filename, T& buffer) {
367 static_assert(
369 "The buffer argument must be a matrix or a vector (or a corresponding span).");
370
371 using U = typename T::value_type;
372 // Identify the Cfitsio data type code to be used and the expected Cfitsio image type code,
373 // based on the buffer element type, i.e. type of U.
374 int expected_bitpix = 0;
375 int datatype = 0;
376 IdentifyCfitsioTypes<U>(expected_bitpix, datatype);
377
378 fitsfile* fptr = nullptr;
379 int status = 0; // It is important to initialise this to zero before every Cfitsio API call.
380 if (fits_open_image(&fptr, filename.c_str(), READONLY, &status) != 0) {
381 std::string msg =
382 fmt::format("Failed to open FITS file '{}'. {}", filename, GetCfitsioErrorMsg(status));
383 CII_THROW(RtctkException, msg);
384 }
385
386 try {
387 // Read and check that the pixel format used in the FITS file corresponds to the type used
388 // for the buffer's elements passed to this function.
389 int bitpix = -1;
390 status = 0;
391 if (fits_get_img_equivtype(fptr, &bitpix, &status) != 0) {
392 std::string msg = fmt::format("Failed to read the image type from FITS file '{}'. {}",
393 filename,
394 GetCfitsioErrorMsg(status));
395 CII_THROW(RtctkException, msg);
396 }
397
398 if (bitpix == LONGLONG_IMG) {
399 // NOTE: Correcting the bitpix for ULONGLONG_IMG. There is a bug in Cfitsio that returns
400 // LONGLONG_IMG instead of ULONGLONG_IMG.
401 std::array<char, FLEN_CARD> value; // Must use maximum possible FITS card size.
402 status = 0;
403 int result = fits_read_keyword(fptr, "BZERO", value.data(), nullptr, &status);
404 if (result != 0 && status != KEY_NO_EXIST) {
405 std::string msg = fmt::format("Failed to read keyword BZERO from '{}'. {}",
406 filename,
407 GetCfitsioErrorMsg(status));
408 CII_THROW(RtctkException, msg);
409 }
410 if (std::string(value.data()) ==
411 std::to_string(std::numeric_limits<uint64_t>::max() / 2 + 1)) {
412#ifdef ULONGLONG_IMG
413 bitpix = ULONGLONG_IMG;
414#else
415 // If we are dealing with an older Cfitsio library that does not have ULONGLONG_IMG
416 // defined, then simply alias to using the signed version instead.
417 bitpix = LONGLONG_IMG;
418#endif
419 }
420 }
421 if (bitpix != expected_bitpix) {
422 std::string msg = fmt::format(
423 "The FITS file '{}' has the wrong image format of {}. "
424 "Expected a FITS image of type {}.",
425 filename,
427 CfitsioImageTypeToString(expected_bitpix));
428 CII_THROW(RtctkException, msg);
429 }
430
431 // Read and check that the FITS file contains the expected number of image axes.
432 int naxis = -1;
433 status = 0;
434 if (fits_get_img_dim(fptr, &naxis, &status) != 0) {
435 std::string msg =
436 fmt::format("Failed to read the number of image axes from FITS file '{}'. {}",
437 filename,
438 GetCfitsioErrorMsg(status));
439 CII_THROW(RtctkException, msg);
440 }
441
442 constexpr bool is_matrix = IS_MATRIX_BUFFER_TYPE<T> or IS_MATRIX_SPAN_TYPE<T>;
443 const int expected_naxis = is_matrix ? 2 : 1;
444
445 if (naxis != expected_naxis) {
446 std::string msg = fmt::format(
447 "The FITS file '{}' has image dimensions that we cannot handle. "
448 "Expected a {}D image.",
449 filename,
450 expected_naxis);
451 CII_THROW(RtctkException, msg);
452 }
453
454 using size_type = typename T::size_type;
455
456 // Read the shape information of the image data, i.e. the dimensions.
457 std::vector<long> dims;
458 dims.resize(static_cast<size_type>(naxis));
459 status = 0;
460 if (fits_get_img_size(fptr, naxis, dims.data(), &status) != 0) {
461 std::string msg = fmt::format("Failed to read the image size from FITS file '{}'. {}",
462 filename,
463 GetCfitsioErrorMsg(status));
464 CII_THROW(RtctkException, msg);
465 }
466
467 long n_elements = 1;
468 for (auto dim : dims) {
469 n_elements *= dim;
470 }
471
472 if constexpr (IS_VECTOR_TYPE<T>) {
473 buffer.resize(static_cast<size_type>(dims[0]));
474 } else if constexpr (IS_MATRIX_BUFFER_TYPE<T>) {
475 auto nrows = static_cast<size_type>(dims[1]);
476 auto ncols = static_cast<size_type>(dims[0]);
477 buffer.resize(nrows, ncols);
478 } else {
479 if (buffer.size() < static_cast<size_type>(n_elements)) {
480 CII_THROW(BufferTooSmall, buffer.size(), n_elements);
481 }
482 }
483
484 // Set the pixel start location to 1 for all dimensions. Cfitsio counts from 1, not 0.
485 std::vector<long> fpixel(dims.size(), 1);
486 int anynull = 0;
487
488 if constexpr (std::is_same_v<U, bool>) {
489 // For booleans we need to first read the FITS data as integers, then convert these
490 // into actual boolean values and fill the buffer.
491 std::vector<int8_t> tmp_buffer;
492 tmp_buffer.resize(buffer.size());
493
494 status = 0;
495 if (fits_read_pix(fptr,
496 datatype,
497 fpixel.data(),
498 n_elements,
499 nullptr,
500 tmp_buffer.data(),
501 &anynull,
502 &status) != 0) {
503 std::string msg = fmt::format("Failed to read the image from FITS file '{}'. {}",
504 filename,
505 GetCfitsioErrorMsg(status));
506 CII_THROW(RtctkException, msg);
507 }
508
509 std::transform(
510 tmp_buffer.begin(), tmp_buffer.end(), buffer.begin(), [](int8_t element) {
511 return element == 0 ? false : true;
512 });
513 } else {
514 // For non-booleans we read the FITS data directly into the buffer.
515 status = 0;
516 if (fits_read_pix(fptr,
517 datatype,
518 fpixel.data(),
519 n_elements,
520 nullptr,
521 buffer.data(),
522 &anynull,
523 &status) != 0) {
524 std::string msg = fmt::format("Failed to read the image from FITS file '{}'. {}",
525 filename,
526 GetCfitsioErrorMsg(status));
527 CII_THROW(RtctkException, msg);
528 }
529 }
530 } catch (...) {
531 // Attempt to close the FITS file if an exception was thrown. But do not throw an additional
532 // exception if closing the file failed. Just log this error and rethrow the original
533 // exception.
534 status = 0;
535 if (fits_close_file(fptr, &status) != 0) {
536 LOG4CPLUS_ERROR(GetLogger("rtctk"),
537 fmt::format("Failed to close file '{}' while handling an exception. {}",
538 filename,
539 GetCfitsioErrorMsg(status)));
540 }
541 throw;
542 }
543
544 status = 0;
545 if (fits_close_file(fptr, &status) != 0) {
546 std::string msg = fmt::format(
547 "Failed to close the FITS file '{}'. {}", filename, GetCfitsioErrorMsg(status));
548 CII_THROW(RtctkException, msg);
549 }
550}
551
552template <>
553void WriteDataToFits(const std::string& filename, const std::any& buffer);
554
555template <>
556void ReadDataFromFits(const std::string& filename, std::any& buffer);
557
566template <typename T>
567[[deprecated("Replaced by WriteDataToFits")]]
568void WriteMatrixToFits(const std::string& filename, const T& matrix, bool boolean_items = false) {
570 "The matrix argument must be of type MatrixBuffer or MatrixSpan.");
571 assert(uint64_t(matrix.GetNrows()) <= uint64_t(std::numeric_limits<long>::max()));
572 assert(uint64_t(matrix.GetNcols()) <= uint64_t(std::numeric_limits<long>::max()));
573
574 using U = typename T::value_type;
575 // Workout the appropriate Cfitsio image type code and data type code to use, based on the type
576 // used with MatrixBuffer or MatrixSpan, i.e. the type of U.
577 int bitpix = 0;
578 int datatype = 0;
579 IdentifyCfitsioTypes<U>(bitpix, datatype);
580
581 std::string tmpfilename = filename + ".new-" + std::to_string(getpid());
582
583 // Create the new FITS file and open it for modifications.
584 fitsfile* fptr = nullptr;
585 int status = 0; // It is important to initialise this to zero before each Cfitsio API call.
586 if (fits_create_file(&fptr, tmpfilename.c_str(), &status) != 0) {
587 std::string msg = fmt::format(
588 "Failed to create FITS file '{}'. {}", tmpfilename, GetCfitsioErrorMsg(status));
589 CII_THROW(RtctkException, msg);
590 }
591
592 try {
593 // Create the image header in the HDU.
594 long nrows = static_cast<long>(matrix.GetNrows());
595 long ncols = static_cast<long>(matrix.GetNcols());
596 std::array<long, 2> shape = {ncols, nrows};
597 status = 0;
598 if (fits_create_img(fptr, bitpix, shape.size(), shape.data(), &status) != 0) {
599 std::string msg = fmt::format("Failed to create the image type from FITS file '{}'. {}",
600 tmpfilename,
601 GetCfitsioErrorMsg(status));
602 CII_THROW(RtctkException, msg);
603 }
604
605 if (boolean_items) {
606 std::string value;
607 value.reserve(FLEN_CARD); // Must use maximum keyword card size according to Cfitsio.
608 value = "true\0";
609 assert(value.capacity() == FLEN_CARD);
610 if (fits_write_key(fptr, TSTRING, "BOOLITEM", value.data(), nullptr, &status) != 0) {
611 std::string msg = fmt::format("Failed to write keyword BOOLITEM to '{}'. {}",
612 filename,
613 GetCfitsioErrorMsg(status));
614 CII_THROW(RtctkException, msg);
615 }
616 }
617
618 // Write the matrix data as pixels.
619 std::array<long, 2> fpixel = {1, 1}; // Pixel start location. Cfitsio counts from 1, not 0.
620 long nelements = matrix.size();
621 // We are forced to use const_cast, since the fits_write_pix function does not accept
622 // const void*.
623 void* array = const_cast<void*>(reinterpret_cast<const void*>(matrix.data()));
624 status = 0;
625 if (fits_write_pix(fptr, datatype, fpixel.data(), nelements, array, &status) != 0) {
626 std::string msg = fmt::format("Failed to write the matrix to FITS file '{}'. {}",
627 tmpfilename,
628 GetCfitsioErrorMsg(status));
629 CII_THROW(RtctkException, msg);
630 }
631 } catch (...) {
632 // Attempt to close the FITS file if an exception was thrown. But do not throw an additional
633 // exception if closing the file failed. Just log this error and rethrow the original
634 // exception.
635 status = 0;
636 if (fits_close_file(fptr, &status) != 0) {
637 LOG4CPLUS_ERROR(GetLogger("rtctk"),
638 fmt::format("Failed to close file '{}' while handling an exception. {}",
639 tmpfilename,
640 GetCfitsioErrorMsg(status)));
641 }
642 throw;
643 }
644
645 status = 0;
646 if (fits_close_file(fptr, &status) != 0) {
647 std::string msg = fmt::format(
648 "Failed to close the FITS file '{}'. ", tmpfilename, GetCfitsioErrorMsg(status));
649 CII_THROW(RtctkException, msg);
650 }
651
652 // Rename the temporary file to its final value. Updating the file in this manner will make sure
653 // that different processes or threads will see changes to the file in an atomic manner. Either
654 // the old file will be visible, or the new file, but not a partially modified file.
655 try {
656 std::filesystem::rename(tmpfilename, filename);
657 } catch (const std::exception& error) {
658 CII_THROW_WITH_NESTED(
660 error,
661 fmt::format("Failed to rename FITS file '{}' to '{}'.", tmpfilename, filename))
662 }
663}
664
672template <typename T>
673[[deprecated("Replaced by ReadDataFromFits")]]
674void ReadMatrixFromFits(const std::string& filename, T& matrix) {
676 "The matrix argument must be of type MatrixBuffer or MatrixSpan.");
677
678 using U = typename T::value_type;
679 // Identify the Cfitsio data type code to be used and the expected Cfitsio image type code,
680 // based on the type used with MatrixBuffer or MatrixSpan, i.e. type of U.
681 int expected_bitpix = 0;
682 int datatype = 0;
683 IdentifyCfitsioTypes<U>(expected_bitpix, datatype);
684
685 fitsfile* fptr = nullptr;
686 int status = 0; // It is important to initialise this to zero before every Cfitsio API call.
687 if (fits_open_image(&fptr, filename.c_str(), READONLY, &status) != 0) {
688 std::string msg =
689 fmt::format("Failed to open FITS file '{}'. {}", filename, GetCfitsioErrorMsg(status));
690 CII_THROW(RtctkException, msg);
691 }
692
693 try {
694 // Read and check that the pixel format used in the FITS file corresponds to the type used
695 // for the matrix object passed to this function.
696 int bitpix = -1;
697 status = 0;
698 if (fits_get_img_equivtype(fptr, &bitpix, &status) != 0) {
699 std::string msg = fmt::format("Failed to read the image type from FITS file '{}'. {}",
700 filename,
701 GetCfitsioErrorMsg(status));
702 CII_THROW(RtctkException, msg);
703 }
704 if (bitpix == LONGLONG_IMG) {
705 // NOTE: Correcting the bitpix for ULONGLONG_IMG. There is a bug in Cfitsio that returns
706 // LONGLONG_IMG instead of ULONGLONG_IMG.
707 char value[80]; // Use maximum possible FITS card size of 80 bytes.
708 status = 0;
709 int result = fits_read_keyword(fptr, "BZERO", value, nullptr, &status);
710 if (result != 0 and status != KEY_NO_EXIST) {
711 std::string msg = fmt::format("Failed to read keyword BZERO from '{}'. {}",
712 filename,
713 GetCfitsioErrorMsg(status));
714 CII_THROW(RtctkException, msg);
715 }
716 if (std::string(value) ==
717 std::to_string(std::numeric_limits<uint64_t>::max() / 2 + 1)) {
718#ifdef ULONGLONG_IMG
719 bitpix = ULONGLONG_IMG;
720#else
721 // If we are dealing with an older Cfitsio library that does not have ULONGLONG_IMG
722 // defined, then simply alias to using the signed version instead.
723 bitpix = LONGLONG_IMG;
724#endif
725 }
726 }
727 if (bitpix != expected_bitpix) {
728 std::string msg = fmt::format(
729 "The FITS file '{}' has the wrong image format of {}. Expected a FITS image of "
730 "type {}.",
731 filename,
733 CfitsioImageTypeToString(expected_bitpix));
734 CII_THROW(RtctkException, msg);
735 }
736
737 // Read and check that the number of image axes is 2.
738 int naxis = -1;
739 status = 0;
740 if (fits_get_img_dim(fptr, &naxis, &status) != 0) {
741 std::string msg =
742 fmt::format("Failed to read the number of image axes from FITS file '{}'. {}",
743 filename,
744 GetCfitsioErrorMsg(status));
745 CII_THROW(RtctkException, msg);
746 }
747 if (naxis != 2) {
748 std::string msg = fmt::format(
749 "The FITS file '{}' has image dimensions that we cannot handle. Expect a 2D image "
750 "when loading as a matrix.",
751 filename);
752 CII_THROW(RtctkException, msg);
753 }
754
755 long size[2] = {-1, -1};
756 status = 0;
757 if (fits_get_img_size(fptr, 2, size, &status) != 0) {
758 std::string msg = fmt::format("Failed to read the image size from FITS file '{}'. {}",
759 filename,
760 GetCfitsioErrorMsg(status));
761 CII_THROW(RtctkException, msg);
762 }
763
764 using size_type = typename T::size_type;
765 auto nrows = static_cast<size_type>(size[1]);
766 auto ncols = static_cast<size_type>(size[0]);
767 long nelements = size[0] * size[1]; // Total number of pixels, i.e. rows * columns.
768
769 long fpixel[2] = {1, 1}; // Pixel start location. Cfitsio counts from 1, not 0.
770 int anynull = 0; // Boolean flag indicating if there were any null/nil pixel values.
771 if constexpr (IS_MATRIX_BUFFER_TYPE<T>) {
772 matrix.resize(nrows, ncols);
773 } else {
774 if (matrix.size() < static_cast<size_type>(nrows * ncols)) {
775 CII_THROW(BufferTooSmall, matrix.size(), nrows * ncols);
776 }
777 }
778 void* array = matrix.data();
779 status = 0;
780 int result =
781 fits_read_pix(fptr, datatype, fpixel, nelements, nullptr, array, &anynull, &status);
782 if (result != 0) {
783 std::string msg = fmt::format("Failed to read the image from FITS file '{}'. {}",
784 filename,
785 GetCfitsioErrorMsg(status));
786 CII_THROW(RtctkException, msg);
787 }
788 } catch (...) {
789 // Attempt to close the FITS file if an exception was thrown. But do not throw an additional
790 // exception if closing the file failed. Just log this error and rethrow the original
791 // exception.
792 status = 0;
793 if (fits_close_file(fptr, &status) != 0) {
794 LOG4CPLUS_ERROR(GetLogger("rtctk"),
795 fmt::format("Failed to close file '{}' while handling an exception. {}",
796 filename,
797 GetCfitsioErrorMsg(status)));
798 }
799 throw;
800 }
801
802 status = 0;
803 if (fits_close_file(fptr, &status) != 0) {
804 std::string msg = fmt::format(
805 "Failed to close the FITS file '{}'. {}", filename, GetCfitsioErrorMsg(status));
806 CII_THROW(RtctkException, msg);
807 }
808}
809
818template <typename T>
819[[deprecated("Replaced by WriteDataToFits")]]
820void WriteVectorToFits(const std::string& filename, const T& vector, bool boolean_items = false) {
821 static_assert(IS_VECTOR_TYPE<T> or IS_SPAN_TYPE<T>,
822 "The vector argument must be of type std::vector or gsl::span.");
823 assert(uint64_t(vector.size()) <= uint64_t(std::numeric_limits<long>::max()));
824
825 using U = typename T::value_type;
826 // Workout the appropriate Cfitsio image type code and data type code to use, based on the type
827 // used with std::vector or gsl::span, i.e. the type of U.
828 int bitpix = 0;
829 int datatype = 0;
830 IdentifyCfitsioTypes<U>(bitpix, datatype);
831
832 std::string tmpfilename = filename + ".new-" + std::to_string(getpid());
833
834 // Create the new FITS file and open it for modifications.
835 fitsfile* fptr = nullptr;
836 int status = 0; // It is important to initialise this to zero before each Cfitsio API call.
837 if (fits_create_file(&fptr, tmpfilename.c_str(), &status) != 0) {
838 std::string msg = fmt::format(
839 "Failed to create FITS file '{}'. {}", tmpfilename, GetCfitsioErrorMsg(status));
840 CII_THROW(RtctkException, msg);
841 }
842
843 try {
844 // Create the image header in the HDU.
845 long size = static_cast<long>(vector.size());
846 status = 0;
847 if (fits_create_img(fptr, bitpix, 1, &size, &status) != 0) {
848 std::string msg = fmt::format("Failed to create the image type from FITS file '{}'. {}",
849 tmpfilename,
850 GetCfitsioErrorMsg(status));
851 CII_THROW(RtctkException, msg);
852 }
853
854 if (boolean_items) {
855 std::string value;
856 value.reserve(FLEN_CARD); // Must use maximum keyword card size according to Cfitsio.
857 value = "true\0";
858 assert(value.capacity() == FLEN_CARD);
859 if (fits_write_key(fptr, TSTRING, "BOOLITEM", value.data(), nullptr, &status) != 0) {
860 std::string msg = fmt::format("Failed to write keyword BOOLITEM to '{}'. {}",
861 filename,
862 GetCfitsioErrorMsg(status));
863 CII_THROW(RtctkException, msg);
864 }
865 }
866
867 // Write the vector data as pixels.
868 long fpixel = 1; // Pixel start location. Cfitsio counts from 1, not 0.
869 long nelements = vector.size();
870 // We are forced to use const_cast, since the fits_write_pix function does not accept
871 // const void*.
872 void* array = const_cast<void*>(reinterpret_cast<const void*>(vector.data()));
873 status = 0;
874 if (fits_write_pix(fptr, datatype, &fpixel, nelements, array, &status) != 0) {
875 std::string msg = fmt::format("Failed to write the vector to FITS file '{}'. {}",
876 tmpfilename,
877 GetCfitsioErrorMsg(status));
878 CII_THROW(RtctkException, msg);
879 }
880 } catch (...) {
881 // Attempt to close the FITS file if an exception was thrown. But do not throw an additional
882 // exception if closing the file failed. Just log this error and rethrow the original
883 // exception.
884 status = 0;
885 if (fits_close_file(fptr, &status) != 0) {
886 LOG4CPLUS_ERROR(GetLogger("rtctk"),
887 fmt::format("Failed to close file '{}' while handling an exception. {}",
888 tmpfilename,
889 GetCfitsioErrorMsg(status)));
890 }
891 throw;
892 }
893
894 status = 0;
895 if (fits_close_file(fptr, &status) != 0) {
896 std::string msg = fmt::format(
897 "Failed to close the FITS file '{}'. {}", tmpfilename, GetCfitsioErrorMsg(status));
898 CII_THROW(RtctkException, msg);
899 }
900
901 // Rename the temporary file to its final value. Updating the file in this manner will make sure
902 // that different processes or threads will see changes to the file in an atomic manner. Either
903 // the old file will be visible, or the new file, but not a partially modified file.
904 try {
905 std::filesystem::rename(tmpfilename, filename);
906 } catch (const std::exception& error) {
907 CII_THROW_WITH_NESTED(
909 error,
910 fmt::format("Failed to rename FITS file '{}' to '{}'.", tmpfilename, filename));
911 }
912}
913
921template <typename T>
922[[deprecated("Replaced by ReadDataFromFits")]]
923void ReadVectorFromFits(const std::string& filename, T& vector) {
924 static_assert(IS_VECTOR_TYPE<T> or IS_SPAN_TYPE<T>,
925 "The vector argument must be of type std::vector or gsl::span.");
926
927 using U = typename T::value_type;
928 // Identify the Cfitsio data type code to be used and the expected Cfitsio image type code,
929 // based on the type used with std::vector or gsl::span, i.e. type of U.
930 int expected_bitpix = 0;
931 int datatype = 0;
932 IdentifyCfitsioTypes<U>(expected_bitpix, datatype);
933
934 fitsfile* fptr = nullptr;
935 int status = 0; // It is important to initialise this to zero before every Cfitsio API call.
936 if (fits_open_image(&fptr, filename.c_str(), READONLY, &status) != 0) {
937 std::string msg =
938 fmt::format("Failed to open FITS file '{}'. {}", filename, GetCfitsioErrorMsg(status));
939 CII_THROW(RtctkException, msg);
940 }
941
942 try {
943 // Read and check that the pixel format used in the FITS file corresponds to the type used
944 // for the vector object passed to this function.
945 int bitpix = -1;
946 status = 0;
947 if (fits_get_img_equivtype(fptr, &bitpix, &status) != 0) {
948 std::string msg = fmt::format("Failed to read the image type from FITS file '{}'. {}",
949 filename,
950 GetCfitsioErrorMsg(status));
951 CII_THROW(RtctkException, msg);
952 }
953 if (bitpix == LONGLONG_IMG) {
954 // NOTE: Correcting the bitpix for ULONGLONG_IMG. There is a bug in Cfitsio that returns
955 // LONGLONG_IMG instead of ULONGLONG_IMG.
956 char value[80]; // Use maximum possible FITS card size of 80 bytes.
957 status = 0;
958 int result = fits_read_keyword(fptr, "BZERO", value, nullptr, &status);
959 if (result != 0 and status != KEY_NO_EXIST) {
960 std::string msg = fmt::format("Failed to read keyword BZERO from '{}'. {}",
961 filename,
962 GetCfitsioErrorMsg(status));
963 CII_THROW(RtctkException, msg);
964 }
965 if (std::string(value) ==
966 std::to_string(std::numeric_limits<uint64_t>::max() / 2 + 1)) {
967#ifdef ULONGLONG_IMG
968 bitpix = ULONGLONG_IMG;
969#else
970 // If we are dealing with an older Cfitsio library that does not have ULONGLONG_IMG
971 // defined, then simply alias to using the signed version instead.
972 bitpix = LONGLONG_IMG;
973#endif
974 }
975 }
976 if (bitpix != expected_bitpix) {
977 std::string msg = fmt::format(
978 "The FITS file '{}' has the wrong image format of {}. Expected a FITS image of "
979 "type {}.",
980 filename,
982 CfitsioImageTypeToString(expected_bitpix));
983 CII_THROW(RtctkException, msg);
984 }
985
986 // Read and check that the number of image axes is 2.
987 int naxis = -1;
988 status = 0;
989 if (fits_get_img_dim(fptr, &naxis, &status) != 0) {
990 std::string msg =
991 fmt::format("Failed to read the number of image axes from FITS file '{}'. {}",
992 filename,
993 GetCfitsioErrorMsg(status));
994 CII_THROW(RtctkException, msg);
995 }
996 if (naxis != 1) {
997 std::string msg = fmt::format(
998 "The FITS file '{}' has image dimensions that we cannot handle. Expect a 1D image "
999 "when loading as a vector.",
1000 filename);
1001 CII_THROW(RtctkException, msg);
1002 }
1003
1004 long nelements = -1;
1005 status = 0;
1006 if (fits_get_img_size(fptr, 1, &nelements, &status) != 0) {
1007 std::string msg =
1008 fmt::format("Failed to read the 1D image size from FITS file '{}'. {}",
1009 filename,
1010 GetCfitsioErrorMsg(status));
1011 CII_THROW(RtctkException, msg);
1012 }
1013
1014 using size_type = typename T::size_type;
1015 long fpixel = 1; // Pixel start location. Cfitsio counts from 1, not 0.
1016 int anynull = 0; // Boolean flag indicating if there were any null/nil pixel values.
1017 if constexpr (IS_VECTOR_TYPE<T>) {
1018 vector.resize(static_cast<size_type>(nelements));
1019 } else {
1020 if (vector.size() < static_cast<size_type>(nelements)) {
1021 CII_THROW(BufferTooSmall, vector.size(), nelements);
1022 }
1023 }
1024 void* array = vector.data();
1025 status = 0;
1026 int result =
1027 fits_read_pix(fptr, datatype, &fpixel, nelements, nullptr, array, &anynull, &status);
1028 if (result != 0) {
1029 std::string msg = fmt::format("Failed to read the image from FITS file '{}'. {}",
1030 filename,
1031 GetCfitsioErrorMsg(status));
1032 CII_THROW(RtctkException, msg);
1033 }
1034 } catch (...) {
1035 // Attempt to close the FITS file if an exception was thrown. But do not throw an additional
1036 // exception if closing the file failed. Just log this error and rethrow the original
1037 // exception.
1038 status = 0;
1039 if (fits_close_file(fptr, &status) != 0) {
1040 LOG4CPLUS_ERROR(GetLogger("rtctk"),
1041 fmt::format("Failed to close file '{}' while handling an exception. {}",
1042 filename,
1043 GetCfitsioErrorMsg(status)));
1044 }
1045 throw;
1046 }
1047
1048 status = 0;
1049 if (fits_close_file(fptr, &status) != 0) {
1050 std::string msg = fmt::format(
1051 "Failed to close the FITS file '{}'. {}", filename, GetCfitsioErrorMsg(status));
1052 CII_THROW(RtctkException, msg);
1053 }
1054}
1055
1056// The following are template specialisations for vectors and matrices of boolean values.
1057
1061template <typename A>
1062[[deprecated("Replaced by WriteDataToFits")]]
1063void WriteMatrixToFits(const std::string& filename, const MatrixBuffer<bool, A>& matrix) {
1064#pragma GCC diagnostic push
1065#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1066 // Convert the boolean matrix to a matrix of 8 bit integers and write that to file instead.
1067 MatrixBuffer<int8_t> int_matrix;
1068 int_matrix.resize(matrix.GetNrows(), matrix.GetNcols());
1069 for (MatrixBuffer<int8_t>::size_type n = 0; n < int_matrix.GetNrows(); ++n) {
1070 for (MatrixBuffer<int8_t>::size_type m = 0; m < int_matrix.GetNcols(); ++m) {
1071 int_matrix(n, m) = matrix(n, m) ? 1 : 0;
1072 }
1073 }
1074 WriteMatrixToFits(filename, int_matrix, true);
1075#pragma GCC diagnostic pop
1076}
1077
1081template <typename A>
1082[[deprecated("Replaced by ReadDataFromFits")]]
1083void ReadMatrixFromFits(const std::string& filename, MatrixBuffer<bool, A>& matrix) {
1084#pragma GCC diagnostic push
1085#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1086 // Load the matrix from file as 8 bit integers and convert it to a boolean matrix.
1087 MatrixBuffer<int8_t> int_matrix;
1088 ReadMatrixFromFits(filename, int_matrix);
1089 matrix.resize(int_matrix.GetNrows(), int_matrix.GetNcols());
1090 for (MatrixBuffer<int8_t>::size_type n = 0; n < int_matrix.GetNrows(); ++n) {
1091 for (MatrixBuffer<int8_t>::size_type m = 0; m < int_matrix.GetNcols(); ++m) {
1092 matrix(n, m) = int_matrix(n, m) == 0 ? false : true;
1093 }
1094 }
1095#pragma GCC diagnostic pop
1096}
1097
1101template <typename A>
1102[[deprecated("Replaced by WriteDataToFits")]]
1103void WriteVectorToFits(const std::string& filename, const std::vector<bool, A>& vector) {
1104#pragma GCC diagnostic push
1105#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1106 // Concert the boolean vector to a vector of 8 bit integers and write that to file instead.
1107 std::vector<int8_t> int_vector;
1108 int_vector.resize(vector.size());
1109 for (std::vector<int8_t>::size_type n = 0; n < int_vector.size(); ++n) {
1110 int_vector[n] = vector[n] ? 1 : 0;
1111 }
1112 WriteVectorToFits(filename, int_vector, true);
1113#pragma GCC diagnostic pop
1114}
1115
1119template <typename A>
1120[[deprecated("Replaced by ReadDataFromFits")]]
1121void ReadVectorFromFits(const std::string& filename, std::vector<bool, A>& vector) {
1122#pragma GCC diagnostic push
1123#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1124 // Load the vector from file as 8 bit integers and convert it to a boolean vector.
1125 std::vector<int8_t> int_vector;
1126 ReadVectorFromFits(filename, int_vector);
1127 vector.resize(int_vector.size());
1128 for (std::vector<int8_t>::size_type n = 0; n < int_vector.size(); ++n) {
1129 vector[n] = int_vector[n] == 0 ? false : true;
1130 }
1131#pragma GCC diagnostic pop
1132}
1133
1134} // namespace rtctk::componentFramework
1135
1136#endif // RTCTK_COMPONENTFRAMEWORK_FITSIOFUNCTIONS_HPP
The BufferTooSmall is thrown when an API call fails because the provided buffer is not big enough to ...
Definition exceptions.hpp:266
A buffer class representing 2D matrix data.
Definition matrixBuffer.hpp:28
size_type GetNcols() const
Definition matrixBuffer.hpp:89
size_type GetNrows() const
Definition matrixBuffer.hpp:85
constexpr void resize(size_type n, size_type m)
Definition matrixBuffer.hpp:60
The RtctkException class is the base class for all Rtctk exceptions.
Definition exceptions.hpp:213
Provides macros and utilities for exception handling.
Logging Support Library based on log4cplus.
Declaration of the MatrixBuffer template class used in APIs.
Definition commandReplier.cpp:22
std::string GetCfitsioErrorMsg(int status)
Helper function to convert a Cfitsio status code to a human readable message.
Definition fitsIoFunctions.cpp:23
void ReadVectorFromFits(const std::string &filename, T &vector)
Reads a FITS file containing a 1D image into a buffer object representing a vector.
Definition fitsIoFunctions.hpp:923
void WriteMatrixToFits(const std::string &filename, const T &matrix, bool boolean_items=false)
Writes data representing a matrix as an image to a FITS file.
Definition fitsIoFunctions.hpp:568
void ReadMatrixFromFits(const std::string &filename, T &matrix)
Reads a FITS file image into a buffer object representing a matrix.
Definition fitsIoFunctions.hpp:674
constexpr bool IS_MATRIX_SPAN_TYPE
Is true if the type is a MatrixSpan<U> of some type U.
Definition typeTraits.hpp:128
void ReadDataFromFits(const std::string &filename, std::any &buffer)
Definition fitsIoFunctions.cpp:406
constexpr bool IS_SPAN_TYPE
Is true if the type is a gsl::span<U> of some type U.
Definition typeTraits.hpp:107
void WriteDataToFits(const std::string &filename, const std::any &buffer)
Definition fitsIoFunctions.cpp:395
log4cplus::Logger & GetLogger(const std::string &name="app")
Get handle to a specific logger.
Definition logger.cpp:192
std::string CfitsioDataTypeToString(int datatype)
Returns a string representation of a Cfitsio data type code.
Definition fitsIoFunctions.cpp:61
std::vector< uint64_t > GetFitsImageShape(const std::string &filename)
Get the shape of a FITS image.
Definition fitsIoFunctions.cpp:274
std::string CfitsioImageTypeToString(int bitpix)
Returns a string representation of a Cfitsio image type code.
Definition fitsIoFunctions.cpp:32
void WriteVectorToFits(const std::string &filename, const T &vector, bool boolean_items=false)
Writes data as a 1D image to a FITS file.
Definition fitsIoFunctions.hpp:820
constexpr bool IS_VECTOR_TYPE
Is true if the type is a std::vector<U> of some type U.
Definition typeTraits.hpp:47
constexpr bool IS_MATRIX_BUFFER_TYPE
Is true if the type is a MatrixBuffer<U> of some type U.
Definition typeTraits.hpp:77
const std::type_info & GetFitsImageType(const std::string &filename)
Get the C++ type corresponding to a FITS image.
Definition fitsIoFunctions.cpp:104
std::size_t GetFitsImageSize(const std::string &filename)
Get the number of pixels in a FITS image.
Definition fitsIoFunctions.cpp:336
Provides useful mechanisms to test various type traits.