LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
base.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010, 2014 LSST Corporation.
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 
38 FATAL_EXCEPTIONS = (MemoryError, FatalAlgorithmError) # Exceptions that the framework should always propagate up
39 
40 def generateAlgorithmName(AlgClass):
41  """Generate a string name for an algorithm class that strips away terms that are generally redundant
42  while (hopefully) remaining easy to trace to the code.
43 
44  The returned name will cobmine the package name, with any "lsst" and/or "meas" prefix removed,
45  with the class name, with any "Algorithm" suffix removed. For instance,
46  lsst.meas.base.SdssShapeAlgorithm becomes "base_SdssShape".
47  """
48  name = AlgClass.__name__
49  pkg = AlgClass.__module__
50  name = name.replace("Algorithm", "")
51  terms = pkg.split(".")
52  if terms[-1].endswith("Lib"):
53  terms = terms[:-1]
54  if terms[0] == "lsst":
55  terms = terms[1:]
56  if terms[0] == "meas":
57  terms = terms[1:]
58  if name.lower().startswith(terms[-1].lower()):
59  terms = terms[:-1]
60  return "%s_%s" % ("_".join(terms), name)
61 
62 # Translation map from new PixelFlags to old ones defined in meas_algorithms
63 
64 _flagMap = {
65  "base_PixelFlags_flag_bad":"flags.pixel.bad",
66  "base_PixelFlags_flag_edge":"flags.pixel.edge",
67  "base_PixelFlags_flag_interpolated":"flags.pixel.interpolated.any",
68  "base_PixelFlags_flag_saturated":"flags.pixel.saturated.any",
69  "base_PixelFlags_flag_cr":"flags.pixel.cr.any",
70  "base_PixelFlags_flag_interpolatedCenter":"flags.pixel.interpolated.center",
71  "base_PixelFlags_flag_saturatedCenter":"flags.pixel.saturated.center",
72  "base_PixelFlags_flag_crCenter":"flags.pixel.cr.center",
73  }
74 
75 def Version0FlagMapper(flags):
76  _flags = []
77  for name in flags:
78  if name in _flagMap.keys():
79  _flags.append(_flagMap[name])
80  else:
81  _flags.append(name)
82  return _flags
83 
84 def addDependencyFlagAliases(AlgClass, name, schema):
85  """!
86  Add aliases to flag fields that an algorithm depends on.
87 
88  When an algorithm relies on the slot centroid or shape as an input, it can fail (or be unreliable)
89  simply because the algorithm it depends on failed. In that case, we already have a flag field
90  that indicates why the algorithm failed (the slot failure flag), but it's not obviously connected
91  to the failure of the dependent algorithm. This function adds aliases to the appropriate slot flag
92  for C++ algorithms that depend on that slot, in the namespace of the dependent algorithm.
93 
94  @param[in] AlgClass Swig-wrappped C++ algorithm class; must have a class attribute Input that
95  constains the type of Input object it requires.
96  @param[in] name Name of the dependent algorithm
97  @param[out] schema Schema to add the aliases to
98 
99  """
100  if issubclass(AlgClass.Input, FootprintCentroidInput):
101  schema.getAliasMap().set("%s_flag_badCentroid" % name, "slot_Centroid_flag")
102  if issubclass(AlgClass.Input, FootprintCentroidShapeInput):
103  schema.getAliasMap().set("%s_flag_badShape" % name, "slot_Shape_flag")
104 
105 
106 class PluginRegistry(lsst.pex.config.Registry):
107  """!
108  Base class for plugin registries
109 
110  The Plugin class allowed in the registry is defined on the ctor of the registry
111  Single-frame and forced plugins have different registries.
112  """
113 
114  class Configurable(object):
115  """!
116  Class used as the actual element in the registry
117 
118  Rather than constructing a Plugin instance, it returns a tuple
119  of (runlevel, name, config, PluginClass), which can then
120  be sorted before the plugins are instantiated.
121  """
122 
123  __slots__ = "PluginClass", "name"
124 
125  def __init__(self, name, PluginClass):
126  """!
127  Initialize registry with Plugin Class
128  """
129  self.name = name
130  self.PluginClass = PluginClass
131 
132  @property
133  def ConfigClass(self): return self.PluginClass.ConfigClass
134 
135  def __call__(self, config):
136  return (config.executionOrder, self.name, config, self.PluginClass)
137 
138  def register(self, name, PluginClass):
139  """!
140  Register a Plugin class with the given name.
141 
142  The same Plugin may be registered multiple times with different names; this can
143  be useful if we often want to run it multiple times with different configuration.
144 
145  The name will be used as a prefix for all fields produced by the Plugin, and it
146  should generally contain the name of the Plugin or Algorithm class itself
147  as well as enough of the namespace to make it clear where to find the code.
148  """
149  lsst.pex.config.Registry.register(self, name, self.Configurable(name, PluginClass))
150 
151  def makeField(self, doc, default=None, optional=False, multi=False):
152  return lsst.pex.config.RegistryField(doc, self, default, optional, multi)
153 
154 
155 def register(name):
156  """!
157  A Python decorator that registers a class, using the given name, in its base class's PluginRegistry.
158  For example,
159  @code
160  @register("base_TransformedCentroid")
161  class ForcedTransformedCentroidPlugin(ForcedPlugin):
162  ...
163  @endcode
164  is equivalent to:
165  @code
166  class ForcedTransformedCentroidPlugin(ForcedPlugin):
167  ...
168  @ForcedPlugin.registry.register("base_TransformedCentroid", ForcedTransformedCentroidPlugin)
169  @enedcode
170  """
171  def decorate(PluginClass):
172  PluginClass.registry.register(name, PluginClass)
173  return PluginClass
174  return decorate
175 
176 
177 class PluginMap(collections.OrderedDict):
178  """!
179  Map of plugins to be run for a task
180 
181  We assume Plugins are added to the PluginMap according to their executionOrder, so this
182  class doesn't actually do any of the sorting (though it does have to maintain that order).
183  """
184 
185  def iter(self):
186  """Call each plugin in the map which has a measure() method
187  """
188  for plugin in self.itervalues():
189  if plugin.config.doMeasure:
190  yield plugin
191 
192  def iterN(self):
193  """Call each plugin in the map which has a measureN() method
194  """
195  for plugin in self.itervalues():
196  if plugin.config.doMeasureN:
197  yield plugin
198 
199 class BasePluginConfig(lsst.pex.config.Config):
200  """!
201  Base class measurement Plugin config classes.
202 
203  Most derived classes will want to override setDefaults() in order to customize
204  the default exceutionOrder.
205 
206  A derived class whose corresponding Plugin class implements measureN() should
207  additionally add a bool doMeasureN field to replace the bool class attribute
208  defined here.
209  """
210 
211  executionOrder = lsst.pex.config.Field(
212  dtype=float, default=2.0,
213  doc="""Sets the relative order of plugins (smaller numbers run first).
214 
215 In general, the following values should be used (intermediate values
216 are also allowed, but should be avoided unless they are needed):
217  0.0 ------ centroids and other algorithms that require only a Footprint and
218  its Peaks as input
219  1.0 ------ shape measurements and other algorithms that require
220  getCentroid() to return a good centroid in addition to a
221  Footprint and its Peaks.
222  2.0 ------ flux algorithms that require both getShape() and getCentroid()
223  in addition to the Footprint and its Peaks
224  3.0 ------ Corrections applied to fluxes (i.e. aperture corrections, tying
225  model to PSF fluxes). All flux measurements should have an
226  executionOrder < 3.0, while all algorithms that rely on corrected
227  fluxes (i.e. classification) should have executionOrder > 3.0.
228 """
229  )
230 
231 
232  doMeasure = lsst.pex.config.Field(dtype=bool, default=True,
233  doc="whether to run this plugin in single-object mode")
234 
235  doMeasureN = False # replace this class attribute with a Field if measureN-capable
236 
237 class BasePlugin(object):
238  """!
239  Base class for measurement plugins.
240 
241  This is the base class for SingleFramePlugin and ForcedPlugin; derived classes should inherit
242  from one of those.
243  """
244 
245  def fail(self, measRecord, error=None):
246  """!
247  Record a failure of the measure or measureN() method.
248 
249  When measure() raises an exception, the measurement framework
250  will call fail() to allow the plugin to set its failure flag
251  field(s). When measureN() raises an exception, fail() will be
252  called repeatedly with all the records that were being
253  measured.
254 
255  If the exception is a MeasurementError, it will be passed as
256  the error argument; in all other cases the error argument will
257  be None, and the failure will be logged by the measurement
258  framework as a warning.
259  """
260  traceback.print_exc()
261  message = ("The algorithm '%s' thinks it cannot fail, but it did; "
262  "please report this as a bug (the full traceback is above)."
263  % self.__class__.__name__)
264  raise NotImplementedError(message)
265 
266 class SourceSlotConfig(lsst.pex.config.Config):
267  """!
268  Slot configuration which assigns a particular named plugin to each of a set of
269  slots. Each slot allows a type of measurement to be fetched from the SourceTable
270  without knowing which algorithm was used to produced the data.
271 
272  NOTE: the default algorithm for each slot must be registered, even if the default is not used.
273  """
274 
275  centroid = lsst.pex.config.Field(dtype=str, default="base_SdssCentroid", optional=True,
276  doc="the name of the centroiding algorithm used to set source x,y")
277  shape = lsst.pex.config.Field(dtype=str, default="base_SdssShape", optional=True,
278  doc="the name of the algorithm used to set source moments parameters")
279  apFlux = lsst.pex.config.Field(dtype=str, default="base_SincFlux", optional=True,
280  doc="the name of the algorithm used to set the source aperture flux slot")
281  modelFlux = lsst.pex.config.Field(dtype=str, default="base_GaussianFlux", optional=True,
282  doc="the name of the algorithm used to set the source model flux slot")
283  psfFlux = lsst.pex.config.Field(dtype=str, default="base_PsfFlux", optional=True,
284  doc="the name of the algorithm used to set the source psf flux slot")
285  instFlux = lsst.pex.config.Field(dtype=str, default="base_GaussianFlux", optional=True,
286  doc="the name of the algorithm used to set the source inst flux slot")
287 
288  def setupSchema(self, schema):
289  """Convenience method to setup a Schema's slots according to the config definition.
290 
291  This is defined in the Config class to support use in unit tests without needing
292  to construct a Task object.
293  """
294  aliases = schema.getAliasMap()
295  if self.centroid is not None: aliases.set("slot_Centroid", self.centroid)
296  if self.shape is not None: aliases.set("slot_Shape", self.shape)
297  if self.apFlux is not None: aliases.set("slot_ApFlux", self.apFlux)
298  if self.modelFlux is not None: aliases.set("slot_ModelFlux", self.modelFlux)
299  if self.psfFlux is not None: aliases.set("slot_PsfFlux", self.psfFlux)
300  if self.instFlux is not None: aliases.set("slot_InstFlux", self.instFlux)
301 
302 
303 class BaseMeasurementConfig(lsst.pex.config.Config):
304  """!
305  Base config class for all measurement driver tasks.
306  """
307 
308  slots = lsst.pex.config.ConfigField(
309  dtype = SourceSlotConfig,
310  doc="Mapping from algorithms to special aliases in Source."
311  )
312 
313  doReplaceWithNoise = lsst.pex.config.Field(dtype=bool, default=True, optional=False,
314  doc='When measuring, replace other detected footprints with noise?')
315 
316  noiseReplacer = lsst.pex.config.ConfigField(
317  dtype=NoiseReplacerConfig,
318  doc="configuration that sets how to replace neighboring sources with noise"
319  )
320 
321  def validate(self):
322  lsst.pex.config.Config.validate(self)
323  if self.slots.centroid is not None and self.slots.centroid not in self.plugins.names:
324  raise ValueError("source centroid slot algorithm is not being run.")
325  if self.slots.shape is not None and self.slots.shape not in self.plugins.names:
326  raise ValueError("source shape slot algorithm '%s' is not being run." % self.slots.shape)
327  for slot in (self.slots.psfFlux, self.slots.apFlux, self.slots.modelFlux, self.slots.instFlux):
328  if slot is not None:
329  for name in self.plugins.names:
330  if len(name) <= len(slot) and name == slot[:len(name)]:
331  break
332  else:
333  raise ValueError("source flux slot algorithm '%s' is not being run." % slot)
334 
335 
336 ## @addtogroup LSST_task_documentation
337 ## @{
338 ## @page baseMeasurementTask
339 ## BaseMeasurementTask @copybrief BaseMeasurementTask
340 ## @}
341 
342 class BaseMeasurementTask(lsst.pipe.base.Task):
343  """!
344  Ultimate base class for all measurement tasks.
345 
346  This base class for SingleFrameMeasurementTask and ForcedMeasurementTask mostly exists to share
347  code between the two, and generally should not be used directly.
348  """
349 
350  ConfigClass = BaseMeasurementConfig
351  _DefaultName = "measurement"
352  tableVersion = 1
353 
354  def __init__(self, algMetadata=None, **kwds):
355  """!
356  Constructor; only called by derived classes.
357 
358  @param[in] algMetadata An lsst.daf.base.PropertyList that will be filled with metadata
359  about the plugins being run. If None, an empty PropertyList will
360  be created.
361  @param[in] **kwds Additional arguments passed to lsst.pipe.base.Task.__init__.
362 
363  This attaches two public attributes to the class for use by derived classes and parent tasks:
364  - plugins: an empty PluginMap, which will eventually contain all active plugins that will by
365  invoked by the run() method (to be filled by subclasses). This should be considered read-only.
366  - algMetadata: a lsst.daf.base.PropertyList that will contain additional information about the
367  active plugins to be saved with the output catalog (to be filled by subclasses).
368  """
369  super(BaseMeasurementTask, self).__init__(**kwds)
371  if algMetadata is None:
372  algMetadata = lsst.daf.base.PropertyList()
373  self.algMetadata = algMetadata
374 
375  def callMeasure(self, measRecord, *args, **kwds):
376  """!
377  Call the measure() method on all plugins, handling exceptions in a consistent way.
378 
379  @param[in,out] measRecord lsst.afw.table.SourceRecord that corresponds to the object being
380  measured, and where outputs should be written.
381  @param[in] *args Positional arguments forwarded to Plugin.measure()
382  @param[in] **kwds Keyword arguments forwarded to Plugin.measure()
383 
384  This method can be used with plugins that have different signatures; the only requirement is that
385  'measRecord' be the first argument. Subsequent positional arguments and keyword arguments are
386  forwarded directly to the plugin.
387 
388  This method should be considered "protected"; it is intended for use by derived classes, not users.
389  """
390  for plugin in self.plugins.iter():
391  try:
392  plugin.measure(measRecord, *args, **kwds)
393  except FATAL_EXCEPTIONS:
394  raise
395  except MeasurementError as error:
396  plugin.fail(measRecord, error)
397  except Exception as error:
398  self.log.warn("Error in %s.measure on record %s: %s"
399  % (plugin.name, measRecord.getId(), error))
400  plugin.fail(measRecord)
401 
402  def callMeasureN(self, measCat, *args, **kwds):
403  """!
404  Call the measureN() method on all plugins, handling exceptions in a consistent way.
405 
406  @param[in,out] measCat lsst.afw.table.SourceCatalog containing records for just
407  the source family to be measured, and where outputs should
408  be written.
409  @param[in] *args Positional arguments forwarded to Plugin.measure()
410  @param[in] **kwds Keyword arguments forwarded to Plugin.measure()
411 
412  This method can be used with plugins that have different signatures; the only requirement is that
413  'measRecord' be the first argument. Subsequent positional arguments and keyword arguments are
414  forwarded directly to the plugin.
415 
416  This method should be considered "protected"; it is intended for use by derived classes, not users.
417  """
418  for plugin in self.plugins.iterN():
419  try:
420  plugin.measureN(measCat, *args, **kwds)
421  except FATAL_EXCEPTIONS:
422  raise
423  except MeasurementError as error:
424  for measRecord in measCat:
425  plugin.fail(measRecord, error)
426  except Exception as error:
427  for measRecord in measCat:
428  plugin.fail(measRecord)
429  self.log.warn("Error in %s.measureN on records %s-%s: %s"
430  % (plugin.name, measCat[0].getId(), measCat[-1].getId(), error))
def __init__
Constructor; only called by derived classes.
Definition: base.py:354
Slot configuration which assigns a particular named plugin to each of a set of slots.
Definition: base.py:266
Base class measurement Plugin config classes.
Definition: base.py:199
def generateAlgorithmName
Definition: base.py:40
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
Class used as the actual element in the registry.
Definition: base.py:114
Ultimate base class for all measurement tasks.
Definition: base.py:342
Base class for plugin registries.
Definition: base.py:106
def __init__
Initialize registry with Plugin Class.
Definition: base.py:125
def addDependencyFlagAliases
Add aliases to flag fields that an algorithm depends on.
Definition: base.py:84
def callMeasure
Call the measure() method on all plugins, handling exceptions in a consistent way.
Definition: base.py:375
def register
Register a Plugin class with the given name.
Definition: base.py:138
def Version0FlagMapper
Definition: base.py:75
Base class for measurement plugins.
Definition: base.py:237
Map of plugins to be run for a task.
Definition: base.py:177
Base config class for all measurement driver tasks.
Definition: base.py:303
def register
A Python decorator that registers a class, using the given name, in its base class&#39;s PluginRegistry...
Definition: base.py:155
def callMeasureN
Call the measureN() method on all plugins, handling exceptions in a consistent way.
Definition: base.py:402
def fail
Record a failure of the measure or measureN() method.
Definition: base.py:245