LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
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
23 measurement tasks.
24 """
25 
26 import lsst.pipe.base
27 import lsst.pex.config
28 
29 from .pluginRegistry import PluginMap
30 from .exceptions import FatalAlgorithmError, MeasurementError
31 from .pluginsBase import BasePluginConfig, BasePlugin
32 from .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
39 FATAL_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 
196 class 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  ----------
386  measCat : `lsst.afw.table.SourceCatalog`
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.
433  measCat : `lsst.afw.table.SourceCatalog`
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)
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)