Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-42426: Guard against empty footprints #35

Merged
merged 5 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 22 additions & 3 deletions python/lsst/meas/extensions/gaap/_gaap.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def __init__(self, errors: dict[str, Exception]):
super().__init__(message, 1) # the second argument does not matter.


class NoPixelError(Exception):
"""Raised when the footprint has no pixels.

This is caught by the measurement framework, which then calls the
`fail` method of the plugin without passing in a value for `error`.
"""


class BaseGaapFluxConfig(measBase.BaseMeasurementPluginConfig):
"""Configuration parameters for Gaussian Aperture and PSF (GAaP) plugin.
"""
Expand Down Expand Up @@ -340,6 +348,8 @@ def __init__(self, config: BaseGaapFluxConfig, name, schema, logName=None) -> No
self.flagHandler = measBase.FlagHandler.addFields(schema, name, flagDefs)
self.EdgeFlagKey = schema.addField(schema.join(name, "flag_edge"), type="Flag",
doc="Source is too close to the edge")
self.NoPixelKey = schema.addField(schema.join(name, "flag_no_pixel"), type="Flag",
doc="No pixels in the footprint")
self._failKey = schema.addField(name + '_flag', type="Flag", doc="Set for any fatal failure")

self.psfMatchTask = config._modelPsfMatch.target(config=config._modelPsfMatch)
Expand Down Expand Up @@ -539,16 +549,23 @@ def _gaussianizeAndMeasure(self, measRecord: lsst.afw.table.SourceRecord,
Raised if the PSF Gaussianization fails for any of the target PSFs.
lsst.meas.base.FatalAlgorithmError
Raised if the Exposure does not contain a PSF model.
NoPixelError
Raised if the footprint has no pixels.

Notes
-----
This method is the entry point to the mixin from the concrete derived
classes.
"""
psf = exposure.getPsf()
if psf is None:

# Raise errors if the plugin would fail for this record for all
# scaling factors and sigmas.
if measRecord.getFootprint().getArea() == 0:
self._setFlag(measRecord, self.name, "no_pixel")
raise NoPixelError

if (psf := exposure.getPsf()) is None:
raise measBase.FatalAlgorithmError("No PSF in exposure")
wcs = exposure.getWcs()

psfSigma = psf.computeShape(center).getTraceRadius()
if not (psfSigma > 0): # This captures NaN and negative values.
Expand All @@ -558,6 +575,8 @@ def _gaussianizeAndMeasure(self, measRecord: lsst.afw.table.SourceRecord,
else:
errorCollection = dict()

wcs = exposure.getWcs()

for scalingFactor in self.config.scalingFactors:
targetSigma = scalingFactor*psfSigma
# If this target PSF is bound to fail for all apertures,
Expand Down
8 changes: 8 additions & 0 deletions tests/test_gaap.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,14 @@ def testFlags(self, sigmas=[0.4, 0.5, 0.7], scalingFactors=[1.15, 1.25, 1.4, 100
except InvalidParameterError:
self.assertTrue(record[baseName + "_flag_bigPsf"])

# Set an empty footprint and check that no_pixels flag is set.
record = catalog[1]
record.setFootprint(afwDetection.Footprint())
with self.assertRaises(lsst.meas.extensions.gaap._gaap.NoPixelError):
algorithm.measure(record, exposure)
self.assertTrue(record[algName + "_flag"])
self.assertTrue(record[algName + "_flag_no_pixel"])

# Ensure that the edge flag is set for the source at the corner.
record = catalog[2]
algorithm.measure(record, exposure)
Expand Down