From 93ea0136ac92c2716465eacc31934a978eb7981c Mon Sep 17 00:00:00 2001 From: fred3m Date: Wed, 26 Nov 2025 10:54:19 -0800 Subject: [PATCH 1/3] Change NoWorkFound to more specific errors --- python/lsst/meas/algorithms/dynamicDetection.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/python/lsst/meas/algorithms/dynamicDetection.py b/python/lsst/meas/algorithms/dynamicDetection.py index 62ec905e..d63f7fec 100644 --- a/python/lsst/meas/algorithms/dynamicDetection.py +++ b/python/lsst/meas/algorithms/dynamicDetection.py @@ -11,12 +11,13 @@ from .detection import SourceDetectionConfig, SourceDetectionTask from .skyObjects import SkyObjectsTask +from .subtractBackground import TooManyMaskedPixelsError from lsst.afw.detection import FootprintSet from lsst.afw.geom import makeCdMatrix, makeSkyWcs, SpanSet from lsst.afw.table import SourceCatalog, SourceTable from lsst.meas.base import ForcedMeasurementTask -from lsst.pipe.base import NoWorkFound, Struct +from lsst.pipe.base import Struct import lsst.afw.image import lsst.afw.math @@ -89,8 +90,9 @@ class DynamicDetectionConfig(SourceDetectionConfig): minFractionSources = Field(dtype=float, default=0.02, doc="Minimum fraction of the requested number of sky sources for dynamic " "detection to be considered a success. If the number of good sky sources " - "identified falls below this threshold, a NoWorkFound error is raised so " - "that this dataId is no longer considered in downstream processing.") + "identified falls below this threshold, an InsufficientSourcesError error " + "is raised so that this dataId is no longer considered in downstream " + "processing.") doBrightPrelimDetection = Field(dtype=bool, default=True, doc="Do initial bright detection pass where footprints are grown " "by brightGrowFactor?") @@ -245,7 +247,7 @@ def calculateThreshold(self, exposure, seed, sigma=None, minFractionSourcesFacto Raises ------ - NoWorkFound + InsufficientSourcesError Raised if the number of good sky sources found is less than the minimum fraction (``self.config.minFractionSources``*``minFractionSourcesFactor``) @@ -437,7 +439,7 @@ def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, if nGoodPix/nPix < self.config.minGoodPixelFraction: msg = (f"Image has a very low good pixel fraction ({nGoodPix} of {nPix}), so not worth further " "consideration") - raise NoWorkFound(msg) + raise TooManyMaskedPixelsError(msg) with self.tempWideBackgroundContext(exposure): # Could potentially smooth with a wider kernel than the PSF in @@ -512,7 +514,7 @@ def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, growOverride=growOverride) if results.numPos == 0: msg = "No footprints were detected, so further processing would be moot" - raise NoWorkFound(msg) + raise InsufficientSourcesError(msg) else: self.log.warning("nPeaks/nFootprint = %.2f (max is %.1f)", results.numPosPeaks/results.numPos, From f353a9b5aa3accd0c66b5baed2d5a4640077106b Mon Sep 17 00:00:00 2001 From: fred3m Date: Wed, 26 Nov 2025 15:51:06 -0800 Subject: [PATCH 2/3] Use ZeroFootprintError for no detection footprints --- .../lsst/meas/algorithms/dynamicDetection.py | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/python/lsst/meas/algorithms/dynamicDetection.py b/python/lsst/meas/algorithms/dynamicDetection.py index d63f7fec..4ec0a149 100644 --- a/python/lsst/meas/algorithms/dynamicDetection.py +++ b/python/lsst/meas/algorithms/dynamicDetection.py @@ -3,6 +3,7 @@ "DynamicDetectionConfig", "DynamicDetectionTask", "InsufficientSourcesError", + "ZeroFootprintError", ] import numpy as np @@ -17,14 +18,14 @@ from lsst.afw.geom import makeCdMatrix, makeSkyWcs, SpanSet from lsst.afw.table import SourceCatalog, SourceTable from lsst.meas.base import ForcedMeasurementTask -from lsst.pipe.base import Struct +from lsst.pipe.base import AlgorithmError, Struct import lsst.afw.image import lsst.afw.math import lsst.geom as geom -class InsufficientSourcesError(Exception): +class InsufficientSourcesError(AlgorithmError): """Raised if an insufficient number of sky sources are found for dynamic detection. @@ -58,6 +59,33 @@ def metadata(self): return self._metadata +class ZeroFootprintError(AlgorithmError): + """Raised if no footprints are detected in the image. + + Parameters + ---------- + msg : `str` + Error message. + **kwargs : `dict`, optional + Additional keyword arguments to initialize the Exception base class. + """ + def __init__(self, msg, **kwargs): + self.msg = msg + self._metadata = kwargs + super().__init__(msg, **kwargs) + + def __str__(self): + # Exception doesn't handle **kwargs, so we need a custom str. + return f"{self.msg}: {self.metadata}" + + @property + def metadata(self): + for key, value in self._metadata.items(): + if not isinstance(value, (int, float, str)): + raise TypeError(f"{key} is of type {type(value)}, but only (int, float, str) are allowed.") + return self._metadata + + class DynamicDetectionConfig(SourceDetectionConfig): """Configuration for DynamicDetectionTask """ @@ -514,7 +542,7 @@ def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, growOverride=growOverride) if results.numPos == 0: msg = "No footprints were detected, so further processing would be moot" - raise InsufficientSourcesError(msg) + raise ZeroFootprintError(msg) else: self.log.warning("nPeaks/nFootprint = %.2f (max is %.1f)", results.numPosPeaks/results.numPos, From 20e9ef936f51eb6504d19c4ee842d7d6965d839a Mon Sep 17 00:00:00 2001 From: fred3m Date: Wed, 26 Nov 2025 16:17:53 -0800 Subject: [PATCH 3/3] Catch errors generating a PSF during detection --- python/lsst/meas/algorithms/detection.py | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/python/lsst/meas/algorithms/detection.py b/python/lsst/meas/algorithms/detection.py index 9ce8c123..4dc00ff5 100644 --- a/python/lsst/meas/algorithms/detection.py +++ b/python/lsst/meas/algorithms/detection.py @@ -21,7 +21,7 @@ # see . # -__all__ = ("SourceDetectionConfig", "SourceDetectionTask", "addExposures") +__all__ = ("PsfGenerationError", "SourceDetectionConfig", "SourceDetectionTask", "addExposures") from contextlib import contextmanager @@ -40,6 +40,33 @@ from .subtractBackground import SubtractBackgroundTask, backgroundFlatContext +class PsfGenerationError(pipeBase.AlgorithmError): + """Raised when we cannot generate a PSF for detection + + Parameters + ---------- + msg : `str` + Error message. + **kwargs : `dict`, optional + Additional keyword arguments to initialize the Exception base class. + """ + def __init__(self, msg, **kwargs): + self.msg = msg + self._metadata = kwargs + super().__init__(msg, **kwargs) + + def __str__(self): + # Exception doesn't handle **kwargs, so we need a custom str. + return f"{self.msg}: {self.metadata}" + + @property + def metadata(self): + for key, value in self._metadata.items(): + if not isinstance(value, (int, float, str)): + raise TypeError(f"{key} is of type {type(value)}, but only (int, float, str) are allowed.") + return self._metadata + + class SourceDetectionConfig(pexConfig.Config): """Configuration parameters for the SourceDetectionTask """ @@ -466,8 +493,10 @@ def getPsf(self, exposure, sigma=None): if sigma is None: psf = exposure.getPsf() if psf is None: - raise RuntimeError("Unable to determine PSF to use for detection: no sigma provided") + raise PsfGenerationError("Unable to determine PSF to use for detection: no sigma provided") sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius() + if not np.isfinite(sigma) or sigma <= 0.0: + raise PsfGenerationError("Invalid sigma=%s for PSF used in detection" % (sigma,)) size = self.calculateKernelSize(sigma) psf = afwDet.GaussianPsf(size, size, sigma) return psf