Using lsst.afw.detection.Footprint to represent detection areas

The fundamental unit of a detection in the LSST pipeline is an instance of the Footprint class. This class contains the x, y locations for pixels that are part of a detection, as well as the x, y location and intensity of pixels which are considered local peaks within the detection area.

Spans

Internally the pixel locations of a detection are stored in an instance of a SpanSet which can be retrieved through the getSpans function (or the Footprint.spans property in Python). The SpanSet provides methods for working with the detected area as a mathematical set. Of important note is that SpanSets are immutable once created. If the area of a Footprint needs to be updated after initialization a new SpanSet must be created which will then replace the original SpanSet held by the Footprint instance. This can be done by calling the setSpans method (or assigning to the Footprint.spans property in Python). This does not modify the PeakCatalog, and there may be peaks which no longer fall inside the footprint. Call removeOrphanPeaks to remove these peaks. Below modifying a SpanSet is demonstrated.

from lsst.afw.geom import SpanSet
from lsst.afw.detection import Footprint

# Create a SpanSet and use it to construct a Footprint
radius = 5
ss = SpanSet.fromShape(radius, offset=(10, 10))
foot = Footprint(ss)

# Demo the SpanSet is the same
assert(ss == foot.spans)

# Create a new SpanSet and assign it to the Footprint
newRadius = 4
ss2 = SpanSet.fromShape(newRadius, offset=(7, 7))
foot.spans = ss2

# Show that the SpanSet held by the Footprint is no longer the original
assert(ss != foot.spans)

Some common operations which involve calling SpanSet methods, and possibly setting the results back to a Footprint, have convenience methods defined in the Footprint class. Unlike the SpanSet class a Footprint is mutable, such that calling the some convenience methods (e.g. dilate, erode) modifies the Footprint, as shown below.

from lsst.afw.geom import SpanSet
from lsst.afw.detection import Footprint

# Create a SpanSet and use it to construct a Footprint
radius = 5
ss = SpanSet.fromShape(radius, offset=(10, 10))
foot = Footprint(ss)

# Grab the SpanSet back from the Footprint and show it is the same
ssFromFoot = foot.spans
assert(ss is ssFromFoot)

# Modify the Footprint in place (automatically update the internal reference
# to the SpanSet), show that they are now different
kernelRad = 2
foot.erode(kernelRad)
newSsFromFoot = foot.spans
assert(ss is not newSsFromFoot)

In cases where the location information is all that is needed, it is strongly suggested to use a SpanSet directly and avoid the overhead of carrying around an empty PeakCatalog. The constructors for a Footprint have been structured to remind users of this by first requiring a SpanSet to be created.

Peaks

Information on the location and intensity of the detected peaks within a Footprint are kept in a PeakCatalog which can be retrieved with the getPeaks method (or the .peaks property within Python). Like with a Footprint’s SpanSet it is possible to set a Footprint's PeakCatalog. Unlike with the SpanSet member it is only possible to set a new PeakCatalog if the existing catalog is empty. It is also possible to change the schema that defines the PeakCatalog but the existing catalog must be empty for this operation as well. Another asymmetry between the SpanSet and PeakCatalog members is in the behavior of the Python property accessor. In Python the .spans property is both readable and writable, while the .peaks property is read only. This behavior is intended to make a programmer cognisant of the fact that the PeakCatalog can only be set if it is currently empty. The PeakCatalog can be populated by calling methods on the catalog itself, or with the convenience method addPeak supplied with the Footprint class. An additional convince method sortPeaks is also provided to increase the ease of sorting the catalog. The following is an example of using a Footprint’s PeakCatalog.

from lsst.afw.geom import SpanSet
from lsst.afw.detection import Footprint

# Create a Footprint from a SpanSet
radius = 5
ss = SpanSet.fromShape(radius, offset=(10, 10))
foot = Footprint(ss)

# Add a few peaks to the PeakCatalog (x, y, intensity)
foot.addPeak(7, 7, 95)
foot.addPeak(8, 8, 103)
foot.addPeak(9, 9, 100)

# Sort the peaks according to the intensity
foot.sortPeaks()

# Print the peaks in the Footprint
for peak in foot.peaks:
    print(peak.getPeakValue())

# Output:
# 103.0
# 100.0
# 95.0

Regions

The Footprint class also contains a few miscellaneous data members and methods unrelated to the main data containers mentioned above. One such data member is the``region`` which defines the boundary of the image in which the detection was made. This property can be retrieved or set with getRegion and setRegion respectively.

Transformations

A method named transform is provided which operates on both the SpanSet and PeakCatalog transforming the x, y values into a new coordinate system. The transform method returns a newly created Footprint.

Handling discontinuous Footprints

To split apart a footprint which may have a discontinuous area into continuous regions which contain only peaks which fall in the region use the split method. Occasionally, as mentioned above, operations on the SpanSet may create an area which no longer contains the x, y locations of peaks in the PeakCatalog. When this occurs the removeOrphanPeaks may be used to trim peaks which fall outside the new area.