LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
base.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 """Base and utility classes for measurement frameworks
24 
25 This includes base classes for plugins and tasks and utility classes such as PluginMap
26 that are shared by the single-frame measurement framework and the forced measurement framework.
27 """
28 
29 import traceback
30 import collections
31 
32 import lsst.pipe.base
33 import lsst.pex.config
34 
35 from .baseLib import *
36 from .noiseReplacer import *
37 from .transforms import PassThroughTransform
38 
39 # Exceptions that the measurement tasks should always propagate up to their callers
40 FATAL_EXCEPTIONS = (MemoryError, FatalAlgorithmError)
41 
42 def generateAlgorithmName(AlgClass):
43  """Generate a string name for an algorithm class that strips away terms that are generally redundant
44  while (hopefully) remaining easy to trace to the code.
45 
46  The returned name will cobmine the package name, with any "lsst" and/or "meas" prefix removed,
47  with the class name, with any "Algorithm" suffix removed. For instance,
48  lsst.meas.base.SdssShapeAlgorithm becomes "base_SdssShape".
49  """
50  name = AlgClass.__name__
51  pkg = AlgClass.__module__
52  name = name.replace("Algorithm", "")
53  terms = pkg.split(".")
54  if terms[-1].endswith("Lib"):
55  terms = terms[:-1]
56  if terms[0] == "lsst":
57  terms = terms[1:]
58  if terms[0] == "meas":
59  terms = terms[1:]
60  if name.lower().startswith(terms[-1].lower()):
61  terms = terms[:-1]
62  return "%s_%s" % ("_".join(terms), name)
63 
64 class PluginRegistry(lsst.pex.config.Registry):
65  """!
66  Base class for plugin registries
67 
68  The Plugin class allowed in the registry is defined in the ctor of the registry.
69 
70  Single-frame and forced plugins have different registries.
71  """
72 
73  class Configurable(object):
74  """!
75  Class used as the actual element in the registry
76 
77  Rather than constructing a Plugin instance, its __call__ method
78  (invoked by RegistryField.apply) returns a tuple
79  of (executionOrder, name, config, PluginClass), which can then
80  be sorted before the plugins are instantiated.
81  """
82 
83  __slots__ = "PluginClass", "name"
84 
85  def __init__(self, name, PluginClass):
86  """!
87  Create a Configurable object for the given PluginClass and name
88  """
89  self.name = name
90  self.PluginClass = PluginClass
91 
92  @property
93  def ConfigClass(self): return self.PluginClass.ConfigClass
94 
95  def __call__(self, config):
96  return (self.PluginClass.getExecutionOrder(), self.name, config, self.PluginClass)
97 
98  def register(self, name, PluginClass):
99  """!
100  Register a Plugin class with the given name.
101 
102  The same Plugin may be registered multiple times with different names; this can
103  be useful if we often want to run it multiple times with different configuration.
104 
105  The name will be used as a prefix for all fields produced by the Plugin, and it
106  should generally contain the name of the Plugin or Algorithm class itself
107  as well as enough of the namespace to make it clear where to find the code.
108  """
109  lsst.pex.config.Registry.register(self, name, self.Configurable(name, PluginClass))
110 
111  def makeField(self, doc, default=None, optional=False, multi=False):
112  return lsst.pex.config.RegistryField(doc, self, default, optional, multi)
113 
114 
115 def register(name):
116  """!
117  A Python decorator that registers a class, using the given name, in its base class's PluginRegistry.
118  For example,
119  @code
120  @register("base_TransformedCentroid")
121  class ForcedTransformedCentroidPlugin(ForcedPlugin):
122  ...
123  @endcode
124  is equivalent to:
125  @code
126  class ForcedTransformedCentroidPlugin(ForcedPlugin):
127  ...
128  @ForcedPlugin.registry.register("base_TransformedCentroid", ForcedTransformedCentroidPlugin)
129  @endcode
130  """
131  def decorate(PluginClass):
132  PluginClass.registry.register(name, PluginClass)
133  return PluginClass
134  return decorate
135 
136 
137 class PluginMap(collections.OrderedDict):
138  """!
139  Map of plugins to be run for a task
140 
141  We assume Plugins are added to the PluginMap according to their "Execution Order", so this
142  class doesn't actually do any of the sorting (though it does have to maintain that order,
143  which it does by inheriting from OrderedDict).
144  """
145 
146  def iter(self):
147  """Call each plugin in the map which has a measure() method
148  """
149  for plugin in self.itervalues():
150  if plugin.config.doMeasure:
151  yield plugin
152 
153  def iterN(self):
154  """Call each plugin in the map which has a measureN() method
155  """
156  for plugin in self.itervalues():
157  if plugin.config.doMeasureN:
158  yield plugin
159 
160 class BasePluginConfig(lsst.pex.config.Config):
161  """!
162  Base class measurement Plugin config classes.
163 
164  Most derived classes will want to override setDefaults() in order to customize
165  the default exceutionOrder.
166 
167  A derived class whose corresponding Plugin class implements measureN() should
168  additionally add a bool doMeasureN field to replace the bool class attribute
169  defined here.
170  """
171 
172  doMeasure = lsst.pex.config.Field(dtype=bool, default=True,
173  doc="whether to run this plugin in single-object mode")
174 
175  doMeasureN = False # replace this class attribute with a Field if measureN-capable
176 
177 class BasePlugin(object):
178  """!
179  Base class for measurement plugins.
180 
181  This is the base class for SingleFramePlugin and ForcedPlugin; derived classes should inherit
182  from one of those.
183  """
184 
185  @staticmethod
187  """Sets the relative order of plugins (smaller numbers run first).
188 
189  In general, the following values should be used (intermediate values
190  are also allowed, but should be avoided unless they are needed):
191  0.0 ------ centroids and other algorithms that require only a Footprint and
192  its Peaks as input
193  1.0 ------ shape measurements and other algorithms that require
194  getCentroid() to return a good centroid in addition to a
195  Footprint and its Peaks.
196  2.0 ------ flux algorithms that require both getShape() and getCentroid()
197  in addition to the Footprint and its Peaks
198  3.0 ------ algorithms that operate on fluxes (e.g. classification,
199  aperture correction).
200 
201  Must be reimplemented (as a static or class method) by concrete derived classes.
202 
203  This approach was chosen instead of a full graph-based analysis of dependencies
204  because algorithm dependencies are usually both quite simple and entirely substitutable:
205  an algorithm that requires a centroid can typically make use of any centroid algorithms
206  outputs. That makes it relatively easy to figure out the correct value to use for any
207  particular algorithm.
208  """
209  raise NotImplementedError("All plugins must implement getExecutionOrder()")
210 
211  def __init__(self, config, name):
212  """!
213  Initialize the measurement object.
214 
215  @param[in] config An instance of this class's ConfigClass.
216  @param[in] name The string the plugin was registered with.
217  """
218  object.__init__(self)
219  self.config = config
220  self.name = name
221 
222  def fail(self, measRecord, error=None):
223  """!
224  Record a failure of the measure or measureN() method.
225 
226  When measure() raises an exception, the measurement framework
227  will call fail() to allow the plugin to set its failure flag
228  field(s). When measureN() raises an exception, fail() will be
229  called repeatedly with all the records that were being
230  measured.
231 
232  If the exception is a MeasurementError, it will be passed as
233  the error argument; in all other cases the error argument will
234  be None, and the failure will be logged by the measurement
235  framework as a warning.
236  """
237  traceback.print_exc()
238  message = ("The algorithm '%s' thinks it cannot fail, but it did; "
239  "please report this as a bug (the full traceback is above)."
240  % self.__class__.__name__)
241  raise NotImplementedError(message)
242 
243  @staticmethod
245  """!
246  Get the measurement transformation appropriate to this plugin.
247 
248  This returns a subclass of MeasurementTransform, which may be
249  instantiated with details of the algorithm configuration and then
250  called with information about calibration and WCS to convert from raw
251  measurement quantities to calibrated units. Calibrated data is then
252  provided in a separate output table.
253 
254  By default, we copy everything from the input to the output without
255  transformation.
256  """
257  return PassThroughTransform
258 
259 
260 class SourceSlotConfig(lsst.pex.config.Config):
261  """!
262  Slot configuration which assigns a particular named plugin to each of a set of
263  slots. Each slot allows a type of measurement to be fetched from the SourceTable
264  without knowing which algorithm was used to produced the data.
265 
266  NOTE: the default algorithm for each slot must be registered, even if the default is not used.
267  """
268 
269  centroid = lsst.pex.config.Field(dtype=str, default="base_SdssCentroid", optional=True,
270  doc="the name of the centroiding algorithm used to set source x,y")
271  shape = lsst.pex.config.Field(dtype=str, default="base_SdssShape", optional=True,
272  doc="the name of the algorithm used to set source moments parameters")
273  apFlux = lsst.pex.config.Field(dtype=str, default="base_CircularApertureFlux_0", optional=True,
274  doc="the name of the algorithm used to set the source aperture flux slot")
275  modelFlux = lsst.pex.config.Field(dtype=str, default="base_GaussianFlux", optional=True,
276  doc="the name of the algorithm used to set the source model flux slot")
277  psfFlux = lsst.pex.config.Field(dtype=str, default="base_PsfFlux", optional=True,
278  doc="the name of the algorithm used to set the source psf flux slot")
279  instFlux = lsst.pex.config.Field(dtype=str, default="base_GaussianFlux", optional=True,
280  doc="the name of the algorithm used to set the source inst flux slot")
281 
282  def setupSchema(self, schema):
283  """Convenience method to setup a Schema's slots according to the config definition.
284 
285  This is defined in the Config class to support use in unit tests without needing
286  to construct a Task object.
287  """
288  aliases = schema.getAliasMap()
289  if self.centroid is not None: aliases.set("slot_Centroid", self.centroid)
290  if self.shape is not None: aliases.set("slot_Shape", self.shape)
291  if self.apFlux is not None: aliases.set("slot_ApFlux", self.apFlux)
292  if self.modelFlux is not None: aliases.set("slot_ModelFlux", self.modelFlux)
293  if self.psfFlux is not None: aliases.set("slot_PsfFlux", self.psfFlux)
294  if self.instFlux is not None: aliases.set("slot_InstFlux", self.instFlux)
295 
296 
297 class BaseMeasurementConfig(lsst.pex.config.Config):
298  """!
299  Base config class for all measurement driver tasks.
300  """
301 
302  slots = lsst.pex.config.ConfigField(
303  dtype = SourceSlotConfig,
304  doc="Mapping from algorithms to special aliases in Source."
305  )
306 
307  doReplaceWithNoise = lsst.pex.config.Field(dtype=bool, default=True, optional=False,
308  doc='When measuring, replace other detected footprints with noise?')
309 
310  noiseReplacer = lsst.pex.config.ConfigField(
311  dtype=NoiseReplacerConfig,
312  doc="configuration that sets how to replace neighboring sources with noise"
313  )
314 
315  def validate(self):
316  lsst.pex.config.Config.validate(self)
317  if self.slots.centroid is not None and self.slots.centroid not in self.plugins.names:
318  raise ValueError("source centroid slot algorithm is not being run.")
319  if self.slots.shape is not None and self.slots.shape not in self.plugins.names:
320  raise ValueError("source shape slot algorithm '%s' is not being run." % self.slots.shape)
321  for slot in (self.slots.psfFlux, self.slots.apFlux, self.slots.modelFlux, self.slots.instFlux):
322  if slot is not None:
323  for name in self.plugins.names:
324  if len(name) <= len(slot) and name == slot[:len(name)]:
325  break
326  else:
327  raise ValueError("source flux slot algorithm '%s' is not being run." % slot)
328 
329 
330 ## @addtogroup LSST_task_documentation
331 ## @{
332 ## @page baseMeasurementTask
333 ## BaseMeasurementTask @copybrief BaseMeasurementTask
334 ## @}
335 
336 class BaseMeasurementTask(lsst.pipe.base.Task):
337  """!
338  Ultimate base class for all measurement tasks.
339 
340  This base class for SingleFrameMeasurementTask and ForcedMeasurementTask mostly exists to share
341  code between the two, and generally should not be used directly.
342  """
343 
344  ConfigClass = BaseMeasurementConfig
345  _DefaultName = "measurement"
346 
347  def __init__(self, algMetadata=None, **kwds):
348  """!
349  Constructor; only called by derived classes.
350 
351  @param[in] algMetadata An lsst.daf.base.PropertyList that will be filled with metadata
352  about the plugins being run. If None, an empty PropertyList will
353  be created.
354  @param[in] **kwds Additional arguments passed to lsst.pipe.base.Task.__init__.
355 
356  This attaches two public attributes to the class for use by derived classes and parent tasks:
357  - plugins: an empty PluginMap, which will eventually contain all active plugins that will by
358  invoked by the run() method (to be filled by subclasses). This should be considered read-only.
359  - algMetadata: a lsst.daf.base.PropertyList that will contain additional information about the
360  active plugins to be saved with the output catalog (to be filled by subclasses).
361  """
362  super(BaseMeasurementTask, self).__init__(**kwds)
364  if algMetadata is None:
365  algMetadata = lsst.daf.base.PropertyList()
366  self.algMetadata = algMetadata
367 
368  def initializePlugins(self, **kwds):
369  """Initialize the plugins (and slots) according to the configuration.
370 
371  Derived class constructors should call this method to fill the self.plugins
372  attribute and add correspond output fields and slot aliases to the output schema.
373 
374  In addition to the attributes added by BaseMeasurementTask.__init__, a self.schema
375  attribute holding the output schema must also be present before this method is called, .
376 
377  Keyword arguments are forwarded directly to plugin constructors, allowing derived
378  classes to use plugins with different signatures.
379  """
380  # Make a place at the beginning for the centroid plugin to run first (because it's an OrderedDict,
381  # adding an empty element in advance means it will get run first when it's reassigned to the
382  # actual Plugin).
383  if self.config.slots.centroid != None:
384  self.plugins[self.config.slots.centroid] = None
385  # Init the plugins, sorted by execution order. At the same time add to the schema
386  for executionOrder, name, config, PluginClass in sorted(self.config.plugins.apply()):
387  self.plugins[name] = PluginClass(config, name, metadata=self.algMetadata, **kwds)
388  # In rare circumstances (usually tests), the centroid slot not be coming from an algorithm,
389  # which means we'll have added something we don't want to the plugins map, and we should
390  # remove it.
391  if self.config.slots.centroid is not None and self.plugins[self.config.slots.centroid] is None:
392  del self.plugins[self.config.slots.centroid]
393 
394  def callMeasure(self, measRecord, *args, **kwds):
395  """!
396  Call the measure() method on all plugins, handling exceptions in a consistent way.
397 
398  @param[in,out] measRecord lsst.afw.table.SourceRecord that corresponds to the object being
399  measured, and where outputs should be written.
400  @param[in] *args Positional arguments forwarded to Plugin.measure()
401  @param[in] **kwds Keyword arguments forwarded to Plugin.measure()
402 
403  This method can be used with plugins that have different signatures; the only requirement is that
404  'measRecord' be the first argument. Subsequent positional arguments and keyword arguments are
405  forwarded directly to the plugin.
406 
407  This method should be considered "protected"; it is intended for use by derived classes, not users.
408  """
409  for plugin in self.plugins.iter():
410  try:
411  plugin.measure(measRecord, *args, **kwds)
412  except FATAL_EXCEPTIONS:
413  raise
414  except MeasurementError as error:
415  plugin.fail(measRecord, error)
416  except Exception as error:
417  self.log.warn("Error in %s.measure on record %s: %s"
418  % (plugin.name, measRecord.getId(), error))
419  plugin.fail(measRecord)
420 
421  def callMeasureN(self, measCat, *args, **kwds):
422  """!
423  Call the measureN() method on all plugins, handling exceptions in a consistent way.
424 
425  @param[in,out] measCat lsst.afw.table.SourceCatalog containing records for just
426  the source family to be measured, and where outputs should
427  be written.
428  @param[in] *args Positional arguments forwarded to Plugin.measure()
429  @param[in] **kwds Keyword arguments forwarded to Plugin.measure()
430 
431  This method can be used with plugins that have different signatures; the only requirement is that
432  'measRecord' be the first argument. Subsequent positional arguments and keyword arguments are
433  forwarded directly to the plugin.
434 
435  This method should be considered "protected"; it is intended for use by derived classes, not users.
436  """
437  for plugin in self.plugins.iterN():
438  try:
439  plugin.measureN(measCat, *args, **kwds)
440  except FATAL_EXCEPTIONS:
441  raise
442  except MeasurementError as error:
443  for measRecord in measCat:
444  plugin.fail(measRecord, error)
445  except Exception as error:
446  for measRecord in measCat:
447  plugin.fail(measRecord)
448  self.log.warn("Error in %s.measureN on records %s-%s: %s"
449  % (plugin.name, measCat[0].getId(), measCat[-1].getId(), error))
def __init__
Constructor; only called by derived classes.
Definition: base.py:347
Slot configuration which assigns a particular named plugin to each of a set of slots.
Definition: base.py:260
Base class measurement Plugin config classes.
Definition: base.py:160
def generateAlgorithmName
Definition: base.py:42
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
def __init__
Initialize the measurement object.
Definition: base.py:211
Class used as the actual element in the registry.
Definition: base.py:73
Ultimate base class for all measurement tasks.
Definition: base.py:336
Base class for plugin registries.
Definition: base.py:64
def __init__
Create a Configurable object for the given PluginClass and name.
Definition: base.py:85
def callMeasure
Call the measure() method on all plugins, handling exceptions in a consistent way.
Definition: base.py:394
def getTransformClass
Get the measurement transformation appropriate to this plugin.
Definition: base.py:244
def register
Register a Plugin class with the given name.
Definition: base.py:98
Base class for measurement plugins.
Definition: base.py:177
Map of plugins to be run for a task.
Definition: base.py:137
Base config class for all measurement driver tasks.
Definition: base.py:297
def register
A Python decorator that registers a class, using the given name, in its base class&#39;s PluginRegistry...
Definition: base.py:115
def callMeasureN
Call the measureN() method on all plugins, handling exceptions in a consistent way.
Definition: base.py:421
def fail
Record a failure of the measure or measureN() method.
Definition: base.py:222