Skip to content

Commit

Permalink
Merge pull request #655 from lsst/tickets/DM-36169
Browse files Browse the repository at this point in the history
DM-36169: ability to read metadata from specific named HDUs
  • Loading branch information
weatherhead99 committed Sep 14, 2022
2 parents 190dffa + 0106e6c commit 6a55182
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 8 deletions.
51 changes: 51 additions & 0 deletions include/lsst/afw/fits.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ struct ImageWriteOptions {
static std::shared_ptr<daf::base::PropertySet> validate(daf::base::PropertySet const& config);
};

/**
* @brief an enum representing the various types of FITS HDU that are available in cfitsio library
*
* This is an int because the value it maps to in cfitsio is also an int.
*/
enum class HduType : int { IMAGE = 0, ASCII_TABLE = 1, BIN_TABLE = 2, ANY = -1 };

/**
* @brief A simple struct that combines the two arguments that must be passed to most cfitsio routines
* and contains thin and/or templated wrappers around common cfitsio routines.
Expand Down Expand Up @@ -329,6 +336,16 @@ class Fits {
*/
void setHdu(int hdu, bool relative = false);

/**
* Set the current HDU using its name, version and type
*
* @param[in] name The name of the HDU to move to
* @param[in] hdutype The type of HDU to match. If not supplied, defaults to ANY_HDU
* @param[in] hduver The value of EXTVER to match. If not supplied, defaults to 0
*
*/
void setHdu(std::string const& name, HduType hdutype = HduType::ANY, int hduver = 0);

/// Return the number of HDUs in the file.
int countHdus();

