LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
baseMeasurement.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
22"""Base measurement task, which subclassed by the single frame and forced
23measurement tasks.
24"""
25
26import lsst.pipe.base
27import lsst.pex.config
28
29from .pluginRegistry import PluginMap
30from .exceptions import FatalAlgorithmError, MeasurementError
31from .pluginsBase import BasePluginConfig, BasePlugin
32from .noiseReplacer import NoiseReplacerConfig
33
34__all__ = ("BaseMeasurementPluginConfig", "BaseMeasurementPlugin",
35 "BaseMeasurementConfig", "BaseMeasurementTask")
36
37# Exceptions that the measurement tasks should always propagate up to their
38# callers
39FATAL_EXCEPTIONS = (MemoryError, FatalAlgorithmError)
40
41
43 """Base config class for all measurement plugins.
44
45 Notes
46 -----
47 Most derived classes will want to override `setDefaults` in order to
48 customize the default `executionOrder`.
49
50 A derived class whose corresponding Plugin class implements a do `measureN`
51 method should additionally add a bool `doMeasureN` field to replace the
52 bool class attribute defined here.
53 """
54
55 doMeasure = lsst.pex.config.Field(dtype=bool, default=True,
56 doc="whether to run this plugin in single-object mode")
57
58 doMeasureN = False # replace this class attribute with a Field if measureN-capable
59
60
62 """Base class for all measurement plugins.
63
64 Notes
65 -----
66 This is class is a placeholder for future behavior which will be shared
67 only between measurement plugins and is implemented for symmetry with the
68 measurement base plugin configuration class
69 """
70
71 pass
72
73
75 """Assign named plugins to measurement slots.
76
77 Slot configuration which assigns a particular named plugin to each of a set
78 of slots. Each slot allows a type of measurement to be fetched from the
79 `lsst.afw.table.SourceTable` without knowing which algorithm was used to
80 produced the data.
81
82 Notes
83 -----
84 The default algorithm for each slot must be registered, even if the default
85 is not used.
86 """
87
89 centroid = Field(dtype=str, default="base_SdssCentroid", optional=True,
90 doc="the name of the centroiding algorithm used to set source x,y")
91 shape = Field(dtype=str, default="base_SdssShape", optional=True,
92 doc="the name of the algorithm used to set source moments parameters")
93 psfShape = Field(dtype=str, default="base_SdssShape_psf", optional=True,
94 doc="the name of the algorithm used to set PSF moments parameters")
95 apFlux = Field(dtype=str, default="base_CircularApertureFlux_12_0", optional=True,
96 doc="the name of the algorithm used to set the source aperture instFlux slot")
97 modelFlux = Field(dtype=str, default="base_GaussianFlux", optional=True,
98 doc="the name of the algorithm used to set the source model instFlux slot")
99 psfFlux = Field(dtype=str, default="base_PsfFlux", optional=True,
100 doc="the name of the algorithm used to set the source psf instFlux slot")
101 gaussianFlux = Field(dtype=str, default="base_GaussianFlux", optional=True,
102 doc="the name of the algorithm used to set the source Gaussian instFlux slot")
103 calibFlux = Field(dtype=str, default="base_CircularApertureFlux_12_0", optional=True,
104 doc="the name of the instFlux measurement algorithm used for calibration")
105
106 def setupSchema(self, schema):
107 """Set up a slots in a schema following configuration directives.
108
109 Parameters
110 ----------
111 schema : `lsst.afw.table.Schema`
112 The schema in which slots will be set up.
113
114 Notes
115 -----
116 This is defined in this configuration class to support use in unit
117 tests without needing to construct an `lsst.pipe.base.Task` object.
118 """
119 aliases = schema.getAliasMap()
120 if self.centroidcentroid is not None:
121 aliases.set("slot_Centroid", self.centroidcentroid)
122 if self.shapeshape is not None:
123 aliases.set("slot_Shape", self.shapeshape)
124 if self.psfShapepsfShape is not None:
125 aliases.set("slot_PsfShape", self.psfShapepsfShape)
126 if self.apFluxapFlux is not None:
127 aliases.set("slot_ApFlux", self.apFluxapFlux)
128 if self.modelFluxmodelFlux is not None:
129 aliases.set("slot_ModelFlux", self.modelFluxmodelFlux)
130 if self.psfFluxpsfFlux is not None:
131 aliases.set("slot_PsfFlux", self.psfFluxpsfFlux)
132 if self.gaussianFluxgaussianFlux is not None:
133 aliases.set("slot_GaussianFlux", self.gaussianFluxgaussianFlux)
134 if self.calibFluxcalibFlux is not None:
135 aliases.set("slot_CalibFlux", self.calibFluxcalibFlux)
136
137
139 """Base configuration for all measurement driver tasks.
140
141 Examples
142 --------
143 Subclasses should define the 'plugins' and 'undeblended' registries, e.g.
144
145 .. code-block:: py
146
147 plugins = PluginBaseClass.registry.makeField(
148 multi=True,
149 default=[],
150 doc="Plugins to be run and their configuration"
151 )
152 undeblended = PluginBaseClass.registry.makeField(
153 multi=True,
154 default=[],
155 doc="Plugins to run on undeblended image"
156 )
157
158 where ``PluginBaseClass`` is the appropriate base class of the plugin
159 (e.g., `SingleFramePlugin` or `ForcedPlugin`).
160 """
161
163 dtype=SourceSlotConfig,
164 doc="Mapping from algorithms to special aliases in Source."
165 )
166
167 doReplaceWithNoise = lsst.pex.config.Field(
168 dtype=bool, default=True, optional=False,
169 doc='When measuring, replace other detected footprints with noise?')
170
172 dtype=NoiseReplacerConfig,
173 doc="configuration that sets how to replace neighboring sources with noise"
174 )
175 undeblendedPrefix = lsst.pex.config.Field(
176 dtype=str, default="undeblended_",
177 doc="Prefix to give undeblended plugins"
178 )
179
180 def validate(self):
181 lsst.pex.config.Config.validate(self)
182 if self.slotsslots.centroid is not None and self.slotsslots.centroid not in self.plugins.names:
183 raise ValueError("source centroid slot algorithm is not being run.")
184 if self.slotsslots.shape is not None and self.slotsslots.shape not in self.plugins.names:
185 raise ValueError("source shape slot algorithm '%s' is not being run." % self.slotsslots.shape)
186 for slot in (self.slotsslots.psfFlux, self.slotsslots.apFlux, self.slotsslots.modelFlux,
187 self.slotsslots.gaussianFlux, self.slotsslots.calibFlux):
188 if slot is not None:
189 for name in self.plugins.names:
190 if len(name) <= len(slot) and name == slot[:len(name)]:
191 break
192 else:
193 raise ValueError("source instFlux slot algorithm '%s' is not being run." % slot)
194
195
196class BaseMeasurementTask(lsst.pipe.base.Task):
197 """Ultimate base class for all measurement tasks.
198
199 Parameters
200 ----------
201 algMetadata : `lsst.daf.base.PropertyList` or `None`
202 Will be modified in-place to contain metadata about the plugins being
203 run. If `None`, an empty `~lsst.daf.base.PropertyList` will be
204 created.
205 **kwds
206 Additional arguments passed to `lsst.pipe.base.Task.__init__`.
207
208 Notes
209 -----
210 This base class for `SingleFrameMeasurementTask` and
211 `ForcedMeasurementTask` mostly exists to share code between the two, and
212 generally should not be used directly.
213 """
214
215 ConfigClass = BaseMeasurementConfig
216 _DefaultName = "measurement"
217
218 plugins = None
219 """Plugins to be invoked (`PluginMap`).
220
221 Initially empty, this will be populated as plugins are initialized. It
222 should be considered read-only.
223 """
224
225 algMetadata = None
226 """Metadata about active plugins (`lsst.daf.base.PropertyList`).
227
228 Contains additional information about active plugins to be saved with
229 the output catalog. Will be filled by subclasses.
230 """
231
232 def __init__(self, algMetadata=None, **kwds):
233 super(BaseMeasurementTask, self).__init__(**kwds)
234 self.pluginsplugins = PluginMap()
235 self.undeblendedPluginsundeblendedPlugins = PluginMap()
236 if algMetadata is None:
237 algMetadata = lsst.daf.base.PropertyList()
238 self.algMetadataalgMetadata = algMetadata
239
240 def initializePlugins(self, **kwds):
241 """Initialize plugins (and slots) according to configuration.
242
243 Parameters
244 ----------
245 **kwds
246 Keyword arguments forwarded directly to plugin constructors.
247
248 Notes
249 -----
250 Derived class constructors should call this method to fill the
251 `plugins` attribute and add corresponding output fields and slot
252 aliases to the output schema.
253
254 In addition to the attributes added by `BaseMeasurementTask.__init__`,
255 a ``schema``` attribute holding the output schema must be present
256 before this method is called.
257
258 Keyword arguments are forwarded directly to plugin constructors,
259 allowing derived classes to use plugins with different signatures.
260 """
261 # Make a place at the beginning for the centroid plugin to run first
262 # (because it's an OrderedDict, adding an empty element in advance
263 # means it will get run first when it's reassigned to the actual
264 # Plugin).
265 if self.config.slots.centroid is not None:
266 self.pluginsplugins[self.config.slots.centroid] = None
267 # Init the plugins, sorted by execution order. At the same time add to
268 # the schema
269 for executionOrder, name, config, PluginClass in sorted(self.config.plugins.apply()):
270 # Pass logName to the plugin if the plugin is marked as using it
271 # The task will use this name to log plugin errors, regardless.
272 if getattr(PluginClass, "hasLogName", False):
273 self.pluginsplugins[name] = PluginClass(config, name, metadata=self.algMetadataalgMetadata,
274 logName=self.log.getChild(name).name, **kwds)
275 else:
276 self.pluginsplugins[name] = PluginClass(config, name, metadata=self.algMetadataalgMetadata, **kwds)
277
278 # In rare circumstances (usually tests), the centroid slot not be
279 # coming from an algorithm, which means we'll have added something we
280 # don't want to the plugins map, and we should remove it.
281 if self.config.slots.centroid is not None and self.pluginsplugins[self.config.slots.centroid] is None:
282 del self.pluginsplugins[self.config.slots.centroid]
283 # Initialize the plugins to run on the undeblended image
284 for executionOrder, name, config, PluginClass in sorted(self.config.undeblended.apply()):
285 undeblendedName = self.config.undeblendedPrefix + name
286 if getattr(PluginClass, "hasLogName", False):
287 self.undeblendedPluginsundeblendedPlugins[name] = PluginClass(config, undeblendedName,
288 metadata=self.algMetadataalgMetadata,
289 logName=self.log.getChild(undeblendedName).name,
290 **kwds)
291 else:
292 self.undeblendedPluginsundeblendedPlugins[name] = PluginClass(config, undeblendedName,
293 metadata=self.algMetadataalgMetadata, **kwds)
294
295 def callMeasure(self, measRecord, *args, **kwds):
296 """Call ``measure`` on all plugins and consistently handle exceptions.
297
298 Parameters
299 ----------
300 measRecord : `lsst.afw.table.SourceRecord`
301 The record corresponding to the object being measured. Will be
302 updated in-place with the results of measurement.
303 *args
304 Positional arguments forwarded to ``plugin.measure``
305 **kwds
306 Keyword arguments. Two are handled locally:
307
308 beginOrder : `int`
309 Beginning execution order (inclusive). Measurements with
310 ``executionOrder`` < ``beginOrder`` are not executed. `None`
311 for no limit.
312
313 endOrder : `int`
314 Ending execution order (exclusive). Measurements with
315 ``executionOrder`` >= ``endOrder`` are not executed. `None`
316 for no limit.
317
318 Others are forwarded to ``plugin.measure()``.
319
320 Notes
321 -----
322 This method can be used with plugins that have different signatures;
323 the only requirement is that ``measRecord`` be the first argument.
324 Subsequent positional arguments and keyword arguments are forwarded
325 directly to the plugin.
326
327 This method should be considered "protected": it is intended for use by
328 derived classes, not users.
329 """
330 beginOrder = kwds.pop("beginOrder", None)
331 endOrder = kwds.pop("endOrder", None)
332 for plugin in self.pluginsplugins.iter():
333 if beginOrder is not None and plugin.getExecutionOrder() < beginOrder:
334 continue
335 if endOrder is not None and plugin.getExecutionOrder() >= endOrder:
336 break
337 self.doMeasurementdoMeasurement(plugin, measRecord, *args, **kwds)
338
339 def doMeasurement(self, plugin, measRecord, *args, **kwds):
340 """Call ``measure`` on the specified plugin.
341
342 Exceptions are handled in a consistent way.
343
344 Parameters
345 ----------
346 plugin : subclass of `BasePlugin`
347 Plugin that will be executed.
348 measRecord : `lsst.afw.table.SourceRecord`
349 The record corresponding to the object being measured. Will be
350 updated in-place with the results of measurement.
351 *args
352 Positional arguments forwarded to ``plugin.measure()``.
353 **kwds
354 Keyword arguments forwarded to ``plugin.measure()``.
355
356 Notes
357 -----
358 This method can be used with plugins that have different signatures;
359 the only requirement is that ``plugin`` and ``measRecord`` be the first
360 two arguments. Subsequent positional arguments and keyword arguments
361 are forwarded directly to the plugin.
362
363 This method should be considered "protected": it is intended for use by
364 derived classes, not users.
365 """
366 try:
367 plugin.measure(measRecord, *args, **kwds)
368 except FATAL_EXCEPTIONS:
369 raise
370 except MeasurementError as error:
371 self.log.getChild(plugin.name).debug(
372 "MeasurementError in %s.measure on record %s: %s",
373 plugin.name, measRecord.getId(), error)
374 plugin.fail(measRecord, error)
375 except Exception as error:
376 self.log.getChild(plugin.name).debug(
377 "Exception in %s.measure on record %s: %s",
378 plugin.name, measRecord.getId(), error)
379 plugin.fail(measRecord)
380
381 def callMeasureN(self, measCat, *args, **kwds):
382 """Call ``measureN`` on all plugins and consistently handle exceptions.
383
384 Parameters
385 ----------
387 Catalog containing only the records for the source family to be
388 measured, and where outputs should be written.
389 *args
390 Positional arguments forwarded to ``plugin.measure()``
391 **kwds
392 Keyword arguments. Two are handled locally:
393
394 beginOrder:
395 Beginning execution order (inclusive): Measurements with
396 ``executionOrder`` < ``beginOrder`` are not executed. `None`
397 for no limit.
398 endOrder:
399 Ending execution order (exclusive): measurements with
400 ``executionOrder`` >= ``endOrder`` are not executed. `None` for
401 no ``limit``.
402
403 Others are are forwarded to ``plugin.measure()``.
404
405 Notes
406 -----
407 This method can be used with plugins that have different signatures;
408 the only requirement is that ``measRecord`` be the first argument.
409 Subsequent positional arguments and keyword arguments are forwarded
410 directly to the plugin.
411
412 This method should be considered "protected": it is intended for use by
413 derived classes, not users.
414 """
415 beginOrder = kwds.pop("beginOrder", None)
416 endOrder = kwds.pop("endOrder", None)
417 for plugin in self.pluginsplugins.iterN():
418 if beginOrder is not None and plugin.getExecutionOrder() < beginOrder:
419 continue
420 if endOrder is not None and plugin.getExecutionOrder() >= endOrder:
421 break
422 self.doMeasurementNdoMeasurementN(plugin, measCat, *args, **kwds)
423
424 def doMeasurementN(self, plugin, measCat, *args, **kwds):
425 """Call ``measureN`` on the specified plugin.
426
427 Exceptions are handled in a consistent way.
428
429 Parameters
430 ----------
431 plugin : subclass of `BasePlugin`
432 Plugin that will be executed.
434 Catalog containing only the records for the source family to be
435 measured, and where outputs should be written.
436 *args
437 Positional arguments forwarded to ``plugin.measureN()``.
438 **kwds
439 Keyword arguments forwarded to ``plugin.measureN()``.
440
441 Notes
442 -----
443 This method can be used with plugins that have different signatures;
444 the only requirement is that the ``plugin`` and ``measCat`` be the
445 first two arguments. Subsequent positional arguments and keyword
446 arguments are forwarded directly to the plugin.
447
448 This method should be considered "protected": it is intended for use by
449 derived classes, not users.
450 """
451 try:
452 plugin.measureN(measCat, *args, **kwds)
453 except FATAL_EXCEPTIONS:
454 raise
455
456 except MeasurementError as error:
457 for measRecord in measCat:
458 self.log.getChild(plugin.name).debug(
459 "MeasurementError in %s.measureN on records %s-%s: %s",
460 plugin.name, measCat[0].getId(), measCat[-1].getId(), error)
461 plugin.fail(measRecord, error)
462 except Exception as error:
463 for measRecord in measCat:
464 plugin.fail(measRecord)
465 self.log.getChild(plugin.name).debug(
466 "Exception in %s.measureN on records %s-%s: %s",
467 plugin.name, measCat[0].getId(), measCat[-1].getId(), error)
table::Key< int > to
table::Key< int > a
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
Table class that contains measurements made on a single exposure.
Definition: Source.h:217
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
def doMeasurement(self, plugin, measRecord, *args, **kwds)
def doMeasurementN(self, plugin, measCat, *args, **kwds)
def callMeasure(self, measRecord, *args, **kwds)
def __init__(self, algMetadata=None, **kwds)
def callMeasureN(self, measCat, *args, **kwds)
bool defined
Definition: slots.cc:27