Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions python/lsst/meas/algorithms/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# see <https://www.lsstcorp.org/LegalNotices/>.
#

__all__ = ("SourceDetectionConfig", "SourceDetectionTask", "addExposures")
__all__ = ("PsfGenerationError", "SourceDetectionConfig", "SourceDetectionTask", "addExposures")

from contextlib import contextmanager

Expand All @@ -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
"""
Expand Down Expand Up @@ -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
Expand Down
44 changes: 37 additions & 7 deletions python/lsst/meas/algorithms/dynamicDetection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"DynamicDetectionConfig",
"DynamicDetectionTask",
"InsufficientSourcesError",
"ZeroFootprintError",
]

import numpy as np
Expand All @@ -11,19 +12,20 @@

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 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.

Expand Down Expand Up @@ -57,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
"""
Expand Down Expand Up @@ -89,8 +118,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?")
Expand Down Expand Up @@ -245,7 +275,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``)
Expand Down Expand Up @@ -437,7 +467,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
Expand Down Expand Up @@ -512,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 NoWorkFound(msg)
raise ZeroFootprintError(msg)
else:
self.log.warning("nPeaks/nFootprint = %.2f (max is %.1f)",
results.numPosPeaks/results.numPos,
Expand Down