ifw-daq  3.0.0-pre2
IFW Data Acquisition modules
keyword.hpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_ocm_libfits
4  * @copyright 2022 ESO - European Southern Observatory
5  *
6  * @brief Contains data structure for FITS keywords
7  */
8 #ifndef DAQ_OCM_DAQ_FITS_KEYWORD_HPP_
9 #define DAQ_OCM_DAQ_FITS_KEYWORD_HPP_
10 
11 #include <cassert>
12 
13 #include <array>
14 
15 #include <iosfwd>
16 #include <optional>
17 #include <string>
18 #include <variant>
19 #include <vector>
20 
21 #include <cfitsio/fitsio.h>
22 
23 namespace daq::fits {
24 // Forward declarations
25 enum KeywordType : uint8_t;
26 
27 namespace constants {
28 /**
29  * FITS keyword record length.
30  */
31 static constexpr size_t RECORD_LENGTH = 80u;
32 /**
33  * FITS keyword name length.
34  */
35 static constexpr size_t KEYWORD_NAME_LENGTH = 8u;
36 /**
37  * ESO keyword prefix, up to and including trailing whitespace
38  */
39 static constexpr std::string_view ESO_HIERARCH_PREFIX = "HIERARCH ESO ";
40 /**
41  * Absolute maximum length of logical keyword assuming non-packed value-indicator ' = ' and 1 char
42  * value (-4u)
43  */
44 static constexpr size_t ESO_HIERARCH_MAX_NAME_LENGTH =
45  RECORD_LENGTH - ESO_HIERARCH_PREFIX.size() - 4u;
46 /**
47  * FITS value indicator.
48  */
49 static constexpr std::string_view VALUE_INDICATOR = "= ";
50 /**
51  * Blank chars which is used to determine end of keyword record.
52  */
53 static constexpr std::string_view BLANK_CHARS = std::string_view(" \0", 2);
54 } // namespace constants
55 
56 /**
57  * Type of FITS keyword.
58  *
59  * FITS standard define two types: Value keywords and Commentary keywords.
60  *
61  * ESO conventions adds a third "ESO hiearchical keyword" which is strictly speaking a
62  * Commentary keyword with keyword name "HIERARCH".
63  */
64 enum KeywordType : uint8_t {
65  /**
66  * A value keyword.
67  */
69  /**
70  * An ESO hiearchical keyword.
71  */
72  Eso,
73  /**
74  * A commentary keyword, which are keywords that do not fall into the previous categories.
75  */
77 };
78 
80  std::string_view name;
82 };
83 
84 /**
85  * Fits keyword type
86  *
87  * See https://heasarc.gsfc.nasa.gov/docs/software/fitsio/c/c_user/node52.html#ffdtyp
88  */
89 enum class KeywordClass : int {
90  Structure = TYP_STRUC_KEY,
91  Compression = TYP_CMPRS_KEY,
92  Scale = TYP_SCAL_KEY,
93  Null = TYP_NULL_KEY,
94  Dim = TYP_DIM_KEY,
95  Range = TYP_RANG_KEY,
96  Unit = TYP_UNIT_KEY,
97  Disp = TYP_DISP_KEY,
98  HduId = TYP_HDUID_KEY,
99  Checksum = TYP_CKSUM_KEY,
100  Wcs = TYP_WCS_KEY,
101  RefSys = TYP_REFSYS_KEY,
102  Comment = TYP_COMM_KEY,
103  Continue = TYP_CONT_KEY,
104 
105  User = TYP_USER_KEY,
106 };
107 
108 /**
109  * @returns true if keyword type and names are equal.
110  * @relatesalso KeywordNameView
111  */
112 bool operator==(KeywordNameView lhs, KeywordNameView rhs) noexcept;
113 
114 /**
115  * @returns true if keyword type and names are not equal.
116  * @relatesalso KeywordNameView
117  */
118 bool operator!=(KeywordNameView lhs, KeywordNameView rhs) noexcept;
119 
120 /**
121  * Represents the literal 80-character FITS keyword record.
122  *
123  * @ingroup daq_ocm_libfits
124  */
125 class LiteralKeyword final {
126 public:
127  /**
128  * Decomposed components a literal keyword.
129  */
130  struct Components {
132 
133  std::string_view name;
134  std::string_view value;
135  /**
136  * Comment may be empty
137  */
138  std::string_view comment;
139  };
140 
141  /**
142  * Initializes an empty record (filled with ' ' characters)
143  */
144  LiteralKeyword() noexcept;
145 
146  /**
147  * Construct literal keyword from a fixed size record.
148  *
149  * @param record FITS keyword record literal.
150  */
151  explicit LiteralKeyword(std::array<char, constants::RECORD_LENGTH> record);
152 
153  /**
154  * Construct literal keyword from string_view.
155  *
156  * @note If string_view is empty this will be interpreted as the (valid) keyword " ".
157  *
158  * @param record literal keyword record.
159  *
160  * @throw std::invalid_argument if record is invalid (size() > 80).
161  */
162  explicit LiteralKeyword(std::string_view record);
163 
164  LiteralKeyword(LiteralKeyword const& other) noexcept;
165  LiteralKeyword& operator=(LiteralKeyword const& other) noexcept;
166 
167  /**
168  * @return Keyword type.
169  */
170  constexpr KeywordType GetType() const noexcept {
171  return m_components.type;
172  }
173 
174  /**
175  * Query logical keyword name.
176  *
177  * The logical keyword name depends on the type of keyword:
178  *
179  * - Type::Value: Returns same value as GetName() but without trailing whitespaces.
180  * - Type::Eso: Returns the logical keyword name following "HIEARCH ESO" up to the first "=",
181  * without leading or trailing whitespaces.
182  * - Type::Commentary: Keywords that do not fall into
183  *
184  * @returns the logical keyword name which depends on the type of keyword and the type.
185  */
186  constexpr KeywordNameView GetName() const& noexcept {
187  return {m_components.name, m_components.type};
188  }
189  KeywordNameView GetName() && noexcept = delete;
190 
191  /**
192  * @returns the FITS keyword record without trailing blank characters.
193  *
194  * @todo Rename to GetLogicalRecord?
195  */
196  std::string_view GetRecord() const& noexcept;
197  constexpr std::string_view GetRecord() && noexcept = delete;
198 
199  /**
200  * Get components of the keyword in its literal form with insignifant whitespaces removed.
201  */
202  constexpr Components GetComponents() const& noexcept {
203  return m_components;
204  }
205 
206 private:
207  std::array<char, constants::RECORD_LENGTH> m_record;
208  // Components refer to m_record
209  Components m_components;
210 };
211 
212 /**
213  * Sort by logical keyword name (not DICD sort)
214  *
215  * @relatesalso LiteralKeyword
216  * @ingroup daq_ocm_libfits
217  */
218 bool operator<(LiteralKeyword const&, LiteralKeyword const&) noexcept;
219 
220 /**
221  * Compares equally if keyword record in @a lhs is the same as @a rhs (char for char).
222  *
223  * @relatesalso LiteralKeyword
224  * @ingroup daq_ocm_libfits
225  */
226 bool operator==(LiteralKeyword const& lhs, LiteralKeyword const& rhs) noexcept;
227 
228 /**
229  * Compares inequally if keyword record in @a lhs is not the same as @a rhs (char for char).
230  *
231  * @relatesalso LiteralKeyword
232  * @ingroup daq_ocm_libfits
233  */
234 bool operator!=(LiteralKeyword const&, LiteralKeyword const&) noexcept;
235 
236 std::ostream& operator<<(std::ostream& os, LiteralKeyword const& kw);
237 
238 /**
239  * Non template base class that is purely used to avoid type-deduction issues of ValueType.
240  */
242  // Possible value types that can be carried in a keyword
243  using ValueType = std::variant<std::string, int64_t, uint64_t, double, bool>;
244 };
245 
246 /**
247  * A type safe version of LiteralKeyword that consist of the three basic components of a FITS
248  * keyword keyword record: name, value and optional comment.
249  *
250  * @note name should be used for the logical keyword name, not the keyword name according to FITS
251  * standard. E.g. the ESO hiearchical keywords all have the keyword name HIERARCH according to the
252  * standard, but this structure should instead use the logical fields where e.g. "HIERARCH ESO DPR
253  * CATG" is the logical keyword name.
254  *
255  * It does not model any specific unit, only the value type. Therefore it is not strictly necessary
256  * to have all possible types represented in ValueType (e.g. both singel and double precision
257  * floats).
258  *
259  * The idea is that the unit, formatting and ranges is provided provided by dictionaries.
260  *
261  * It also typically requires a dictionary to be able to understand how to parse @c value.
262  *
263  * @ingroup daq_ocm_libfits
264  */
265 template <class Trait>
267  BasicKeyword() = default;
268  BasicKeyword(BasicKeyword const&) = default;
269  BasicKeyword(BasicKeyword&&) noexcept = default;
270  BasicKeyword(std::string name,
271  char const* string_value,
272  std::optional<std::string> comment = std::nullopt);
273  BasicKeyword(std::string name,
275  std::optional<std::string> comment = std::nullopt);
276 
277  BasicKeyword& operator=(BasicKeyword&& rhs) noexcept = default;
278  BasicKeyword& operator=(BasicKeyword const& rhs) = default;
279 
280  /**
281  * Query logical keyword name.
282  *
283  * @returns logical keyword name.
284  */
285  constexpr KeywordNameView GetName() const& noexcept {
286  return KeywordNameView{name, Trait::GetKeywordType()};
287  }
288  KeywordNameView GetName() && = delete;
289 
290  /**
291  * Compares all members for equality.
292  */
293  bool operator==(BasicKeyword const& rhs) const noexcept;
294  /**
295  * Compares all members for inequality.
296  */
297  bool operator!=(BasicKeyword const& rhs) const noexcept;
298  /**
299  * Uses @a name property as the sorting index.
300  */
301  bool operator<(BasicKeyword const& rhs) const noexcept;
302 
303  /**
304  * Trimmed keyword name
305  */
306  std::string name;
308  /**
309  * Trimmed keyword comment
310  */
311  std::optional<std::string> comment;
312 };
313 
315  static constexpr KeywordType GetKeywordType() noexcept {
316  return KeywordType::Value;
317  }
318 };
320  static constexpr KeywordType GetKeywordType() noexcept {
321  return KeywordType::Eso;
322  }
323 };
324 
325 /**
326  * Standard FITS value keyword.
327  *
328  * @ingroup daq_ocm_libfits
329  */
331 
332 /**
333  * ESO hiearchical keyword.
334  *
335  * @ingroup daq_ocm_libfits
336  */
338 
339 /**
340  * Keyword sorting function.
341  *
342  * @relatesalso ValueKeyword
343  * @relatesalso EsoKeyword
344  * @ingroup daq_ocm_libfits
345  */
346 bool operator<(LiteralKeyword const&, EsoKeyword const&) noexcept;
347 /**
348  * Keyword sorting function.
349  *
350  * @relatesalso ValueKeyword
351  * @relatesalso LiteralKeyword
352  * @ingroup daq_ocm_libfits
353  */
354 bool operator<(LiteralKeyword const&, ValueKeyword const&) noexcept;
355 /**
356  * Keyword sorting function.
357  *
358  * @relatesalso ValueKeyword
359  * @relatesalso EsoKeyword
360  * @ingroup daq_ocm_libfits
361  */
362 bool operator<(ValueKeyword const&, EsoKeyword const&) noexcept;
363 /**
364  * Keyword sorting function.
365  *
366  * @relatesalso ValueKeyword
367  * @relatesalso LiteralKeyword
368  * @ingroup daq_ocm_libfits
369  */
370 bool operator<(ValueKeyword const&, LiteralKeyword const&) noexcept;
371 
372 /**
373  * Keyword sorting function.
374  *
375  * @relatesalso ValueKeyword
376  * @relatesalso EsoKeyword
377  * @ingroup daq_ocm_libfits
378  */
379 bool operator<(EsoKeyword const&, ValueKeyword const&) noexcept;
380 /**
381  * Keyword sorting function.
382  *
383  * @relatesalso LiteralKeyword
384  * @relatesalso EsoKeyword
385  * @ingroup daq_ocm_libfits
386  */
387 bool operator<(EsoKeyword const&, LiteralKeyword const&) noexcept;
388 
389 template <class Trait>
390 std::ostream& operator<<(std::ostream& os, BasicKeyword<Trait> const& kw);
391 
392 template <class Trait>
393 std::ostream& operator<<(std::ostream& os, BasicKeywordBase::ValueType const& kw);
394 
395 /**
396  * The different variants of keywords that are supported.
397  *
398  * @ingroup daq_ocm_libfits
399  */
400 using KeywordVariant = std::variant<ValueKeyword, EsoKeyword, LiteralKeyword>;
401 
402 /**
403  * Subset of value-typed keywords.
404  *
405  * @ingroup daq_ocm_libfits
406  */
407 using TypedKeywordVariant = std::variant<ValueKeyword, EsoKeyword>;
408 
409 /**
410  * Vector of keywords.
411  *
412  * @ingroup daq_ocm_libfits
413  */
414 using KeywordVector = std::vector<KeywordVariant>;
415 
416 /**
417  * Ostream formatting of keywords.
418  *
419  * @ingroup daq_ocm_libfits
420  */
421 std::ostream& operator<<(std::ostream& os, KeywordVariant const& kw);
422 
423 /**
424  * Get keyword name from @a keyword.
425  * @param keyword Keyword to query.
426  */
427 constexpr KeywordNameView GetKeywordName(EsoKeyword const& keyword) noexcept {
428  return keyword.GetName();
429 }
430 
431 constexpr KeywordNameView GetKeywordName(ValueKeyword const& keyword) noexcept {
432  return keyword.GetName();
433 }
434 
435 /**
436  * Get keyword name from @a keyword variant.
437  * @param keyword Keyword to query.
438  */
439 constexpr KeywordNameView GetKeywordName(KeywordVariant const& keyword) noexcept {
440  return std::visit([&](auto const& kw) { return kw.GetName(); }, keyword);
441 }
442 
443 /**
444  * Get keyword class
445  * @param name Keyword name.
446  * @return keyword class.
447  */
448 KeywordClass GetKeywordClass(std::string_view name);
449 
450 /**
451  * Compare logical keyword names of keyword of the same type.
452  *
453  * Comparing names only is useful to e.g. look up keyword by name, to modify it.
454  *
455  * @param lhs value to compare
456  * @param rhs value to compare
457  * @returns true iff lhs and rhs are the same type and their name property compares equal.
458  *
459  * @ingroup daq_ocm_libfits
460  */
461 bool NameEquals(KeywordVariant const& lhs, KeywordVariant const& rhs) noexcept;
462 
463 enum class ConflictPolicy {
464  /**
465  * Replace keyword that conflicts.
466  */
467  Replace,
468  /**
469  * Skip keyword that conflicts.
470  */
471  Skip
472 };
473 
474 /**
475  * Updates @a to with keywords from @a from.
476  *
477  * New keywords are inserted at the end. Existing keywords will be updated with new value if
478  * keyword type and name is the same.
479  *
480  * @param to Keywords to update to.
481  * @param from Keywords to update from.
482  * @param policy Determines what to do for a keyword conflict.
483  *
484  * @ingroup daq_ocm_libfits
485  */
487  KeywordVector const& from,
489 
490 /**
491  * Insert keywords
492  *
493  * Conflicting keywords are deleted from the current position.
494  *
495  * @param position Insert position that must lie within range (to_first, to_last]
496  * @param to_first Start of range to consider when deleting duplicate keywords.
497  * @param to_last End of range to consider when deleting duplicate keywords.
498  * @param from_first Range to insert from.
499  * @param from_last Range to insert from.
500  * @param from Keywords to update from.
501  *
502  * @ingroup daq_ocm_libfits
503  */
504 void InsertKeywords(KeywordVector& keywords,
505  KeywordVector::iterator position,
506  KeywordVector::const_iterator from_first,
507  KeywordVector::const_iterator from_last);
508 
509 /**
510  * @name Format keyword using default formatting rules.
511  *
512  * @param keyword Keyword to format.
513  * @throw std::invalid_argument on failure.
514  */
515 /// @{
516 LiteralKeyword Format(KeywordVariant const& keyword);
517 LiteralKeyword Format(ValueKeyword const& keyword);
518 LiteralKeyword Format(EsoKeyword const& keyword);
519 
520 LiteralKeyword Format(LiteralKeyword::Components const& keyword);
521 /// @}
522 
523 /**
524  * Versioned namespace of sorting functions in case standard evolves but instruments want to freeze.
525  */
526 namespace v1 {
527 
528 /**
529  * Sorting function object
530  *
531  * @ingroup daq_ocm_libfits
532  */
533 struct StandardLess {
534  bool operator()(LiteralKeyword const&, LiteralKeyword const&) noexcept;
535 };
536 
537 /**
538  * Sorts keywords according to ESO DICD standards.
539  *
540  * Specifically the following sorting order is applied:
541  *
542  * 1. Value keywords
543  * note: value keywords are not sorted with other value keywords to keep their relative order
544  * from the source they originally come from.
545  * 2. ESO keywords are sorted by
546  * 1. category (DPR, OBS, TPL, GEN, TEL, ADA, INS, DET)
547  * 2. keyword name
548  * 3. Commentary keywords (using only the keyword name)
549  *
550  * @param[in, out] keywords Vector of keywords to sort.
551  *
552  * @ingroup daq_ocm_libfits
553  */
554 void StandardSort(std::vector<LiteralKeyword>& keywords);
555 
556 } // namespace v1
557 } // namespace daq::fits
558 
559 #endif // #ifndef DAQ_OCM_DAQ_KEYWORD_HPP_
Represents the literal 80-character FITS keyword record.
Definition: keyword.hpp:125
std::string_view comment
Comment may be empty.
Definition: keyword.hpp:138
constexpr Components GetComponents() const &noexcept
Get components of the keyword in its literal form with insignifant whitespaces removed.
Definition: keyword.hpp:202
constexpr KeywordType GetType() const noexcept
Definition: keyword.hpp:170
std::string_view GetRecord() const &noexcept
Definition: keyword.cpp:415
KeywordNameView GetName() &&noexcept=delete
constexpr KeywordNameView GetName() const &noexcept
Query logical keyword name.
Definition: keyword.hpp:186
LiteralKeyword() noexcept
Initializes an empty record (filled with ' ' characters)
Definition: keyword.cpp:363
Decomposed components a literal keyword.
Definition: keyword.hpp:130
void StandardSort(std::vector< LiteralKeyword > &keywords)
Sorts keywords according to ESO DICD standards.
Definition: keyword.cpp:720
constexpr KeywordNameView GetKeywordName(EsoKeyword const &keyword) noexcept
Get keyword name from keyword.
Definition: keyword.hpp:427
void InsertKeywords(KeywordVector &keywords, KeywordVector::iterator position, KeywordVector::const_iterator from_first, KeywordVector::const_iterator from_last)
Insert keywords.
Definition: keyword.cpp:570
KeywordType
Type of FITS keyword.
Definition: keyword.hpp:64
@ Eso
An ESO hiearchical keyword.
Definition: keyword.hpp:72
@ Commentary
A commentary keyword, which are keywords that do not fall into the previous categories.
Definition: keyword.hpp:76
@ Value
A value keyword.
Definition: keyword.hpp:68
bool NameEquals(KeywordVariant const &lhs, KeywordVariant const &rhs) noexcept
Compare logical keyword names of keyword of the same type.
Definition: keyword.cpp:478
bool operator==(KeywordNameView lhs, KeywordNameView rhs) noexcept
Definition: keyword.cpp:355
LiteralKeyword Format(KeywordVariant const &keyword)
Definition: keyword.cpp:626
bool operator<(LiteralKeyword const &, LiteralKeyword const &) noexcept
Sort by logical keyword name (not DICD sort)
Definition: keyword.cpp:420
std::variant< ValueKeyword, EsoKeyword > TypedKeywordVariant
Subset of value-typed keywords.
Definition: keyword.hpp:407
KeywordClass GetKeywordClass(std::string_view name)
Get keyword class.
Definition: keyword.cpp:490
KeywordClass
Fits keyword type.
Definition: keyword.hpp:89
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:414
bool operator!=(KeywordNameView lhs, KeywordNameView rhs) noexcept
Definition: keyword.cpp:359
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:400
void UpdateKeywords(KeywordVector &to, KeywordVector const &from, ConflictPolicy policy=ConflictPolicy::Replace)
Updates to with keywords from from.
Definition: keyword.cpp:554
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:330
@ Replace
Replace keyword that conflicts.
@ Skip
Skip keyword that conflicts.
std::ostream & operator<<(std::ostream &os, LiteralKeyword const &kw)
Definition: keyword.cpp:432
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:337
Non template base class that is purely used to avoid type-deduction issues of ValueType.
Definition: keyword.hpp:241
std::variant< std::string, int64_t, uint64_t, double, bool > ValueType
Definition: keyword.hpp:243
A type safe version of LiteralKeyword that consist of the three basic components of a FITS keyword ke...
Definition: keyword.hpp:266
bool operator==(BasicKeyword const &rhs) const noexcept
Compares all members for equality.
Definition: keyword.cpp:464
bool operator!=(BasicKeyword const &rhs) const noexcept
Compares all members for inequality.
Definition: keyword.cpp:469
BasicKeyword(BasicKeyword const &)=default
bool operator<(BasicKeyword const &rhs) const noexcept
Uses name property as the sorting index.
Definition: keyword.cpp:474
KeywordNameView GetName() &&=delete
BasicKeyword(BasicKeyword &&) noexcept=default
std::string name
Trimmed keyword name.
Definition: keyword.hpp:306
std::optional< std::string > comment
Trimmed keyword comment.
Definition: keyword.hpp:311
constexpr KeywordNameView GetName() const &noexcept
Query logical keyword name.
Definition: keyword.hpp:285
static constexpr KeywordType GetKeywordType() noexcept
Definition: keyword.hpp:320
std::string_view name
Definition: keyword.hpp:80
static constexpr KeywordType GetKeywordType() noexcept
Definition: keyword.hpp:315
Sorting function object.
Definition: keyword.hpp:533
bool operator()(LiteralKeyword const &, LiteralKeyword const &) noexcept
Definition: keyword.cpp:677