Expand Down Expand Up @@ -701,6 +718,23 @@ std::shared_ptr<daf::base::PropertyList> combineMetadata(
*/
std::shared_ptr<daf::base::PropertyList> readMetadata(std::string const& fileName, int hdu = DEFAULT_HDU,
bool strip = false);

/** Read FITS header
*
* Includes support for the INHERIT convention: if 'INHERIT = T' is in the header, the
* PHU will be read as well, and nominated HDU will override any duplicated values.
*
* @param fileName the file whose header will be read
* @param hduname the name of the HDU to read
* @param type type of FITS header to match. Defaults to ANY_HDU
* @param hduver version of HDU header to match, defaults to 0 (version ignored)
* @param strip if `true`, common FITS keys that usually have non-metadata intepretations
* (e.g. NAXIS, BITPIX) will be ignored.
*/
std::shared_ptr<daf::base::PropertyList> readMetadata(std::string const& fileName, std::string const& hduname,
HduType type = HduType::ANY, int hduver = 0,
bool strip = false);

/** Read FITS header
*
* Includes support for the INHERIT convention: if 'INHERIT = T' is in the header, the
Expand All @@ -713,6 +747,23 @@ std::shared_ptr<daf::base::PropertyList> readMetadata(std::string const& fileNam
*/
std::shared_ptr<daf::base::PropertyList> readMetadata(fits::MemFileManager& manager, int hdu = DEFAULT_HDU,
bool strip = false);

/** Read FITS header
*
* Includes support for the INHERIT convention: if 'INHERIT = T' is in the header, the
* PHU will be read as well, and nominated HDU will override any duplicated values.
*
* @param manager the in-memory file whose header will be read
* @param hduname the name of the HDU to read
* @param type type of FITS header to match. Defaults to ANY_HDU
* @param hduver version of HDU header to match, defaults to 0 (version ignored)
* @param strip if `true`, common FITS keys that usually have non-metadata intepretations
* (e.g. NAXIS, BITPIX) will be ignored.
*/
std::shared_ptr<daf::base::PropertyList> readMetadata(fits::MemFileManager& manager,
std::string const& hduname, HduType type = HduType::ANY,
int hduver = 0, bool strip = false);

/** Read FITS header
*
* Includes support for the INHERIT convention: if 'INHERIT = T' is in the header, the
Expand Down
12 changes: 11 additions & 1 deletion python/lsst/afw/fits/_fits.cc
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ void declareFits(lsst::utils::python::WrapperCollection &wrappers) {
cls.def("closeFile", &Fits::closeFile);
cls.def("getFileName", &Fits::getFileName);
cls.def("getHdu", &Fits::getHdu);
cls.def("setHdu", &Fits::setHdu, "hdu"_a, "relative"_a = false);
cls.def("setHdu", py::overload_cast<int, bool>(&Fits::setHdu), "hdu"_a, "relative"_a = false);
cls.def(
"setHdu", [](Fits &self, std::string const &name) { self.setHdu(name); }, "name"_a);
cls.def("countHdus", &Fits::countHdus);

cls.def("writeMetadata", &Fits::writeMetadata);
Expand Down Expand Up @@ -261,6 +263,14 @@ void declareFitsModule(lsst::utils::python::WrapperCollection &wrappers) {
return readMetadata(filename, hdu, strip);
},
"fileName"_a, "hdu"_a = DEFAULT_HDU, "strip"_a = false);

mod.def(
"readMetadata",
[](std::string const &filename, std::string const &hduname, bool strip = false) {
return readMetadata(filename, hduname, HduType::ANY, 0, strip);
},
"fileName"_a, "hduName"_a, "strip"_a = false);

mod.def("setAllowImageCompression", &setAllowImageCompression, "allow"_a);
mod.def("getAllowImageCompression", &getAllowImageCompression);

Expand Down
40 changes: 33 additions & 7 deletions src/fits.cc
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,14 @@ void Fits::setHdu(int hdu, bool relative) {
}
}

void Fits::setHdu(std::string const &name, HduType hdutype, int hduver) {
fits_movnam_hdu(reinterpret_cast<fitsfile *>(fptr), static_cast<int>(hdutype),
const_cast<char *>(name.c_str()), hduver, &status);
if (behavior & AUTO_CHECK)
LSST_FITS_CHECK_STATUS(*this, boost::format("Moving to named HDU %s, type %d, hduver %d") % name %
static_cast<int>(hdutype) % hduver);
}

int Fits::countHdus() {
int n = 0;
fits_get_num_hdus(reinterpret_cast<fitsfile *>(fptr), &n, &status);
Expand Down Expand Up @@ -1680,16 +1688,34 @@ std::shared_ptr<daf::base::PropertyList> combineMetadata(
return combineMetadata(*first, *second);
}

std::shared_ptr<daf::base::PropertyList> readMetadata(std::string const &fileName, int hdu, bool strip) {
fits::Fits fp(fileName, "r", fits::Fits::AUTO_CLOSE | fits::Fits::AUTO_CHECK);
fp.setHdu(hdu);
using dafPlistPtr = std::shared_ptr<daf::base::PropertyList>;

namespace detail {
template <typename T, typename... Args>
dafPlistPtr _readMetadata(T &&fitsparm, bool strip, Args... args) {
fits::Fits fp(fitsparm, "r", fits::Fits::AUTO_CLOSE | fits::Fits::AUTO_CHECK);
fp.setHdu(args...);
return readMetadata(fp, strip);
}

std::shared_ptr<daf::base::PropertyList> readMetadata(fits::MemFileManager &manager, int hdu, bool strip) {
fits::Fits fp(manager, "r", fits::Fits::AUTO_CLOSE | fits::Fits::AUTO_CHECK);
fp.setHdu(hdu);
return readMetadata(fp, strip);
} // namespace detail

dafPlistPtr readMetadata(std::string const &fileName, int hdu, bool strip) {
return detail::_readMetadata(fileName, strip, hdu);
}

dafPlistPtr readMetadata(std::string const &fileName, std::string const &hduname, HduType type, int hduver,
bool strip) {
return detail::_readMetadata(fileName, strip, hduname, type, hduver);
}

dafPlistPtr readMetadata(fits::MemFileManager &manager, int hdu, bool strip) {
return detail::_readMetadata(manager, strip, hdu);
}

dafPlistPtr readMetadata(MemFileManager &manager, std::string const &hduname, HduType type, int hduver,
bool strip) {
return detail::_readMetadata(manager, strip, hduname, type, hduver);
}

std::shared_ptr<daf::base::PropertyList> readMetadata(fits::Fits &fitsfile, bool strip) {
Expand Down
1 change: 1 addition & 0 deletions tests/data/multi_extension_metadata.fits
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T BLEEP = 12.0 BLOOP = 'BLOOP ' CHECKSUM= 'TlAXUl6UTlAUTl5U' / HDU checksum updated 2022-09-12T15:54:17 DATASUM = '0 ' / data unit checksum updated 2022-09-12T15:54:17 END XTENSION= 'IMAGE ' / Image extension BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups BLORP = 'BLORP ' EXTNAME = 'EXTRA_IM' / extension name CHECKSUM= 'fdgJgdd9fddGfdd9' / HDU checksum updated 2022-09-12T15:54:17 DATASUM = '0 ' / data unit checksum updated 2022-09-12T15:54:17 END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 0 / length of dimension 1 NAXIS2 = 0 / length of dimension 2 PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups TFIELDS = 0 / number of table fields FVALUE = 12.996 EXTNAME = 'EXTRA_TAB' / extension name CHECKSUM= 'K9dAM7b4K7bAK7b3' / HDU checksum updated 2022-09-12T15:54:17 DATASUM = '0 ' / data unit checksum updated 2022-09-12T15:54:17 END
16 changes: 16 additions & 0 deletions tests/test_fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ def test_ticket_dm_36207(self):
mdattest = fts.readMetadata()["COMMENT"]
self.assertIn("and Astrophysics', volume 376, page 359;", mdattest)

def testNamedHeaderNavigate(self):
testfile = os.path.join(testPath, "data", "multi_extension_metadata.fits")

# load metadata from the extra table header
md_extra_tab = lsst.afw.fits.readMetadata(testfile, hduName="EXTRA_TAB")
# assert the value we put in is in the read metadata
self.assertTrue("FVALUE" in md_extra_tab)

# load metadata from the extra image header, do same test
md_extra_im = lsst.afw.fits.readMetadata(testfile, hduName="EXTRA_IM")
self.assertTrue("BLORP" in md_extra_im)

# now try to load a non-existent named HDU and check that we throw
with self.assertRaises(lsst.afw.fits.FitsError):
lsst.afw.fits.readMetadata(testfile, hduName="CORDON_BLEAU")


class TestMemory(lsst.utils.tests.MemoryTestCase):
pass
Expand Down

0 comments on commit 6a55182

Please sign in to comment.