22 r"""Base classes for single-frame measurement plugins and the associated task.
24 In single-frame measurement, we assume that detection and probably deblending
25 have already been run on the same frame, so a `~lsst.afw.table.SourceCatalog`
26 has already been created with `lsst.afw.detection.Footprint`\ s (which may be
27 "heavy" — that is, include pixel data). Measurements are generally recorded in
28 the coordinate system of the image being measured (and all slot-eligible
29 fields must be), but non-slot fields may be recorded in other coordinate
30 systems if necessary to avoid information loss (this should, of course, be
31 indicated in the field documentation).
36 from lsst.utils.timer
import timeMethod
38 from .pluginRegistry
import PluginRegistry
39 from .baseMeasurement
import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
40 BaseMeasurementConfig, BaseMeasurementTask)
41 from .noiseReplacer
import NoiseReplacer, DummyNoiseReplacer
43 __all__ = (
"SingleFramePluginConfig",
"SingleFramePlugin",
44 "SingleFrameMeasurementConfig",
"SingleFrameMeasurementTask")
48 """Base class for single-frame plugin configuration classes.
54 """Base class for single-frame measurement plugin.
58 config : `SingleFramePlugin.ConfigClass`
59 Configuration for this plugin.
61 The string with which the plugin was registered.
62 schema : `lsst.afw.table.Schema`
63 The schema for the source table . New fields are added here to
64 hold measurements produced by this plugin.
65 metadata : `lsst.daf.base.PropertySet`
66 Plugin metadata that will be attached to the output catalog
67 logName : `str`, optional
68 Name to use when logging errors.
72 New plugins can be created in Python by inheriting directly from this
73 class and implementing the `measure`, `fail` (from `BasePlugin`), and
74 optionally `__init__` and `measureN` methods. Plugins can also be defined
75 in C++ via the `WrappedSingleFramePlugin` class.
79 """Registry of subclasses of `SingleFramePlugin` (`PluginRegistry`).
82 ConfigClass = SingleFramePluginConfig
84 def __init__(self, config, name, schema, metadata, logName=None, **kwds):
85 BaseMeasurementPlugin.__init__(self, config, name, logName=logName)
88 """Measure the properties of a source on a single image.
90 The image may be from a single epoch, or it may be a coadd.
94 measRecord : `lsst.afw.table.SourceRecord`
95 Record describing the object being measured. Previously-measured
96 quantities may be retrieved from here, and it will be updated
97 in-place tih the outputs of this plugin.
98 exposure : `lsst.afw.image.ExposureF`
99 The pixel data to be measured, together with the associated PSF,
100 WCS, etc. All other sources in the image should have been replaced
101 by noise according to deblender outputs.
103 raise NotImplementedError()
106 """Measure the properties of blended sources on a single image.
108 This operates on all members of a blend family at once. The image may
109 be from a single epoch, or it may be a coadd.
113 measCat : `lsst.afw.table.SourceCatalog`
114 Catalog describing the objects (and only those objects) being
115 measured. Previously-measured quantities will be retrieved from
116 here, and it will be updated in-place with the outputs of this
118 exposure : `lsst.afw.image.ExposureF`
119 The pixel data to be measured, together with the associated PSF,
120 WCS, etc. All other sources in the image should have been replaced
121 by noise according to deblender outputs.
125 Derived classes that do not implement ``measureN`` should just inherit
126 this disabled version. Derived classes that do implement ``measureN``
127 should additionally add a bool doMeasureN config field to their config
128 class to signal that measureN-mode is available.
130 raise NotImplementedError()
134 """Config class for single frame measurement driver task.
137 plugins = SingleFramePlugin.registry.makeField(
139 default=[
"base_PixelFlags",
141 "base_NaiveCentroid",
145 "base_CircularApertureFlux",
149 "base_LocalBackground",
151 doc=
"Plugins to be run and their configuration"
153 algorithms = property(
lambda self: self.
pluginsplugins, doc=
"backwards-compatibility alias for plugins")
154 undeblended = SingleFramePlugin.registry.makeField(
157 doc=
"Plugins to run on undeblended image"
162 doc=
"Interval (in seconds) to log messages (at VERBOSE level) while running measurement plugins."
167 """A subtask for measuring the properties of sources on a single exposure.
171 schema : `lsst.afw.table.Schema`
172 Schema of the output resultant catalog. Will be updated to provide
173 fields to accept the outputs of plugins which will be executed by this
175 algMetadata : `lsst.daf.base.PropertyList`, optional
176 Used to record metadaa about algorithm execution. An empty
177 `lsst.daf.base.PropertyList` will be created if `None`.
179 Keyword arguments forwarded to `BaseMeasurementTask`.
182 ConfigClass = SingleFrameMeasurementConfig
184 NOISE_SEED_MULTIPLIER =
"NOISE_SEED_MULTIPLIER"
185 """Name by which the noise seed multiplier is recorded in metadata ('str').
188 NOISE_SOURCE =
"NOISE_SOURCE"
189 """Name by which the noise source is recorded in metadata ('str').
192 NOISE_OFFSET =
"NOISE_OFFSET"
193 """Name by which the noise offset is recorded in metadata ('str').
196 NOISE_EXPOSURE_ID =
"NOISE_EXPOSURE_ID"
197 """Name by which the noise exposire ID is recorded in metadata ('str').
200 def __init__(self, schema, algMetadata=None, **kwds):
201 super(SingleFrameMeasurementTask, self).
__init__(algMetadata=algMetadata, **kwds)
203 self.config.slots.setupSchema(self.
schemaschema)
207 if 'base_Blendedness' in self.
pluginsplugins:
214 def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None):
215 r"""Run single frame measurement over an exposure and source catalog.
219 measCat : `lsst.afw.table.SourceCatalog`
220 Catalog to be filled with the results of measurement. Must contain
221 all the `lsst.afw.table.SourceRecord`\ s to be measured (with
222 `lsst.afw.detection.Footprint`\ s attached), and have a schema
223 that is a superset of ``self.schema``.
224 exposure : `lsst.afw.image.ExposureF`
225 Image containing the pixel data to be measured together with
226 associated PSF, WCS, etc.
227 noiseImage : `lsst.afw.image.ImageF`, optional
228 Can be used to specify the a predictable noise replacement field
229 for testing purposes.
230 exposureId : `int`, optional
231 Unique exposure identifier used to calculate the random number
232 generator seed during noise replacement.
233 beginOrder : `float`, optional
234 Start execution order (inclusive): measurements with
235 ``executionOrder < beginOrder`` are not executed. `None` for no
237 endOrder : `float`, optional
238 Final execution order (exclusive): measurements with
239 ``executionOrder >= endOrder`` are not executed. `None` for no
243 footprints = {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
244 for measRecord
in measCat}
252 if self.config.doReplaceWithNoise:
253 noiseReplacer =
NoiseReplacer(self.config.noiseReplacer, exposure, footprints,
254 noiseImage=noiseImage, log=self.log, exposureId=exposureId)
255 algMetadata = measCat.getMetadata()
256 if algMetadata
is not None:
257 algMetadata.addInt(self.
NOISE_SEED_MULTIPLIERNOISE_SEED_MULTIPLIER, self.config.noiseReplacer.noiseSeedMultiplier)
258 algMetadata.addString(self.
NOISE_SOURCENOISE_SOURCE, self.config.noiseReplacer.noiseSource)
259 algMetadata.addDouble(self.
NOISE_OFFSETNOISE_OFFSET, self.config.noiseReplacer.noiseOffset)
260 if exposureId
is not None:
265 self.
runPluginsrunPlugins(noiseReplacer, measCat, exposure, beginOrder, endOrder)
267 def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None):
268 r"""Call the configured measument plugins on an image.
272 noiseReplacer : `NoiseReplacer`
273 Used to fill sources not being measured with noise.
274 measCat : `lsst.afw.table.SourceCatalog`
275 Catalog to be filled with the results of measurement. Must contain
276 all the `lsst.afw.table.SourceRecord`\ s to be measured (with
277 `lsst.afw.detection.Footprint`\ s attached), and have a schema
278 that is a superset of ``self.schema``.
279 exposure : `lsst.afw.image.ExposureF`
280 Image containing the pixel data to be measured together with
281 associated PSF, WCS, etc.
282 beginOrder : `float`, optional
283 Start execution order (inclusive): measurements with
284 ``executionOrder < beginOrder`` are not executed. `None` for no
286 endOrder : `float`, optional
287 Final execution order (exclusive): measurements with
288 ``executionOrder >= endOrder`` are not executed. `None` for no
293 measParentCat = measCat.getChildren(0)
295 nMeasCat = len(measCat)
296 nMeasParentCat = len(measParentCat)
297 self.log.
info(
"Measuring %d source%s (%d parent%s, %d child%s) ",
298 nMeasCat, (
"" if nMeasCat == 1
else "s"),
299 nMeasParentCat, (
"" if nMeasParentCat == 1
else "s"),
300 nMeasCat - nMeasParentCat, (
"" if nMeasCat - nMeasParentCat == 1
else "ren"))
301 nextLogTime = time.time() + self.config.loggingInterval
303 childrenIter = measCat.getChildren([measParentRecord.getId()
for measParentRecord
in measParentCat])
304 for parentIdx, (measParentRecord, measChildCat)
in enumerate(zip(measParentCat, childrenIter)):
309 for measChildRecord
in measChildCat:
310 noiseReplacer.insertSource(measChildRecord.getId())
311 self.
callMeasurecallMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
314 self.
blendPluginblendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
316 noiseReplacer.removeSource(measChildRecord.getId())
319 noiseReplacer.insertSource(measParentRecord.getId())
320 self.
callMeasurecallMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
323 self.
blendPluginblendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
326 self.
callMeasureNcallMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
327 beginOrder=beginOrder, endOrder=endOrder)
328 self.
callMeasureNcallMeasureN(measChildCat, exposure, beginOrder=beginOrder, endOrder=endOrder)
329 noiseReplacer.removeSource(measParentRecord.getId())
331 if (currentTime := time.time()) > nextLogTime:
332 self.log.verbose(
"Measurement complete for %d parents (and their children) out of %d",
333 parentIdx + 1, nMeasParentCat)
334 nextLogTime = currentTime + self.config.loggingInterval
341 for sourceIndex, source
in enumerate(measCat):
344 if (currentTime := time.time()) > nextLogTime:
345 self.log.verbose(
"Undeblended measurement complete for %d sources out of %d",
346 sourceIndex + 1, nMeasCat)
347 nextLogTime = currentTime + self.config.loggingInterval
352 for source
in measCat:
353 self.
blendPluginblendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
356 """Backwards-compatibility alias for `run`.
358 self.
runrun(measCat, exposure)
def doMeasurement(self, plugin, measRecord, *args, **kwds)
def initializePlugins(self, **kwds)
def callMeasure(self, measRecord, *args, **kwds)
def callMeasureN(self, measCat, *args, **kwds)
def measure(self, measCat, exposure)
def __init__(self, schema, algMetadata=None, **kwds)
string NOISE_SEED_MULTIPLIER
def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None)
def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None)
def measureN(self, measCat, exposure)
def __init__(self, config, name, schema, metadata, logName=None, **kwds)
def measure(self, measRecord, exposure)