LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
FitsSchemaInputMapper.cc
Go to the documentation of this file.
1 // -*- lsst-c++ -*-
2 
3 #include <array>
4 #include <cmath>
5 #include <cstdint>
6 #include <cstdio>
7 #include <string>
8 #include <algorithm>
9 #include <cctype>
10 
11 #include "boost/regex.hpp"
12 #include "boost/multi_index_container.hpp"
13 #include "boost/multi_index/sequenced_index.hpp"
14 #include "boost/multi_index/ordered_index.hpp"
15 #include "boost/multi_index/hashed_index.hpp"
16 #include "boost/multi_index/member.hpp"
17 
18 #include "lsst/log/Log.h"
19 #include "lsst/geom.h"
22 
23 namespace lsst {
24 namespace afw {
25 namespace table {
26 namespace io {
27 
28 namespace {
29 
30 // A quirk of Boost.MultiIndex (which we use for our container of FitsSchemaItems)
31 // that you have to use a special functor (like this one) to set data members
32 // in a container with set indices (because setting those values might require
33 // the element to be moved to a different place in the set). Check out
34 // the Boost.MultiIndex docs for more information.
35 template <std::string FitsSchemaItem::*Member>
36 struct SetFitsSchemaString {
37  void operator()(FitsSchemaItem &item) { item.*Member = _v; }
38  explicit SetFitsSchemaString(std::string const &v) : _v(v) {}
39 
40 private:
41  std::string const &_v;
42 };
43 
44 } // namespace
45 
47 public:
48  // A container class (based on Boost.MultiIndex) that provides three sort orders,
49  // on column number, flag bit, and name (ttype). This allows us to insert fields into the
50  // schema in the correct order, regardless of which order they appear in the
51  // FITS header.
52  typedef boost::multi_index_container<
54  boost::multi_index::indexed_by<
55  boost::multi_index::ordered_non_unique<
56  boost::multi_index::member<FitsSchemaItem, int, &FitsSchemaItem::column>>,
57  boost::multi_index::ordered_non_unique<
58  boost::multi_index::member<FitsSchemaItem, int, &FitsSchemaItem::bit>>,
59  boost::multi_index::hashed_unique<
60  boost::multi_index::member<FitsSchemaItem, std::string, &FitsSchemaItem::ttype>>,
61  boost::multi_index::sequenced<>>>
63 
64  // Typedefs for the special functors used to set data members.
65  typedef SetFitsSchemaString<&FitsSchemaItem::ttype> SetTTYPE;
66  typedef SetFitsSchemaString<&FitsSchemaItem::tform> SetTFORM;
67  typedef SetFitsSchemaString<&FitsSchemaItem::tccls> SetTCCLS;
68  typedef SetFitsSchemaString<&FitsSchemaItem::tunit> SetTUNIT;
69  typedef SetFitsSchemaString<&FitsSchemaItem::doc> SetDoc;
70 
71  // Typedefs for the different indices.
76 
77  // Getters for the different indices.
78  ByColumn &byColumn() { return inputs.get<0>(); }
79  ByBit &byBit() { return inputs.get<1>(); }
80  ByName &byName() { return inputs.get<2>(); }
81  AsList &asList() { return inputs.get<3>(); }
82 
83  Impl() : version(0), flagColumn(0), archiveHdu(-1) {}
84 
85  int version;
95  std::size_t nRowsToPrep = 1;
96 };
97 
98 std::size_t FitsSchemaInputMapper::PREPPED_ROWS_FACTOR = 1 << 15; // determined empirically; see DM-19461.
99 
101  : _impl(std::make_shared<Impl>()) {
102  // Set the table version. If AFW_TABLE_VERSION tag exists, use that
103  // If not, set to 0 if it has an AFW_TYPE, Schema default otherwise (DM-590)
104  if (!metadata.exists("AFW_TYPE")) {
105  _impl->version = lsst::afw::table::Schema::VERSION;
106  }
107  _impl->version = metadata.get("AFW_TABLE_VERSION", _impl->version);
108  _impl->type = metadata.get("AFW_TYPE", _impl->type);
109  if (stripMetadata) {
110  metadata.remove("AFW_TABLE_VERSION");
111  }
112  if (stripMetadata) {
113  metadata.remove("AFW_TYPE");
114  }
115 
116  // Find a key that indicates an Archive stored on other HDUs
117  _impl->archiveHdu = metadata.get("AR_HDU", -1);
118  if (_impl->archiveHdu > 0) {
119  --_impl->archiveHdu; // AR_HDU is 1-indexed for historical reasons (RFC-304; see Source.cc)
120  if (stripMetadata) {
121  metadata.remove("AR_HDU");
122  }
123  }
124 
125  // Read aliases, stored as header entries with key 'ALIAS'
126  try {
127  std::vector<std::string> rawAliases = metadata.getArray<std::string>("ALIAS");
128  for (std::vector<std::string>::const_iterator i = rawAliases.begin(); i != rawAliases.end(); ++i) {
129  std::size_t pos = i->find_first_of(':');
130  if (pos == std::string::npos) {
132  (boost::format("Malformed alias definition: '%s'") % (*i)).str());
133  }
134  _impl->schema.getAliasMap()->set(i->substr(0, pos), i->substr(pos + 1, std::string::npos));
135  }
136  if (stripMetadata) {
137  metadata.remove("ALIAS");
138  }
139  } catch (pex::exceptions::NotFoundError &) {
140  // if there are no aliases, just move on
141  }
142 
143  if (_impl->version == 0) {
144  // Read slots saved using an old mechanism in as aliases, since the new slot mechanism delegates
145  // slot definition to the AliasMap.
146  static std::array<std::pair<std::string, std::string>, 7> oldSlotKeys = {
147  {std::make_pair("PSF_FLUX", "slot_PsfFlux"), std::make_pair("AP_FLUX", "slot_ApFlux"),
148  std::make_pair("INST_FLUX", "slot_GaussianFlux"),
149  std::make_pair("MODEL_FLUX", "slot_ModelFlux"),
150  std::make_pair("CALIB_FLUX", "slot_CalibFlux"), std::make_pair("CENTROID", "slot_Centroid"),
151  std::make_pair("SHAPE", "slot_Shape")}};
152  for (std::size_t i = 0; i < oldSlotKeys.size(); ++i) {
153  std::string target = metadata.get(oldSlotKeys[i].first + "_SLOT", std::string(""));
154  if (!target.empty()) {
155  _impl->schema.getAliasMap()->set(oldSlotKeys[i].second, target);
156  if (stripMetadata) {
157  metadata.remove(oldSlotKeys[i].first);
158  metadata.remove(oldSlotKeys[i].first + "_ERR_SLOT");
159  metadata.remove(oldSlotKeys[i].first + "_FLAG_SLOT");
160  }
161  }
162  }
163  }
164 
165  // Read the rest of the header into the intermediate inputs container.
166  std::vector<std::string> keyList = metadata.getOrderedNames();
167  for (std::vector<std::string>::const_iterator key = keyList.begin(); key != keyList.end(); ++key) {
168  if (key->compare(0, 5, "TTYPE") == 0) {
169  int column = std::stoi(key->substr(5)) - 1;
170  auto iter = _impl->byColumn().lower_bound(column);
171  if (iter == _impl->byColumn().end() || iter->column != column) {
172  iter = _impl->byColumn().insert(iter, FitsSchemaItem(column, -1));
173  }
174  std::string v = metadata.get<std::string>(*key);
175  _impl->byColumn().modify(iter, Impl::SetTTYPE(v));
176  if (iter->doc.empty()) { // don't overwrite if already set with TDOCn
177  _impl->byColumn().modify(iter, Impl::SetDoc(metadata.getComment(*key)));
178  }
179  if (stripMetadata) {
180  metadata.remove(*key);
181  }
182  } else if (key->compare(0, 5, "TFLAG") == 0) {
183  int bit = std::stoi(key->substr(5)) - 1;
184  auto iter = _impl->byBit().lower_bound(bit);
185  if (iter == _impl->byBit().end() || iter->bit != bit) {
186  iter = _impl->byBit().insert(iter, FitsSchemaItem(-1, bit));
187  }
188  std::string v = metadata.get<std::string>(*key);
189  _impl->byBit().modify(iter, Impl::SetTTYPE(v));
190  if (iter->doc.empty()) { // don't overwrite if already set with TFDOCn
191  _impl->byBit().modify(iter, Impl::SetDoc(metadata.getComment(*key)));
192  }
193  if (stripMetadata) {
194  metadata.remove(*key);
195  }
196  } else if (key->compare(0, 4, "TDOC") == 0) {
197  int column = std::stoi(key->substr(4)) - 1;
198  auto iter = _impl->byColumn().lower_bound(column);
199  if (iter == _impl->byColumn().end() || iter->column != column) {
200  iter = _impl->byColumn().insert(iter, FitsSchemaItem(column, -1));
201  }
202  _impl->byColumn().modify(iter, Impl::SetDoc(metadata.get<std::string>(*key)));
203  if (stripMetadata) {
204  metadata.remove(*key);
205  }
206  } else if (key->compare(0, 5, "TFDOC") == 0) {
207  int bit = std::stoi(key->substr(5)) - 1;
208  auto iter = _impl->byBit().lower_bound(bit);
209  if (iter == _impl->byBit().end() || iter->bit != bit) {
210  iter = _impl->byBit().insert(iter, FitsSchemaItem(-1, bit));
211  }
212  _impl->byBit().modify(iter, Impl::SetDoc(metadata.get<std::string>(*key)));
213  if (stripMetadata) {
214  metadata.remove(*key);
215  }
216  } else if (key->compare(0, 5, "TUNIT") == 0) {
217  int column = std::stoi(key->substr(5)) - 1;
218  auto iter = _impl->byColumn().lower_bound(column);
219  if (iter == _impl->byColumn().end() || iter->column != column) {
220  iter = _impl->byColumn().insert(iter, FitsSchemaItem(column, -1));
221  }
222  _impl->byColumn().modify(iter, Impl::SetTUNIT(metadata.get<std::string>(*key)));
223  if (stripMetadata) {
224  metadata.remove(*key);
225  }
226  } else if (key->compare(0, 5, "TCCLS") == 0) {
227  int column = std::stoi(key->substr(5)) - 1;
228  auto iter = _impl->byColumn().lower_bound(column);
229  if (iter == _impl->byColumn().end() || iter->column != column) {
230  iter = _impl->byColumn().insert(iter, FitsSchemaItem(column, -1));
231  }
232  _impl->byColumn().modify(iter, Impl::SetTCCLS(metadata.get<std::string>(*key)));
233  if (stripMetadata) {
234  metadata.remove(*key);
235  }
236  } else if (key->compare(0, 5, "TFORM") == 0) {
237  int column = std::stoi(key->substr(5)) - 1;
238  auto iter = _impl->byColumn().lower_bound(column);
239  if (iter == _impl->byColumn().end() || iter->column != column) {
240  iter = _impl->byColumn().insert(iter, FitsSchemaItem(column, -1));
241  }
242  _impl->byColumn().modify(iter, Impl::SetTFORM(metadata.get<std::string>(*key)));
243  if (stripMetadata) {
244  metadata.remove(*key);
245  }
246  } else if (key->compare(0, 5, "TZERO") == 0) {
247  if (stripMetadata) {
248  metadata.remove(*key);
249  }
250  } else if (key->compare(0, 5, "TSCAL") == 0) {
251  if (stripMetadata) {
252  metadata.remove(*key);
253  }
254  } else if (key->compare(0, 5, "TNULL") == 0) {
255  if (stripMetadata) {
256  metadata.remove(*key);
257  }
258  } else if (key->compare(0, 5, "TDISP") == 0) {
259  if (stripMetadata) {
260  metadata.remove(*key);
261  }
262  }
263  }
264 
265  // Find the column used to store flags, and setup the flag-handling data members from it.
266  _impl->flagColumn = metadata.get("FLAGCOL", 0);
267  if (_impl->flagColumn > 0) {
268  if (stripMetadata) {
269  metadata.remove("FLAGCOL");
270  }
271  --_impl->flagColumn; // switch from 1-indexed to 0-indexed
272  auto iter = _impl->byColumn().find(_impl->flagColumn);
273  if (iter == _impl->byColumn().end()) {
274  throw LSST_EXCEPT(
276  (boost::format("Column for flag data not found; FLAGCOL=%d") % _impl->flagColumn).str());
277  }
278  // Regex to unpack a FITS TFORM value for a bit array column (TFORM code 'X'). The number
279  // that precedes the code is the size of the array; the number that follows it (if present)
280  // is ignored.
281  static boost::regex const regex("(\\d+)?X\\(?(\\d)*\\)?", boost::regex::perl);
282  boost::smatch m;
283  if (!boost::regex_match(iter->tform, m, regex)) {
284  throw LSST_EXCEPT(
286  (boost::format("Invalid TFORM key for flags column: '%s'") % iter->tform).str());
287  }
288  int nFlags = 1;
289  if (m[1].matched) {
290  nFlags = std::stoi(m[1].str());
291  }
292  _impl->flagKeys.resize(nFlags);
293  _impl->flagWorkspace.reset(new bool[nFlags]);
294  // Delete the flag column from the input list so we don't interpret it as a
295  // regular field.
296  _impl->byColumn().erase(iter);
297  }
298 }
299 
305 
306 void FitsSchemaInputMapper::setArchive(std::shared_ptr<InputArchive> archive) { _impl->archive = archive; }
307 
309  int oldHdu = fits.getHdu();
310  if (_impl->archiveHdu < 0) _impl->archiveHdu = oldHdu + 1;
311  try {
312  fits.setHdu(_impl->archiveHdu);
313  _impl->archive.reset(new io::InputArchive(InputArchive::readFits(fits)));
314  fits.setHdu(oldHdu);
315  return true;
316  } catch (afw::fits::FitsError &) {
317  fits.status = 0;
318  fits.setHdu(oldHdu);
319  _impl->archiveHdu = -1;
320  return false;
321  }
322 }
323 
324 bool FitsSchemaInputMapper::hasArchive() const { return static_cast<bool>(_impl->archive); }
325 
327  auto iter = _impl->byName().find(ttype);
328  if (iter == _impl->byName().end()) {
329  return 0;
330  }
331  return &(*iter);
332 }
333 
334 FitsSchemaItem const *FitsSchemaInputMapper::find(int column) const {
335  auto iter = _impl->byColumn().lower_bound(column);
336  if (iter == _impl->byColumn().end() || iter->column != column) {
337  return 0;
338  }
339  return &(*iter);
340 }
341 
343  auto iter = _impl->byColumn().lower_bound(item->column);
344  assert(iter != _impl->byColumn().end() && iter->column == item->column);
345  _impl->byColumn().erase(iter);
346 }
347 
349  auto iter = _impl->byName().find(ttype);
350  if (iter != _impl->byName().end() && iter->ttype == ttype) {
351  _impl->byName().erase(iter);
352  }
353 }
354 
356  auto iter = _impl->byColumn().lower_bound(column);
357  if (iter != _impl->byColumn().end() && iter->column == column) {
358  _impl->byColumn().erase(iter);
359  }
360 }
361 
362 void erase(int column);
363 
365  _impl->readers.push_back(std::move(reader));
366 }
367 
368 namespace {
369 
370 template <typename T>
371 class StandardReader : public FitsColumnReader {
372 public:
374  FieldBase<T> const &base = FieldBase<T>()) {
375  return std::unique_ptr<FitsColumnReader>(new StandardReader(schema, item, base));
376  }
377 
378  StandardReader(Schema &schema, FitsSchemaItem const &item, FieldBase<T> const &base)
379  : _column(item.column), _key(schema.addField<T>(item.ttype, item.doc, item.tunit, base)),
380  _cache(), _cacheFirstRow(0)
381  {}
382 
383  void prepRead(std::size_t firstRow, std::size_t nRows, fits::Fits & fits) override {
384  // We only prep and cache scalar-valued columns, not array-valued
385  // columns, as apparently the order CFITSIO reads array-valued columns
386  // is not the order we want.
387  if (_key.getElementCount() == 1u) {
388  std::size_t nElements = nRows*_key.getElementCount();
389  _cache.resize(nElements);
390  _cacheFirstRow = firstRow;
391  fits.readTableArray(firstRow, _column, nElements, &_cache.front());
392  }
393  }
394 
395  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
396  std::shared_ptr<InputArchive> const &archive) const override {
397  if (_cache.empty()) {
398  fits.readTableArray(row, _column, _key.getElementCount(), record.getElement(_key));
399  } else {
400  assert(row >= _cacheFirstRow);
401  std::size_t offset = row - _cacheFirstRow;
402  assert(offset < _cache.size());
403  std::copy_n(_cache.begin() + offset, _key.getElementCount(), record.getElement(_key));
404  }
405  }
406 
407 private:
408  int _column;
409  Key<T> _key;
411  std::size_t _cacheFirstRow;
412  std::size_t _nRowsToPrep;
413 };
414 
415 class AngleReader : public FitsColumnReader {
416 public:
418  Schema &schema, FitsSchemaItem const &item,
420  return std::unique_ptr<FitsColumnReader>(new AngleReader(schema, item, base));
421  }
422 
423  AngleReader(Schema &schema, FitsSchemaItem const &item, FieldBase<lsst::geom::Angle> const &base)
424  : _column(item.column), _key(schema.addField<lsst::geom::Angle>(item.ttype, item.doc, "", base)) {
425  // We require an LSST-specific key in the headers before parsing a column
426  // as Angle at all, so we don't need to worry about other units or other
427  // spellings of radians. We do continue to support no units for backwards
428  // compatibility.
429  if (!item.tunit.empty() && item.tunit != "rad") {
431  "Angle fields must be persisted in radians (TUNIT='rad').");
432  }
433  }
434 
435  void prepRead(std::size_t firstRow, std::size_t nRows, fits::Fits & fits) override {
436  assert(_key.getElementCount() == 1u);
437  _cache.resize(nRows);
438  _cacheFirstRow = firstRow;
439  fits.readTableArray(firstRow, _column, nRows, &_cache.front());
440  }
441 
442  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
443  std::shared_ptr<InputArchive> const &archive) const override {
444  if (_cache.empty()) {
445  double tmp = 0;
446  fits.readTableScalar(row, _column, tmp);
447  record.set(_key, tmp * lsst::geom::radians);
448  } else {
449  assert(row >= _cacheFirstRow);
450  std::size_t offset = row - _cacheFirstRow;
451  assert(offset < _cache.size());
452  record.set(_key, _cache[offset] * lsst::geom::radians);
453  }
454  }
455 
456 private:
457  int _column;
459  std::vector<double> _cache;
460  std::size_t _cacheFirstRow;
461 };
462 
463 class StringReader : public FitsColumnReader {
464 public:
465  static std::unique_ptr<FitsColumnReader> make(Schema &schema, FitsSchemaItem const &item, int size) {
466  return std::unique_ptr<FitsColumnReader>(new StringReader(schema, item, size));
467  }
468 
469  StringReader(Schema &schema, FitsSchemaItem const &item, int size)
470  : _column(item.column),
471  _key(schema.addField<std::string>(item.ttype, item.doc, item.tunit, size)),
472  _isVariableLength(size == 0) {}
473 
474  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
475  std::shared_ptr<InputArchive> const &archive) const override {
476  std::string s;
477  fits.readTableScalar(row, _column, s, _isVariableLength);
478  record.set(_key, s);
479  }
480 
481 private:
482  int _column;
483  Key<std::string> _key;
484  bool _isVariableLength;
485 };
486 
487 template <typename T>
488 class VariableLengthArrayReader : public FitsColumnReader {
489 public:
491  return std::unique_ptr<FitsColumnReader>(new VariableLengthArrayReader(schema, item));
492  }
493 
494  VariableLengthArrayReader(Schema &schema, FitsSchemaItem const &item)
495  : _column(item.column), _key(schema.addField<Array<T>>(item.ttype, item.doc, item.tunit, 0)) {}
496 
497  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
498  std::shared_ptr<InputArchive> const &archive) const override {
499  int size = fits.getTableArraySize(row, _column);
500  ndarray::Array<T, 1, 1> array = ndarray::allocate(size);
501  fits.readTableArray(row, _column, size, array.getData());
502  record.set(_key, array);
503  }
504 
505 private:
506  int _column;
507  Key<Array<T>> _key;
508 };
509 
510 // Read a 2-element FITS array column as separate x and y Schema fields (hence converting
511 // from the old Point compound field to the new PointKey FunctorKey).
512 template <typename T>
513 class PointConversionReader : public FitsColumnReader {
514 public:
516  return std::unique_ptr<FitsColumnReader>(new PointConversionReader(schema, item));
517  }
518 
519  PointConversionReader(Schema &schema, FitsSchemaItem const &item)
520  : _column(item.column), _key(PointKey<T>::addFields(schema, item.ttype, item.doc, item.tunit)) {}
521 
522  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
523  std::shared_ptr<InputArchive> const &archive) const override {
524  std::array<T, 2> buffer;
525  fits.readTableArray(row, _column, 2, buffer.data());
526  record.set(_key, lsst::geom::Point<T, 2>(buffer[0], buffer[1]));
527  }
528 
529 private:
530  int _column;
531  PointKey<T> _key;
532 };
533 
534 // Read a 2-element FITS array column as separate ra and dec Schema fields (hence converting
535 // from the old Coord compound field to the new CoordKey FunctorKey).
536 class CoordConversionReader : public FitsColumnReader {
537 public:
539  return std::unique_ptr<FitsColumnReader>(new CoordConversionReader(schema, item));
540  }
541 
542  CoordConversionReader(Schema &schema, FitsSchemaItem const &item)
543  : _column(item.column), _key(CoordKey::addFields(schema, item.ttype, item.doc)) {}
544 
545  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
546  std::shared_ptr<InputArchive> const &archive) const override {
548  fits.readTableArray(row, _column, 2, buffer.data());
549  record.set(_key, lsst::geom::SpherePoint(buffer[0], buffer[1]));
550  }
551 
552 private:
553  int _column;
554  CoordKey _key;
555 };
556 
557 // Read a 3-element FITS array column as separate xx, yy, and xy Schema fields (hence converting
558 // from the old Moments compound field to the new QuadrupoleKey FunctorKey).
559 class MomentsConversionReader : public FitsColumnReader {
560 public:
562  return std::unique_ptr<FitsColumnReader>(new MomentsConversionReader(schema, item));
563  }
564 
565  MomentsConversionReader(Schema &schema, FitsSchemaItem const &item)
566  : _column(item.column),
567  _key(QuadrupoleKey::addFields(schema, item.ttype, item.doc, CoordinateType::PIXEL)) {}
568 
569  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
570  std::shared_ptr<InputArchive> const &archive) const override {
571  std::array<double, 3> buffer;
572  fits.readTableArray(row, _column, 3, buffer.data());
573  record.set(_key, geom::ellipses::Quadrupole(buffer[0], buffer[1], buffer[2], false));
574  }
575 
576 private:
577  int _column;
578  QuadrupoleKey _key;
579 };
580 
581 // Read a FITS array column representing a packed symmetric matrix into
582 // Schema fields for each element (hence converting from the old Covariance
583 // compound field to the new CovarianceMatrixKey FunctorKey).
584 template <typename T, int N>
585 class CovarianceConversionReader : public FitsColumnReader {
586 public:
587  static std::string guessUnits(std::string const &oldUnits) {
588  static boost::regex const regex("(.*)(\\^(\\d+))?", boost::regex::perl);
589  boost::smatch m;
590  if (!boost::regex_match(oldUnits, m, regex)) {
591  int oldPower = std::stoi(m[2]);
592  int newPower = std::sqrt(oldPower);
593  return std::to_string(newPower);
594  }
595  return oldUnits;
596  }
597 
599  std::vector<std::string> const &names) {
600  return std::unique_ptr<FitsColumnReader>(new CovarianceConversionReader(schema, item, names));
601  }
602 
603  CovarianceConversionReader(Schema &schema, FitsSchemaItem const &item,
604  std::vector<std::string> const &names)
605  : _column(item.column),
606  _size(names.size()),
607  _key(CovarianceMatrixKey<T, N>::addFields(schema, item.ttype, names, guessUnits(item.tunit))),
608  _buffer(new T[detail::computeCovariancePackedSize(names.size())]) {}
609 
610  void readCell(BaseRecord &record, std::size_t row, afw::fits::Fits &fits,
611  std::shared_ptr<InputArchive> const &archive) const override {
612  fits.readTableArray(row, _column, detail::computeCovariancePackedSize(_size), _buffer.get());
613  for (int i = 0; i < _size; ++i) {
614  for (int j = i; j < _size; ++j) {
615  _key.setElement(record, i, j, _buffer[detail::indexCovariance(i, j)]);
616  }
617  }
618  }
619 
620 private:
621  int _column;
622  int _size;
624  std::unique_ptr<T[]> _buffer;
625 };
626 
627 std::unique_ptr<FitsColumnReader> makeColumnReader(Schema &schema, FitsSchemaItem const &item) {
628  // Regex to unpack a FITS TFORM value. The first number is the size of the array (1 if not present),
629  // followed by an alpha code indicating the type (preceded by P or Q for variable size array).
630  // The last number is ignored.
631  static boost::regex const regex("(\\d+)?([PQ])?(\\u)\\(?(\\d)*\\)?", boost::regex::perl);
632  // start by parsing the format; this tells the element type of the field and the number of elements
633  boost::smatch m;
634  if (!boost::regex_match(item.tform, m, regex)) {
636  }
637  int size = 1;
638  if (m[1].matched) {
639  size = std::stoi(m[1].str());
640  }
641  char code = m[3].str()[0];
642  if (m[2].matched) {
643  // P or Q presence indicates a variable-length array, which we can get by just setting the
644  // size to zero and letting the rest of the logic run its course.
645  size = 0;
646  }
647  // switch code over FITS codes that correspond to different element types
648  switch (code) {
649  case 'B': // 8-bit unsigned integers -- can only be scalars or Arrays
650  if (size == 1) {
651  if (item.tccls == "Array") {
652  return StandardReader<Array<std::uint8_t>>::make(schema, item, size);
653  }
654  return StandardReader<std::uint8_t>::make(schema, item);
655  }
656  if (size == 0) {
657  return VariableLengthArrayReader<std::uint8_t>::make(schema, item);
658  }
659  return StandardReader<Array<std::uint8_t>>::make(schema, item, size);
660 
661  case 'I': // 16-bit integers - can only be scalars or Arrays (we assume they're unsigned, since
662  // that's all we ever write, and CFITSIO will complain later if they aren't)
663  if (size == 1) {
664  if (item.tccls == "Array") {
665  return StandardReader<Array<std::uint16_t>>::make(schema, item, size);
666  }
667  return StandardReader<std::uint16_t>::make(schema, item);
668  }
669  if (size == 0) {
670  return VariableLengthArrayReader<std::uint16_t>::make(schema, item);
671  }
672  return StandardReader<Array<std::uint16_t>>::make(schema, item, size);
673  case 'J': // 32-bit integers - can only be scalars, Point fields, or Arrays
674  if (size == 0) {
675  return VariableLengthArrayReader<std::int32_t>::make(schema, item);
676  }
677  if (item.tccls == "Point") {
678  return PointConversionReader<std::int32_t>::make(schema, item);
679  }
680  if (size > 1 || item.tccls == "Array") {
681  return StandardReader<Array<std::int32_t>>::make(schema, item, size);
682  }
683  return StandardReader<std::int32_t>::make(schema, item);
684  case 'K': // 64-bit integers - can only be scalars.
685  if (size == 1) {
686  return StandardReader<std::int64_t>::make(schema, item);
687  }
688  case 'E': // floats
689  if (size == 0) {
690  return VariableLengthArrayReader<float>::make(schema, item);
691  }
692  if (size == 1) {
693  if (item.tccls == "Array") {
694  return StandardReader<Array<float>>::make(schema, item, 1);
695  }
696  // Just use scalars for Covariances of size 1, since that results in more
697  // natural field names (essentially never happens anyway).
698  return StandardReader<float>::make(schema, item);
699  }
700  if (size == 3 && item.tccls == "Covariance(Point)") {
701  std::vector<std::string> names = {"x", "y"};
702  return CovarianceConversionReader<float, 2>::make(schema, item, names);
703  }
704  if (size == 6 && item.tccls == "Covariance(Moments)") {
705  std::vector<std::string> names = {"xx", "yy", "xy"};
706  return CovarianceConversionReader<float, 3>::make(schema, item, names);
707  }
708  if (item.tccls == "Covariance") {
709  double v = 0.5 * (std::sqrt(1 + 8 * size) - 1);
710  int n = std::lround(v);
711  if (n * (n + 1) != size * 2) {
712  throw LSST_EXCEPT(afw::fits::FitsError, "Covariance field has invalid size.");
713  }
714  std::vector<std::string> names(n);
715  for (int i = 0; i < n; ++i) {
716  names[i] = std::to_string(i);
717  }
718  return CovarianceConversionReader<float, Eigen::Dynamic>::make(schema, item, names);
719  }
720  return StandardReader<Array<float>>::make(schema, item, size);
721  case 'D': // doubles
722  if (size == 0) {
723  return VariableLengthArrayReader<double>::make(schema, item);
724  }
725  if (size == 1) {
726  if (item.tccls == "Angle") {
727  return AngleReader::make(schema, item);
728  }
729  if (item.tccls == "Array") {
730  return StandardReader<Array<double>>::make(schema, item, 1);
731  }
732  return StandardReader<double>::make(schema, item);
733  }
734  if (size == 2) {
735  if (item.tccls == "Point") {
736  return PointConversionReader<double>::make(schema, item);
737  }
738  if (item.tccls == "Coord") {
739  return CoordConversionReader::make(schema, item);
740  }
741  }
742  if (size == 3 && item.tccls == "Moments") {
743  return MomentsConversionReader::make(schema, item);
744  }
745  return StandardReader<Array<double>>::make(schema, item, size);
746  case 'A': // strings
747  // StringReader can read both fixed-length and variable-length (size=0) strings
748  return StringReader::make(schema, item, size);
749  default:
751  }
752 }
753 
754 bool endswith(std::string const &s, std::string const &suffix) {
755  return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
756 }
757 
758 bool isInstFlux(FitsSchemaItem const & item) {
759  // helper lambda to make reading the real logic easier
760  auto includes = [](std::string const & s, char const * target) {
761  return s.find(target) != std::string::npos;
762  };
763  if (!includes(item.ttype, "flux")) return false;
764  if (includes(item.ttype, "modelfit_CModel") && item.tunit.empty()) {
765  // CModel flux fields were written with no units prior to DM-16068,
766  // but should have been "count".
767  return true;
768  }
769  // transform units to lowercase.
770  std::string units(item.tunit);
771  std::transform(units.begin(), units.end(), units.begin(), [](char c) { return std::tolower(c); } );
772  return includes(units, "count") || includes(units, "dn") || includes (units, "adu");
773 }
774 
775 // Replace 'from' with 'to' in 'full', returning the result.
776 std::string replace(std::string full, std::string const & from, std::string const & to) {
777  return full.replace(full.find(from), from.size(), to);
778 }
779 
780 } // namespace
781 
783  if (_impl->version == 0) {
784  AliasMap &aliases = *_impl->schema.getAliasMap();
785  for (auto iter = _impl->asList().begin(); iter != _impl->asList().end(); ++iter) {
786  std::size_t flagPos = iter->ttype.find("flags");
787  if (flagPos != std::string::npos) {
788  // We want to create aliases that resolve "(.*)_flag" to "$1_flags"; old schemas will have
789  // the latter, but new conventions (including slots) expect the former.
790  // But we can't do that, because adding that alias directly results in a cycle in the
791  // aliases (since aliases do partial matches, and keep trying until there are no matches,
792  // we'd have "(.*)_flag" resolve to "$1_flagssssssssssssss...").
793  // Instead, we *rename* from "flags" to "flag", then create the reverse alias.
794  std::string ttype = iter->ttype;
795  std::string prefix = iter->ttype.substr(0, flagPos);
796  ttype.replace(flagPos, 5, "flag");
797  _impl->asList().modify(iter, Impl::SetTTYPE(ttype));
798  // Note that we're not aliasing the full field, just the first part - if we have multiple
799  // flag fields, one alias should be sufficient for all of them (because of partial matching).
800  // Of course, we'll try to recreate that alias every time we handle another flag field with
801  // the same prefix, but AliasMap know hows to handle that no-op set.
802  aliases.set(prefix + "flags", prefix + "flag");
803  } else if (isInstFlux(*iter)) {
804  // Create an alias that resolves "X_instFlux" to "X" or "X_instFluxErr" to "X_err".
805  if (endswith(iter->ttype, "_err")) {
806  aliases.set(replace(iter->ttype, "_err", "_instFluxErr"), iter->ttype);
807  } else {
808  aliases.set(iter->ttype + "_instFlux", iter->ttype);
809  }
810  } else if (endswith(iter->ttype, "_err")) {
811  // Create aliases that resolve "(.*)_(.*)Err" and "(.*)_(.*)_(.*)_Cov" to
812  // "$1_err_$2Err" and "$1_err_$2_$3_Cov", to make centroid and shape uncertainties
813  // available under the new conventions. We don't have to create aliases for the
814  // centroid and shape values themselves, as those will automatically be correct
815  // after the PointConversionReader and MomentsConversionReader do their work.
816  if (iter->tccls == "Covariance(Point)") {
817  aliases.set(replace(iter->ttype, "_err", "_yErr"), iter->ttype + "_yErr");
818  aliases.set(replace(iter->ttype, "_err", "_xErr"), iter->ttype + "_xErr");
819  aliases.set(replace(iter->ttype, "_err", "_x_y_Cov"), iter->ttype + "_x_y_Cov");
820  } else if (iter->tccls == "Covariance(Moments)") {
821  aliases.set(replace(iter->ttype, "_err", "_xxErr"), iter->ttype + "_xxErr");
822  aliases.set(replace(iter->ttype, "_err", "_yyErr"), iter->ttype + "_yyErr");
823  aliases.set(replace(iter->ttype, "_err", "_xyErr"), iter->ttype + "_xyErr");
824  aliases.set(replace(iter->ttype, "_err", "_xx_yy_Cov"), iter->ttype + "_xx_yy_Cov");
825  aliases.set(replace(iter->ttype, "_err", "_xx_xy_Cov"), iter->ttype + "_xx_xy_Cov");
826  aliases.set(replace(iter->ttype, "_err", "_yy_xy_Cov"), iter->ttype + "_yy_xy_Cov");
827  }
828  }
829  }
830  } else if (_impl->version < 3) {
831  // Version == 1 tables use Sigma when we should use Err (see RFC-333) and had no fields
832  // that should have been named Sigma. So provide aliases xErr -> xSigma.
833  // Version <= 2 tables used _flux when we should use _instFlux (see RFC-322).
834  AliasMap &aliases = *_impl->schema.getAliasMap();
835  for (auto iter = _impl->asList().begin(); iter != _impl->asList().end(); ++iter) {
836  std::string name = iter->ttype;
837  if (_impl->version < 2 && endswith(name, "Sigma")) {
838  name = replace(std::move(name), "Sigma", "Err");
839  }
840  if (_impl->version < 3 && isInstFlux(*iter)) {
841  name = replace(std::move(name), "flux", "instFlux");
842  }
843  if (name != iter->ttype) {
844  aliases.set(name, iter->ttype);
845  }
846  }
847  }
848  for (auto iter = _impl->asList().begin(); iter != _impl->asList().end(); ++iter) {
849  if (iter->bit < 0) { // not a Flag column
850  std::unique_ptr<FitsColumnReader> reader = makeColumnReader(_impl->schema, *iter);
851  if (reader) {
852  _impl->readers.push_back(std::move(reader));
853  } else {
854  LOGLS_WARN("afw.FitsSchemaInputMapper", "Format " << iter->tform << " for column "
855  << iter->ttype
856  << " not supported; skipping.");
857  }
858  } else { // is a Flag column
859  if (static_cast<std::size_t>(iter->bit) >= _impl->flagKeys.size()) {
861  (boost::format("Flag field '%s' is is in bit %d (0-indexed) of only %d") %
862  iter->ttype % iter->bit % _impl->flagKeys.size())
863  .str());
864  }
865  _impl->flagKeys[iter->bit] = _impl->schema.addField<Flag>(iter->ttype, iter->doc);
866  }
867  }
868  _impl->asList().clear();
869  if (_impl->schema.getRecordSize() <= 0) {
870  throw LSST_EXCEPT(
872  (boost::format("Non-positive record size: %d; file is corrupt or invalid.") %
873  _impl->schema.getRecordSize()).str()
874  );
875  }
876  _impl->nRowsToPrep = std::max(PREPPED_ROWS_FACTOR / _impl->schema.getRecordSize(), std::size_t(1));
877  return _impl->schema;
878 }
879 
881  if (!_impl->flagKeys.empty()) {
882  fits.readTableArray<bool>(row, _impl->flagColumn, _impl->flagKeys.size(), _impl->flagWorkspace.get());
883  for (std::size_t bit = 0; bit < _impl->flagKeys.size(); ++bit) {
884  record.set(_impl->flagKeys[bit], _impl->flagWorkspace[bit]);
885  }
886  }
887  if (_impl->nRowsToPrep != 1 && row % _impl->nRowsToPrep == 0) {
888  // Give readers a chance to read and cache up to nRowsToPrep rows-
889  // worth of values.
890  std::size_t size = std::min(_impl->nRowsToPrep, fits.countRows() - row);
891  for (auto & reader : _impl->readers) {
892  reader->prepRead(row, size, fits);
893  }
894  }
895  for (auto const & reader : _impl->readers) {
896  reader->readCell(record, row, fits, _impl->archive);
897  }
898 }
899 } // namespace io
900 } // namespace table
901 } // namespace afw
902 } // namespace lsst
#define LOGLS_WARN(logger, message)
Log a warn-level message using an iostream-based interface.
Definition: Log.h:648
static std::size_t PREPPED_ROWS_FACTOR
When processing each column, divide this number by the record size (in bytes) and ask CFITSIO to read...
An ellipse core with quadrupole moments as parameters.
Definition: Quadrupole.h:47
Defines the fields and offsets for a table.
Definition: Schema.h:50
std::size_t countRows()
Return the number of row in a table.
Definition: fits.cc:1142
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
Field base class default implementation (used for numeric scalars and lsst::geom::Angle).
Definition: FieldBase.h:43
T empty(T... args)
table::Key< table::Array< int > > _size
Definition: PsfexPsf.cc:364
A coordinate class intended to represent absolute positions (2-d specialization). ...
Definition: Point.h:211
Key< Flag > const & target
table::Key< int > from
T tolower(T... args)
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
Reports attempts to exceed implementation-defined length limits for some classes. ...
Definition: Runtime.h:76
std::string const & getComment(std::string const &name) const
Get the comment for a string property name (possibly hierarchical).
Definition: PropertyList.cc:77
std::vector< T > getArray(std::string const &name) const
Get the vector of values for a property name (possibly hierarchical).
Definition: PropertyList.cc:73
SetFitsSchemaString<&FitsSchemaItem::tccls > SetTCCLS
T to_string(T... args)
void set(std::string const &alias, std::string const &target)
Add an alias to the schema or replace an existing one.
Definition: AliasMap.cc:82
void readTableScalar(std::size_t row, int col, T &value)
Read an array scalar from a binary table.
Definition: fits.h:595
Mapping class that holds aliases for a Schema.
Definition: AliasMap.h:36
SetFitsSchemaString<&FitsSchemaItem::doc > SetDoc
STL namespace.
T end(T... args)
A class that describes a mapping from a FITS binary table to an afw::table Schema.
T copy_n(T... args)
A simple struct that combines the two arguments that must be passed to most cfitsio routines and cont...
Definition: fits.h:297
int computeCovariancePackedSize(int size)
Defines the packed size of a covariance matrices.
Definition: FieldBase.h:35
long getTableArraySize(int col)
Return the size of an array column.
Definition: fits.cc:1200
AngleUnit constexpr radians
constant with units of radians
Definition: Angle.h:108
A class representing an angle.
Definition: Angle.h:127
A structure that describes a field as a collection of related strings read from the FITS header...
SetFitsSchemaString<&FitsSchemaItem::tform > SetTFORM
Fits * fits
Definition: FitsWriter.cc:90
STL class.
LSST DM logging module built on log4cxx.
T min(T... args)
virtual void remove(std::string const &name)
Remove all values for a property name (possibly hierarchical).
An exception thrown when problems are found when reading or writing FITS files.
Definition: fits.h:36
Reports attempts to access elements using an invalid key.
Definition: Runtime.h:151
T data(T... args)
table::Key< int > to
A base class for image defects.
A FunctorKey used to get or set a lsst::geom::Point from an (x,y) pair of int or double Keys...
Definition: aggregates.h:49
FitsSchemaInputMapper(daf::base::PropertyList &metadata, bool stripMetadata)
Construct a mapper from a PropertyList of FITS header values, stripping recognized keys if desired...
table::Key< int > type
Definition: Detector.cc:163
T replace(T... args)
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
Add a set of quadrupole subfields to a schema and return a QuadrupoleKey that points to them...
Definition: aggregates.cc:100
std::vector< std::unique_ptr< FitsColumnReader > > readers
table::Schema schema
Definition: Amplifier.cc:115
Key< U > key
Definition: Schema.cc:281
boost::multi_index_container< FitsSchemaItem, boost::multi_index::indexed_by< boost::multi_index::ordered_non_unique< boost::multi_index::member< FitsSchemaItem, int, &FitsSchemaItem::column > >, boost::multi_index::ordered_non_unique< boost::multi_index::member< FitsSchemaItem, int, &FitsSchemaItem::bit > >, boost::multi_index::hashed_unique< boost::multi_index::member< FitsSchemaItem, std::string, &FitsSchemaItem::ttype > >, boost::multi_index::sequenced<> > > InputContainer
int indexCovariance(int i, int j)
Defines the ordering of packed covariance matrices.
Definition: FieldBase.h:32
T make_pair(T... args)
bool exists(std::string const &name) const
Determine if a name (possibly hierarchical) exists.
Tag types used to declare specialized field types.
Definition: misc.h:32
T max(T... args)
T move(T... args)
void setHdu(int hdu, bool relative=false)
Set the current HDU.
Definition: fits.cc:512
Field< T >::Element * getElement(Key< T > const &key)
Return a pointer to the underlying elements of a field (non-const).
Definition: BaseRecord.h:93
T find(T... args)
Definition: __init__.py:1
T size(T... args)
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
STL class.
T get(std::string const &name) const
Get the last value for a property name (possibly hierarchical).
Definition: PropertyList.cc:61
FitsSchemaInputMapper & operator=(FitsSchemaInputMapper const &)
Base class for all records.
Definition: BaseRecord.h:31
static int const VERSION
Definition: Schema.h:56
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
A class used as a handle to a particular field in a table.
Definition: fwd.h:45
Schema finalize()
Map any remaining items into regular Schema items, and return the final Schema.
T begin(T... args)
SetFitsSchemaString<&FitsSchemaItem::tunit > SetTUNIT
static InputArchive readFits(fits::Fits &fitsfile)
Read an object from an already open FITS object.
static CoordKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc)
Add a pair of _ra, _dec fields to a Schema, and return a CoordKey that points to them.
Definition: aggregates.cc:83
STL class.
void readTableArray(std::size_t row, int col, int nElements, T *value)
Read an array value from a binary table.
Definition: fits.cc:1175
std::string prefix
Definition: SchemaMapper.cc:79
bool readArchive(afw::fits::Fits &fits)
Set the Archive by reading from the HDU specified by the AR_HDU header entry.
void set(Key< T > const &key, U const &value)
Set value of a field for the given key.
Definition: BaseRecord.h:164
int m
Definition: SpanSet.cc:49
void erase(Item const *item)
Remove the given item (which should have been retrieved via find()) from the mapping, preventing it from being included in the regular fields added by finalize().
int nFlags
Definition: FitsWriter.cc:91
SetFitsSchemaString<&FitsSchemaItem::ttype > SetTTYPE
void setArchive(std::shared_ptr< InputArchive > archive)
Set the Archive to an externally-provided one, overriding any that may have been read.
T transform(T... args)
T sqrt(T... args)
void customize(std::unique_ptr< FitsColumnReader > reader)
Customize a mapping by providing a FitsColumnReader instance that will be invoked by readRecords()...
A multi-catalog archive object used to load table::io::Persistable objects.
Definition: InputArchive.h:31
Polymorphic reader interface used to read different kinds of objects from one or more FITS binary tab...
bool hasArchive() const
Return true if the mapper has an InputArchive.
int getHdu()
Return the current HDU (0-indexed; 0 is the Primary HDU).
Definition: fits.cc:506
Item const * find(std::string const &ttype) const
Find an item with the given column name (ttype), returning nullptr if no such column exists...
std::vector< std::string > getOrderedNames() const
Get the list of property names, in the order they were added.
Definition: PropertyList.cc:81
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys...
Definition: aggregates.h:282
T stoi(T... args)
Key< T > addField(Field< T > const &field, bool doReplace=false)
Add a new field to the Schema, and return the associated Key.
Definition: Schema.cc:668
T lround(T... args)
T compare(T... args)
A FunctorKey used to get or set celestial coordinates from a pair of lsst::geom::Angle keys...
Definition: aggregates.h:210
int row
Definition: CR.cc:145
void readRecord(BaseRecord &record, afw::fits::Fits &fits, std::size_t row)
Fill a record from a FITS binary table row.