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