22r"""Base classes for single-frame measurement plugins and the associated task.
24In single-frame measurement, we assume that detection and probably deblending
25have already been run on the same frame, so a `~lsst.afw.table.SourceCatalog`
26has already been created with `lsst.afw.detection.Footprint`\ s (which may be
27"heavy" — that is, include pixel data). Measurements are generally recorded in
28the coordinate system of the image being measured (and all slot-eligible
29fields must be), but non-slot fields may be recorded in other coordinate
30systems if necessary to avoid information loss (this should, of course, be
31indicated in the field documentation).
34from lsst.utils.logging
import PeriodicLogger
35from lsst.utils.timer
import timeMethod
37from .pluginRegistry
import PluginRegistry
38from .baseMeasurement
import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
39 BaseMeasurementConfig, BaseMeasurementTask)
40from .noiseReplacer
import NoiseReplacer, DummyNoiseReplacer
42__all__ = (
"SingleFramePluginConfig",
"SingleFramePlugin",
43 "SingleFrameMeasurementConfig",
"SingleFrameMeasurementTask")
47 """Base class for single-frame plugin configuration classes.
53 """Base class for single-frame measurement plugin.
57 config : `SingleFramePlugin.ConfigClass`
58 Configuration for this plugin.
60 The string
with which the plugin was registered.
62 The schema
for the source table . New fields are added here to
63 hold measurements produced by this plugin.
65 Plugin metadata that will be attached to the output catalog
66 logName : `str`, optional
67 Name to use when logging errors.
71 New plugins can be created
in Python by inheriting directly
from this
72 class and implementing the `measure`, `fail` (
from `BasePlugin`),
and
73 optionally `__init__`
and `measureN` methods. Plugins can also be defined
74 in C++ via the `WrappedSingleFramePlugin`
class.
78 """Registry of subclasses of `SingleFramePlugin` (`PluginRegistry`).
81 ConfigClass = SingleFramePluginConfig
83 def __init__(self, config, name, schema, metadata, logName=None, **kwds):
84 BaseMeasurementPlugin.__init__(self, config, name, logName=logName)
87 """Measure the properties of a source on a single image.
89 The image may be from a single epoch,
or it may be a coadd.
94 Record describing the object being measured. Previously-measured
95 quantities may be retrieved
from here,
and it will be updated
96 in-place tih the outputs of this plugin.
97 exposure : `lsst.afw.image.ExposureF`
98 The pixel data to be measured, together
with the associated PSF,
99 WCS, etc. All other sources
in the image should have been replaced
100 by noise according to deblender outputs.
102 raise NotImplementedError()
105 """Measure the properties of blended sources on a single image.
107 This operates on all members of a blend family at once. The image may
108 be from a single epoch,
or it may be a coadd.
113 Catalog describing the objects (
and only those objects) being
114 measured. Previously-measured quantities will be retrieved
from
115 here,
and it will be updated
in-place
with the outputs of this
117 exposure : `lsst.afw.image.ExposureF`
118 The pixel data to be measured, together
with the associated PSF,
119 WCS, etc. All other sources
in the image should have been replaced
120 by noise according to deblender outputs.
124 Derived classes that do
not implement ``measureN`` should just inherit
125 this disabled version. Derived classes that do implement ``measureN``
126 should additionally add a bool doMeasureN config field to their config
127 class to signal that
measureN-mode
is available.
129 raise NotImplementedError()
133 """Config class for single frame measurement driver task.
136 plugins = SingleFramePlugin.registry.makeField(
138 default=[
"base_PixelFlags",
140 "base_NaiveCentroid",
144 "base_CircularApertureFlux",
148 "base_LocalBackground",
150 doc=
"Plugins to be run and their configuration"
152 algorithms = property(
lambda self: self.
plugins, doc=
"backwards-compatibility alias for plugins")
153 undeblended = SingleFramePlugin.registry.makeField(
156 doc=
"Plugins to run on undeblended image"
161 """A subtask for measuring the properties of sources on a single exposure.
166 Schema of the output resultant catalog. Will be updated to provide
167 fields to accept the outputs of plugins which will be executed by this
170 Used to record metadaa about algorithm execution. An empty
173 Keyword arguments forwarded to `BaseMeasurementTask`.
176 ConfigClass = SingleFrameMeasurementConfig
178 NOISE_SEED_MULTIPLIER = "NOISE_SEED_MULTIPLIER"
179 """Name by which the noise seed multiplier is recorded in metadata ('str').
182 NOISE_SOURCE = "NOISE_SOURCE"
183 """Name by which the noise source is recorded in metadata ('str').
186 NOISE_OFFSET = "NOISE_OFFSET"
187 """Name by which the noise offset is recorded in metadata ('str').
190 NOISE_EXPOSURE_ID = "NOISE_EXPOSURE_ID"
191 """Name by which the noise exposire ID is recorded in metadata ('str').
194 def __init__(self, schema, algMetadata=None, **kwds):
195 super(SingleFrameMeasurementTask, self).
__init__(algMetadata=algMetadata, **kwds)
197 self.config.slots.setupSchema(self.
schema)
201 if 'base_Blendedness' in self.
plugins:
208 def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None):
209 r"""Run single frame measurement over an exposure and source catalog.
214 Catalog to be filled with the results of measurement. Must contain
217 that
is a superset of ``self.
schema``.
218 exposure : `lsst.afw.image.ExposureF`
219 Image containing the pixel data to be measured together
with
220 associated PSF, WCS, etc.
221 noiseImage : `lsst.afw.image.ImageF`, optional
222 Can be used to specify the a predictable noise replacement field
223 for testing purposes.
224 exposureId : `int`, optional
225 Unique exposure identifier used to calculate the random number
226 generator seed during noise replacement.
227 beginOrder : `float`, optional
228 Start execution order (inclusive): measurements
with
229 ``executionOrder < beginOrder`` are
not executed. `
None`
for no
231 endOrder : `float`, optional
232 Final execution order (exclusive): measurements
with
233 ``executionOrder >= endOrder`` are
not executed. `
None`
for no
236 assert measCat.getSchema().contains(self.
schema)
237 footprints = {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
238 for measRecord
in measCat}
246 if self.config.doReplaceWithNoise:
247 noiseReplacer =
NoiseReplacer(self.config.noiseReplacer, exposure, footprints,
248 noiseImage=noiseImage, log=self.log, exposureId=exposureId)
249 algMetadata = measCat.getMetadata()
250 if algMetadata
is not None:
252 algMetadata.addString(self.
NOISE_SOURCE, self.config.noiseReplacer.noiseSource)
253 algMetadata.addDouble(self.
NOISE_OFFSET, self.config.noiseReplacer.noiseOffset)
254 if exposureId
is not None:
259 self.
runPlugins(noiseReplacer, measCat, exposure, beginOrder, endOrder)
261 def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None):
262 r"""Call the configured measument plugins on an image.
266 noiseReplacer : `NoiseReplacer`
267 Used to fill sources not being measured
with noise.
269 Catalog to be filled
with the results of measurement. Must contain
272 that
is a superset of ``self.
schema``.
273 exposure : `lsst.afw.image.ExposureF`
274 Image containing the pixel data to be measured together
with
275 associated PSF, WCS, etc.
276 beginOrder : `float`, optional
277 Start execution order (inclusive): measurements
with
278 ``executionOrder < beginOrder`` are
not executed. `
None`
for no
280 endOrder : `float`, optional
281 Final execution order (exclusive): measurements
with
282 ``executionOrder >= endOrder`` are
not executed. `
None`
for no
287 measParentCat = measCat.getChildren(0)
289 nMeasCat = len(measCat)
290 nMeasParentCat = len(measParentCat)
291 self.log.info(
"Measuring %d source%s (%d parent%s, %d child%s) ",
292 nMeasCat, (
"" if nMeasCat == 1
else "s"),
293 nMeasParentCat, (
"" if nMeasParentCat == 1
else "s"),
294 nMeasCat - nMeasParentCat, (
"" if nMeasCat - nMeasParentCat == 1
else "ren"))
297 periodicLog = PeriodicLogger(self.log)
299 childrenIter = measCat.getChildren([measParentRecord.getId()
for measParentRecord
in measParentCat])
300 for parentIdx, (measParentRecord, measChildCat)
in enumerate(zip(measParentCat, childrenIter)):
305 for measChildRecord
in measChildCat:
306 noiseReplacer.insertSource(measChildRecord.getId())
307 self.
callMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
310 self.
blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
312 noiseReplacer.removeSource(measChildRecord.getId())
315 noiseReplacer.insertSource(measParentRecord.getId())
316 self.
callMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
319 self.
blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
322 self.
callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
323 beginOrder=beginOrder, endOrder=endOrder)
324 self.
callMeasureN(measChildCat, exposure, beginOrder=beginOrder, endOrder=endOrder)
325 noiseReplacer.removeSource(measParentRecord.getId())
327 periodicLog.log(
"Measurement complete for %d parents (and their children) out of %d",
328 parentIdx + 1, nMeasParentCat)
335 for sourceIndex, source
in enumerate(measCat):
339 periodicLog.log(
"Undeblended measurement complete for %d sources out of %d",
340 sourceIndex + 1, nMeasCat)
345 for source
in measCat:
346 self.
blendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
349 """Backwards-compatibility alias for `run`.
351 self.run(measCat, exposure)
Defines the fields and offsets for a table.
Record class that contains measurements made on a single exposure.
Class for storing ordered metadata with comments.
Class for storing generic metadata.
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)