LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
sfm.py
Go to the documentation of this file.
1# This file is part of meas_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22r"""Base classes for single-frame measurement plugins and the associated task.
23
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).
32"""
33
34from lsst.utils.logging import PeriodicLogger
35from lsst.utils.timer import timeMethod
36
37from .pluginRegistry import PluginRegistry
38from .baseMeasurement import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
39 BaseMeasurementConfig, BaseMeasurementTask)
40from .noiseReplacer import NoiseReplacer, DummyNoiseReplacer
41
42__all__ = ("SingleFramePluginConfig", "SingleFramePlugin",
43 "SingleFrameMeasurementConfig", "SingleFrameMeasurementTask")
44
45
47 """Base class for single-frame plugin configuration classes.
48 """
49 pass
50
51
53 """Base class for single-frame measurement plugin.
54
55 Parameters
56 ----------
57 config : `SingleFramePlugin.ConfigClass`
58 Configuration for this plugin.
59 name : `str`
60 The string with which the plugin was registered.
61 schema : `lsst.afw.table.Schema`
62 The schema for the source table . New fields are added here to
63 hold measurements produced by this plugin.
64 metadata : `lsst.daf.base.PropertySet`
65 Plugin metadata that will be attached to the output catalog
66 logName : `str`, optional
67 Name to use when logging errors.
68
69 Notes
70 -----
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.
75 """
76
77 registry = PluginRegistry(SingleFramePluginConfig)
78 """Registry of subclasses of `SingleFramePlugin` (`PluginRegistry`).
79 """
80
81 ConfigClass = SingleFramePluginConfig
82
83 def __init__(self, config, name, schema, metadata, logName=None, **kwds):
84 BaseMeasurementPlugin.__init__(self, config, name, logName=logName)
85
86 def measure(self, measRecord, exposure):
87 """Measure the properties of a source on a single image.
88
89 The image may be from a single epoch, or it may be a coadd.
90
91 Parameters
92 ----------
93 measRecord : `lsst.afw.table.SourceRecord`
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.
101 """
102 raise NotImplementedError()
103
104 def measureN(self, measCat, exposure):
105 """Measure the properties of blended sources on a single image.
106
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.
109
110 Parameters
111 ----------
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
116 plugin.
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.
121
122 Notes
123 -----
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.
128 """
129 raise NotImplementedError()
130
131
133 """Config class for single frame measurement driver task.
134 """
135
136 plugins = SingleFramePlugin.registry.makeField(
137 multi=True,
138 default=["base_PixelFlags",
139 "base_SdssCentroid",
140 "base_NaiveCentroid",
141 "base_SdssShape",
142 "base_GaussianFlux",
143 "base_PsfFlux",
144 "base_CircularApertureFlux",
145 "base_SkyCoord",
146 "base_Variance",
147 "base_Blendedness",
148 "base_LocalBackground",
149 ],
150 doc="Plugins to be run and their configuration"
151 )
152 algorithms = property(lambda self: self.plugins, doc="backwards-compatibility alias for plugins")
153 undeblended = SingleFramePlugin.registry.makeField(
154 multi=True,
155 default=[],
156 doc="Plugins to run on undeblended image"
157 )
158
159
161 """A subtask for measuring the properties of sources on a single exposure.
162
163 Parameters
164 ----------
165 schema : `lsst.afw.table.Schema`
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
168 task.
169 algMetadata : `lsst.daf.base.PropertyList`, optional
170 Used to record metadaa about algorithm execution. An empty
171 `lsst.daf.base.PropertyList` will be created if `None`.
172 **kwds
173 Keyword arguments forwarded to `BaseMeasurementTask`.
174 """
175
176 ConfigClass = SingleFrameMeasurementConfig
177
178 NOISE_SEED_MULTIPLIER = "NOISE_SEED_MULTIPLIER"
179 """Name by which the noise seed multiplier is recorded in metadata ('str').
180 """
181
182 NOISE_SOURCE = "NOISE_SOURCE"
183 """Name by which the noise source is recorded in metadata ('str').
184 """
185
186 NOISE_OFFSET = "NOISE_OFFSET"
187 """Name by which the noise offset is recorded in metadata ('str').
188 """
189
190 NOISE_EXPOSURE_ID = "NOISE_EXPOSURE_ID"
191 """Name by which the noise exposire ID is recorded in metadata ('str').
192 """
193
194 def __init__(self, schema, algMetadata=None, **kwds):
195 super(SingleFrameMeasurementTask, self).__init__(algMetadata=algMetadata, **kwds)
196 self.schema = schema
197 self.config.slots.setupSchema(self.schema)
198 self.initializePlugins(schema=self.schema)
199
200 # Check to see if blendedness is one of the plugins
201 if 'base_Blendedness' in self.plugins:
202 self.doBlendedness = True
203 self.blendPlugin = self.plugins['base_Blendedness']
204 else:
205 self.doBlendedness = False
206
207 @timeMethod
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.
210
211 Parameters
212 ----------
214 Catalog to be filled with the results of measurement. Must contain
215 all the `lsst.afw.table.SourceRecord`\ s to be measured (with
216 `lsst.afw.detection.Footprint`\ s attached), and have a schema
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
230 limit.
231 endOrder : `float`, optional
232 Final execution order (exclusive): measurements with
233 ``executionOrder >= endOrder`` are not executed. `None` for no
234 limit.
235 """
236 assert measCat.getSchema().contains(self.schema)
237 footprints = {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
238 for measRecord in measCat}
239
240 # noiseReplacer is used to fill the footprints with noise and save
241 # heavy footprints of the source pixels so that they can be restored
242 # one at a time for measurement. After the NoiseReplacer is
243 # constructed, all pixels in the exposure.getMaskedImage() which
244 # belong to objects in measCat will be replaced with noise
245
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:
251 algMetadata.addInt(self.NOISE_SEED_MULTIPLIER, self.config.noiseReplacer.noiseSeedMultiplier)
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:
255 algMetadata.addLong(self.NOISE_EXPOSURE_ID, exposureId)
256 else:
257 noiseReplacer = DummyNoiseReplacer()
258
259 self.runPlugins(noiseReplacer, measCat, exposure, beginOrder, endOrder)
260
261 def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None):
262 r"""Call the configured measument plugins on an image.
263
264 Parameters
265 ----------
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
270 all the `lsst.afw.table.SourceRecord`\ s to be measured (with
271 `lsst.afw.detection.Footprint`\ s attached), and have a schema
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
279 limit.
280 endOrder : `float`, optional
281 Final execution order (exclusive): measurements with
282 ``executionOrder >= endOrder`` are not executed. `None` for no
283 limit.
284 """
285 # First, create a catalog of all parentless sources. Loop through all
286 # the parent sources, first processing the children, then the parent.
287 measParentCat = measCat.getChildren(0)
288
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"))
295
296 # Wrap the task logger into a period logger
297 periodicLog = PeriodicLogger(self.log)
298
299 childrenIter = measCat.getChildren([measParentRecord.getId() for measParentRecord in measParentCat])
300 for parentIdx, (measParentRecord, measChildCat) in enumerate(zip(measParentCat, childrenIter)):
301 # first get all the children of this parent, insert footprint in
302 # turn, and measure
303 # TODO: skip this loop if there are no plugins configured for
304 # single-object mode
305 for measChildRecord in measChildCat:
306 noiseReplacer.insertSource(measChildRecord.getId())
307 self.callMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
308
309 if self.doBlendedness:
310 self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
311
312 noiseReplacer.removeSource(measChildRecord.getId())
313
314 # Then insert the parent footprint, and measure that
315 noiseReplacer.insertSource(measParentRecord.getId())
316 self.callMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
317
318 if self.doBlendedness:
319 self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
320
321 # Finally, process both parent and child set through measureN
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())
326 # Log a message if it has been a while since the last log.
327 periodicLog.log("Measurement complete for %d parents (and their children) out of %d",
328 parentIdx + 1, nMeasParentCat)
329
330 # When done, restore the exposure to its original state
331 noiseReplacer.end()
332
333 # Undeblended plugins only fire if we're running everything
334 if endOrder is None:
335 for sourceIndex, source in enumerate(measCat):
336 for plugin in self.undeblendedPlugins.iter():
337 self.doMeasurement(plugin, source, exposure)
338 # Log a message if it has been a while since the last log.
339 periodicLog.log("Undeblended measurement complete for %d sources out of %d",
340 sourceIndex + 1, nMeasCat)
341
342 # Now we loop over all of the sources one more time to compute the
343 # blendedness metrics
344 if self.doBlendedness:
345 for source in measCat:
346 self.blendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
347
348 def measure(self, measCat, exposure):
349 """Backwards-compatibility alias for `run`.
350 """
351 self.run(measCat, exposure)
table::Key< int > to
Class to describe the properties of a detected object from an image.
Definition: Footprint.h:63
Defines the fields and offsets for a table.
Definition: Schema.h:51
Record class that contains measurements made on a single exposure.
Definition: Source.h:78
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
Class for storing generic metadata.
Definition: PropertySet.h:66
def doMeasurement(self, plugin, measRecord, *args, **kwds)
def callMeasure(self, measRecord, *args, **kwds)
def callMeasureN(self, measCat, *args, **kwds)
def measure(self, measCat, exposure)
Definition: sfm.py:348
def __init__(self, schema, algMetadata=None, **kwds)
Definition: sfm.py:194
def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None)
Definition: sfm.py:261
def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None)
Definition: sfm.py:208
def measureN(self, measCat, exposure)
Definition: sfm.py:104
def __init__(self, config, name, schema, metadata, logName=None, **kwds)
Definition: sfm.py:83
def measure(self, measRecord, exposure)
Definition: sfm.py:86