LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
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 ----------
112 measCat : `lsst.afw.table.SourceCatalog`
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_SdssShape",
141 "base_GaussianFlux",
142 "base_PsfFlux",
143 "base_CircularApertureFlux",
144 "base_SkyCoord",
145 "base_Variance",
146 "base_Blendedness",
147 "base_LocalBackground",
148 "base_CompensatedTophatFlux",
149 "base_ClassificationSizeExtendedness",
150 ],
151 doc="Plugins to be run and their configuration"
152 )
153 algorithms = property(lambda self: self.plugins, doc="backwards-compatibility alias for plugins")
154 undeblended = SingleFramePlugin.registry.makeField(
155 multi=True,
156 default=[],
157 doc="Plugins to run on undeblended image"
158 )
159
160
162 """A subtask for measuring the properties of sources on a single exposure.
163
164 Parameters
165 ----------
166 schema : `lsst.afw.table.Schema`
167 Schema of the output resultant catalog. Will be updated to provide
168 fields to accept the outputs of plugins which will be executed by this
169 task.
170 algMetadata : `lsst.daf.base.PropertyList`, optional
171 Used to record metadaa about algorithm execution. An empty
172 `lsst.daf.base.PropertyList` will be created if `None`.
173 **kwds
174 Keyword arguments forwarded to `BaseMeasurementTask`.
175 """
176
177 ConfigClass = SingleFrameMeasurementConfig
178
179 NOISE_SEED_MULTIPLIER = "NOISE_SEED_MULTIPLIER"
180 """Name by which the noise seed multiplier is recorded in metadata ('str').
181 """
182
183 NOISE_SOURCE = "NOISE_SOURCE"
184 """Name by which the noise source is recorded in metadata ('str').
185 """
186
187 NOISE_OFFSET = "NOISE_OFFSET"
188 """Name by which the noise offset is recorded in metadata ('str').
189 """
190
191 NOISE_EXPOSURE_ID = "NOISE_EXPOSURE_ID"
192 """Name by which the noise exposire ID is recorded in metadata ('str').
193 """
194
195 def __init__(self, schema, algMetadata=None, **kwds):
196 super(SingleFrameMeasurementTask, self).__init__(algMetadata=algMetadata, **kwds)
197 self.schema = schema
198 self.config.slots.setupSchema(self.schema)
199 self.initializePlugins(schema=self.schema)
200
201 # Check to see if blendedness is one of the plugins
202 if 'base_Blendedness' in self.plugins:
203 self.doBlendedness = True
204 self.blendPlugin = self.plugins['base_Blendedness']
205 else:
206 self.doBlendedness = False
207
208 @timeMethod
209 def run(
210 self,
211 measCat,
212 exposure,
213 noiseImage=None,
214 exposureId=None,
215 beginOrder=None,
216 endOrder=None,
217 footprints=None,
218 ):
219 r"""Run single frame measurement over an exposure and source catalog.
220
221 Parameters
222 ----------
223 measCat : `lsst.afw.table.SourceCatalog`
224 Catalog to be filled with the results of measurement. Must contain
225 all the `lsst.afw.table.SourceRecord`\ s to be measured (with
226 `lsst.afw.detection.Footprint`\ s attached), and have a schema
227 that is a superset of ``self.schema``.
228 exposure : `lsst.afw.image.ExposureF`
229 Image containing the pixel data to be measured together with
230 associated PSF, WCS, etc.
231 noiseImage : `lsst.afw.image.ImageF`, optional
232 Can be used to specify the a predictable noise replacement field
233 for testing purposes.
234 exposureId : `int`, optional
235 Unique exposure identifier used to calculate the random number
236 generator seed during noise replacement.
237 beginOrder : `float`, optional
238 Start execution order (inclusive): measurements with
239 ``executionOrder < beginOrder`` are not executed. `None` for no
240 limit.
241 endOrder : `float`, optional
242 Final execution order (exclusive): measurements with
243 ``executionOrder >= endOrder`` are not executed. `None` for no
244 limit.
245 footprints : `dict` {`int`: `lsst.afw.detection.Footprint`}, optional
246 List of footprints to use for noise replacement. If this is not
247 supplied then the footprints from the measCat are used.
248 """
249 assert measCat.getSchema().contains(self.schema)
250 if footprints is None:
251 footprints = self.getFootprintsFromCatalog(measCat)
252
253 # noiseReplacer is used to fill the footprints with noise and save
254 # heavy footprints of the source pixels so that they can be restored
255 # one at a time for measurement. After the NoiseReplacer is
256 # constructed, all pixels in the exposure.getMaskedImage() which
257 # belong to objects in measCat will be replaced with noise
258
259 if self.config.doReplaceWithNoise:
260 noiseReplacer = NoiseReplacer(self.config.noiseReplacer, exposure, footprints,
261 noiseImage=noiseImage, log=self.log, exposureId=exposureId)
262 algMetadata = measCat.getMetadata()
263 if algMetadata is not None:
264 algMetadata.addInt(self.NOISE_SEED_MULTIPLIERNOISE_SEED_MULTIPLIER, self.config.noiseReplacer.noiseSeedMultiplier)
265 algMetadata.addString(self.NOISE_SOURCENOISE_SOURCE, self.config.noiseReplacer.noiseSource)
266 algMetadata.addDouble(self.NOISE_OFFSETNOISE_OFFSET, self.config.noiseReplacer.noiseOffset)
267 if exposureId is not None:
268 algMetadata.addLong(self.NOISE_EXPOSURE_IDNOISE_EXPOSURE_ID, exposureId)
269 else:
270 noiseReplacer = DummyNoiseReplacer()
271
272 self.runPlugins(noiseReplacer, measCat, exposure, beginOrder, endOrder)
273
274 def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None):
275 r"""Call the configured measument plugins on an image.
276
277 Parameters
278 ----------
279 noiseReplacer : `NoiseReplacer`
280 Used to fill sources not being measured with noise.
281 measCat : `lsst.afw.table.SourceCatalog`
282 Catalog to be filled with the results of measurement. Must contain
283 all the `lsst.afw.table.SourceRecord`\ s to be measured (with
284 `lsst.afw.detection.Footprint`\ s attached), and have a schema
285 that is a superset of ``self.schema``.
286 exposure : `lsst.afw.image.ExposureF`
287 Image containing the pixel data to be measured together with
288 associated PSF, WCS, etc.
289 beginOrder : `float`, optional
290 Start execution order (inclusive): measurements with
291 ``executionOrder < beginOrder`` are not executed. `None` for no
292 limit.
293 endOrder : `float`, optional
294 Final execution order (exclusive): measurements with
295 ``executionOrder >= endOrder`` are not executed. `None` for no
296 limit.
297 """
298 # First, create a catalog of all parentless sources. Loop through all
299 # the parent sources, first processing the children, then the parent.
300 measParentCat = measCat.getChildren(0)
301
302 nMeasCat = len(measCat)
303 nMeasParentCat = len(measParentCat)
304 self.log.info("Measuring %d source%s (%d parent%s, %d child%s) ",
305 nMeasCat, ("" if nMeasCat == 1 else "s"),
306 nMeasParentCat, ("" if nMeasParentCat == 1 else "s"),
307 nMeasCat - nMeasParentCat, ("" if nMeasCat - nMeasParentCat == 1 else "ren"))
308
309 # Wrap the task logger into a period logger
310 periodicLog = PeriodicLogger(self.log)
311
312 childrenIter = measCat.getChildren([measParentRecord.getId() for measParentRecord in measParentCat])
313 for parentIdx, (measParentRecord, measChildCat) in enumerate(zip(measParentCat, childrenIter)):
314 # first get all the children of this parent, insert footprint in
315 # turn, and measure
316 # TODO: skip this loop if there are no plugins configured for
317 # single-object mode
318 for measChildRecord in measChildCat:
319 noiseReplacer.insertSource(measChildRecord.getId())
320 self.callMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
321
322 if self.doBlendedness:
323 self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
324
325 noiseReplacer.removeSource(measChildRecord.getId())
326
327 # Then insert the parent footprint, and measure that
328 noiseReplacer.insertSource(measParentRecord.getId())
329 self.callMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
330
331 if self.doBlendedness:
332 self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
333
334 # Finally, process both parent and child set through measureN
335 self.callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
336 beginOrder=beginOrder, endOrder=endOrder)
337 self.callMeasureN(measChildCat, exposure, beginOrder=beginOrder, endOrder=endOrder)
338 noiseReplacer.removeSource(measParentRecord.getId())
339 # Log a message if it has been a while since the last log.
340 periodicLog.log("Measurement complete for %d parents (and their children) out of %d",
341 parentIdx + 1, nMeasParentCat)
342
343 # When done, restore the exposure to its original state
344 noiseReplacer.end()
345
346 # Undeblended plugins only fire if we're running everything
347 if endOrder is None:
348 for sourceIndex, source in enumerate(measCat):
349 for plugin in self.undeblendedPlugins.iter():
350 self.doMeasurement(plugin, source, exposure)
351 # Log a message if it has been a while since the last log.
352 periodicLog.log("Undeblended measurement complete for %d sources out of %d",
353 sourceIndex + 1, nMeasCat)
354
355 # Now we loop over all of the sources one more time to compute the
356 # blendedness metrics
357 if self.doBlendedness:
358 for source in measCat:
359 self.blendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
360
361 def measure(self, measCat, exposure):
362 """Backwards-compatibility alias for `run`.
363 """
364 self.run(measCat, exposure)
doMeasurement(self, plugin, measRecord, *args, **kwds)
__init__(self, schema, algMetadata=None, **kwds)
Definition sfm.py:195
runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None)
Definition sfm.py:274
run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None, footprints=None)
Definition sfm.py:218
measure(self, measRecord, exposure)
Definition sfm.py:86
measureN(self, measCat, exposure)
Definition sfm.py:104
__init__(self, config, name, schema, metadata, logName=None, **kwds)
Definition sfm.py:83