Changes to ExposureInfo

I recently merged some changes to how afw.image.ExposureInfo stores its info. The goal is to make ExposureInfo more flexible in what it can do, as discussed by @jbosch last year.

First, the good news for long-time Stack users is that the changes are backwards-compatible – all existing methods of ExposureInfo behave the same as before, old files can be read by the new code, and new files can be read by old code. This means that Exposure's FITS persistence is still on version 1 (which was introduced to accommodate PhotoCalib).

In addition to its previous capabilities, ExposureInfo now has a set of methods (setComponent, getComponent, hasComponent, removeComponent) that let you attach almost arbitrary objects to an ExposureInfo. The objects can be of any class that inherits from the afw.typehandling.Storable mixin; these classes do not need to be in afw itself. Storable inherits from afw.table.io.Persistable, and you must provide the usual persistence code if you want a component to get saved to FITS files (consistent with ExposureInfo's previous behavior, any component whose isPersistable method returns false will be silently ignored).

Most of the data previously associated with an ExposureInfo can be manipulated with the new interface. The exceptions are the metadata, filter, and visit info, which could not be migrated without breaking backwards compatibility.

Python API

In Python, generic components are associated with string keys:

info.setComponent("ExposureFlags", myFlags)

if info.hasComponent("ExposureFlags"):
    flags = info.getComponent("ExposureFlags")

The ExposureInfo class exposes string constants for all the standard components that have been migrated to the new system. These are called KEY_ followed by the uppercased name used in the old-style get/set methods:

>>> ExposureInfo.KEY_WCS
'SKYWCS'
>>> info.getWcs() == info.getComponent(ExposureInfo.KEY_WCS)
True

The values of these strings are set by the need for backward compatibility with the old persistence format, so unfortunately we can’t change them. Be careful to use 'SKYWCS' and not 'wcs'!

C++ API

In C++, generic components are associated with afw::typehandling::Key objects that provide type information. Due to table::io limitations, components must be passed by shared pointer for now:

info.setComponent(typehandling::makeKey<shared_ptr<FlagInfo>>("ExposureFlags"s),
                  myFlags);

auto key = typehandling::makeKey<shared_ptr<FlagInfo>>("ExposureFlags"s);
if info.hasComponent(key) {
    shared_ptr<FlagInfo> flags = info.getComponent(key);
}

Calls to setComponent with a key that doesn’t match the value will not compile. Like the old-style getters, getComponent will return a null pointer if there’s no object of the requested type (the type need not match the original key exactly).

ExposureInfo exposes the same KEY_ constants in C++ as in Python, but as Key objects:

>>> cout << ExposureInfo::KEY_WCS;
SKYWCS<std::shared_ptr<lsst::afw::geom::SkyWcs>>

Future Plans

The other half of Jim’s discussion focused on replacing table.io persistence with a more flexible framework. The changes to ExposureInfo so far make only minor changes to how persistence is handled, but give us more room to maneuver in how we deal with the bigger issue (mostly because ExposureInfo components no longer need to be in a dependency of afw.image).

Moving away from table.io will break the persistence format (one reason I was so careful about backward-compatibility for now), but will free us from having to implement ExposureInfo components in C++, from arbitrary type restrictions like shared_ptr, or from needing special treatment for classes that are lower-level than afw (in particular, daf.base.PropertySet).

2 Likes