-
Notifications
You must be signed in to change notification settings - Fork 0
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-44287: Refactor error handling to work better with measurement framework and quiet excessive logging #37
Changes from all commits
def35e8
fe6752b
7e7c890
f4033f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,33 +44,12 @@ | |
|
||
|
||
class GaapConvolutionError(measBase.MeasurementError): | ||
"""Collection of any unexpected errors in GAaP during PSF Gaussianization. | ||
|
||
The PSF Gaussianization procedure using `modelPsfMatchTask` may throw | ||
exceptions for certain target PSFs. Such errors are caught until all | ||
measurements are at least attempted. The complete traceback information | ||
is lost, but unique error messages are preserved. | ||
|
||
Parameters | ||
---------- | ||
errors : `dict` [`str`, `Exception`] | ||
The values are exceptions raised, while the keys are the loop variables | ||
(in `str` format) where the exceptions were raised. | ||
"""Raised when there is an error in GAaP convolution. | ||
""" | ||
def __init__(self, errors: dict[str, Exception]): | ||
self.errorDict = errors | ||
message = "Problematic scaling factors = " | ||
message += ", ".join(errors) | ||
message += " Errors: " | ||
message += " | ".join(set(msg.__repr__() for msg in errors.values())) # msg.cpp.what() misses type | ||
super().__init__(message, 1) # the second argument does not matter. | ||
|
||
|
||
class NoPixelError(Exception): | ||
class NoPixelError(measBase.MeasurementError): | ||
"""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`. | ||
""" | ||
|
||
|
||
|
@@ -175,7 +154,6 @@ def _sigmas(self) -> list: | |
|
||
def setDefaults(self) -> None: | ||
# Docstring inherited | ||
# TODO: DM-27482 might change these values. | ||
self._modelPsfMatch.kernel.active.alardNGauss = 1 | ||
self._modelPsfMatch.kernel.active.alardDegGaussDeconv = 1 | ||
self._modelPsfMatch.kernel.active.alardDegGauss = [4] | ||
|
@@ -557,21 +535,28 @@ def _gaussianizeAndMeasure(self, measRecord: lsst.afw.table.SourceRecord, | |
This method is the entry point to the mixin from the concrete derived | ||
classes. | ||
""" | ||
# First make sure we have a PSF. | ||
if (psf := exposure.getPsf()) is None: | ||
raise measBase.FatalAlgorithmError("No PSF in exposure") | ||
|
||
# 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") | ||
self._setScalingAndSigmaFlags(measRecord, self.config.scalingFactors) | ||
raise NoPixelError("No good pixels in footprint", 1) | ||
|
||
psfSigma = psf.computeShape(center).getTraceRadius() | ||
if not (psfSigma > 0): # This captures NaN and negative values. | ||
errorCollection = {str(scalingFactor): measBase.MeasurementError("PSF size could not be measured") | ||
for scalingFactor in self.config.scalingFactor} | ||
raise GaapConvolutionError(errorCollection) | ||
center = measRecord.getCentroid() | ||
self.log.debug("Invalid PSF sigma; cannot solve for PSF matching kernel in GAaP for (%f, %f): %s", | ||
center.getX(), center.getY(), "GAaP Convolution Error") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does the error message say fail to solve for kernel when we don't even attempt solving for it? It made sense when we were handling this in |
||
self._setScalingAndSigmaFlags( | ||
measRecord, | ||
self.config.scalingFactors, | ||
specificFlag="flag_gaussianization", | ||
) | ||
raise GaapConvolutionError("Failed to solve for PSF matching kernel", 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd raise this as a plain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what you mean. I don't see the harm in having a slightly more specific error so that if you look at the debug log there's marginally more information (though there is still the message). But I don't like having the error handling in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, if we want to keep error handling/error collating outside of |
||
else: | ||
errorCollection = dict() | ||
|
||
|
@@ -630,7 +615,19 @@ def _gaussianizeAndMeasure(self, measRecord: lsst.afw.table.SourceRecord, | |
# Raise GaapConvolutionError before exiting the plugin | ||
# if the collection of errors is not empty | ||
if errorCollection: | ||
raise GaapConvolutionError(errorCollection) | ||
message = "Problematic scaling factors = " | ||
message += ", ".join(errorCollection) | ||
message += " Errors: " | ||
message += " | ".join(set(msg.__repr__() for msg in errorCollection.values())) | ||
center = measRecord.getCentroid() | ||
self.log.debug("Failed to solve for PSF matching kernel in GAaP for (%f, %f): %s", | ||
center.getX(), center.getY(), message) | ||
self._setScalingAndSigmaFlags( | ||
measRecord, | ||
errorCollection.keys(), | ||
specificFlag="flag_gaussianization", | ||
) | ||
raise GaapConvolutionError("Failed to solve for PSF matching kernel", 1) | ||
|
||
@staticmethod | ||
def _setFlag(measRecord, baseName, flagName=None): | ||
|
@@ -658,6 +655,27 @@ def _setFlag(measRecord, baseName, flagName=None): | |
genericFlagKey = measRecord.schema.join(baseName, "flag") | ||
measRecord.set(genericFlagKey, True) | ||
|
||
def _setScalingAndSigmaFlags(self, measRecord, scalingFactors, specificFlag=None): | ||
"""Set a full suite of flags for scalingFactors/sigmas. | ||
|
||
Parameters | ||
---------- | ||
measRecord : `~lsst.afw.table.SourceRecord` | ||
Record describing the source being measured. | ||
scalingFactors : `list` [`float`] | ||
List of scaling factors. | ||
specificFlag : `str`, optional | ||
Specific type of flag to set if needed. | ||
""" | ||
for scalingFactor in scalingFactors: | ||
if specificFlag is not None: | ||
flagName = self.ConfigClass._getGaapResultName(scalingFactor, specificFlag, | ||
self.name) | ||
measRecord.set(flagName, True) | ||
for sigma in self.config._sigmas: | ||
baseName = self.ConfigClass._getGaapResultName(scalingFactor, sigma, self.name) | ||
self._setFlag(measRecord, baseName) | ||
|
||
def _isAllFailure(self, measRecord, scalingFactor, targetSigma) -> bool: | ||
"""Check if all measurements would result in failure. | ||
|
||
|
@@ -722,18 +740,9 @@ def fail(self, measRecord, error=None): | |
error : `Exception` | ||
Error causing failure, or `None`. | ||
""" | ||
if error is not None: | ||
center = measRecord.getCentroid() | ||
self.log.error("Failed to solve for PSF matching kernel in GAaP for (%f, %f): %s", | ||
center.getX(), center.getY(), error) | ||
for scalingFactor in error.errorDict: | ||
flagName = self.ConfigClass._getGaapResultName(scalingFactor, "flag_gaussianization", | ||
self.name) | ||
measRecord.set(flagName, True) | ||
for sigma in self.config._sigmas: | ||
baseName = self.ConfigClass._getGaapResultName(scalingFactor, sigma, self.name) | ||
self._setFlag(measRecord, baseName) | ||
else: | ||
# We only need to set the failKey if no error was specified which | ||
# signifies that the flagging was already handled. | ||
if error is None: | ||
measRecord.set(self._failKey, True) | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are moving this around, let's move this below the next block that raises the
FatalAlgorithmError
. On the very off chance something went so bad in processing that all footprints have zero area and no PSF available, I'd want it to fail with aFatalAlgorithmError
.