LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
baseMeasurement.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 measurement task, which subclassed by the single frame and forced measurement tasks.
24 """
25 import traceback
26 
27 import lsst.pipe.base
28 import lsst.pex.config
29 
30 from .applyApCorr import ApplyApCorrTask
31 from .pluginRegistry import PluginMap
32 from .baseLib import FatalAlgorithmError, MeasurementError
33 from .noiseReplacer import NoiseReplacerConfig
34 from .transforms import PassThroughTransform
35 
36 __all__ = ("BasePluginConfig", "BasePlugin", "BaseMeasurementConfig", "BaseMeasurementTask")
37 
38 # Exceptions that the measurement tasks should always propagate up to their callers
39 FATAL_EXCEPTIONS = (MemoryError, FatalAlgorithmError)
40 
41 class BasePluginConfig(lsst.pex.config.Config):
42  """!
43  Base class measurement Plugin config classes.
44 
45  Most derived classes will want to override setDefaults() in order to customize
46  the default exceutionOrder.
47 
48  A derived class whose corresponding Plugin class implements measureN() should
49  additionally add a bool doMeasureN field to replace the bool class attribute
50  defined here.
51  """
52 
53  doMeasure = lsst.pex.config.Field(dtype=bool, default=True,
54  doc="whether to run this plugin in single-object mode")
55 
56  doMeasureN = False # replace this class attribute with a Field if measureN-capable
57 
58 class BasePlugin(object):
59  """!
60  Base class for measurement plugins.
61 
62  This is the base class for SingleFramePlugin and ForcedPlugin; derived classes should inherit
63  from one of those.
64  """
65  # named class constants for execution order
66  CENTROID_ORDER = 0.0
67  SHAPE_ORDER = 1.0
68  FLUX_ORDER = 2.0
69  APCORR_ORDER = 4.0
70  CLASSIFY_ORDER = 5.0
71 
72  @classmethod
74  """Sets the relative order of plugins (smaller numbers run first).
75 
76  In general, the following class constants should be used (other values
77  are also allowed, but should be avoided unless they are needed):
78  CENTROID_ORDER centroids and other algorithms that require only a Footprint and its Peaks as input
79  SHAPE_ORDER shape measurements and other algorithms that require getCentroid() to return
80  a good centroid (in addition to a Footprint and its Peaks).
81  FLUX_ORDER flux algorithms that require both getShape() and getCentroid(),
82  in addition to a Footprint and its Peaks
83  APCORR_ORDER aperture corrections
84  CLASSIFY_ORDER algorithms that operate on aperture-corrected fluxes
85 
86  Must be reimplemented as a class method by concrete derived classes.
87 
88  This approach was chosen instead of a full graph-based analysis of dependencies
89  because algorithm dependencies are usually both quite simple and entirely substitutable:
90  an algorithm that requires a centroid can typically make use of any centroid algorithms
91  outputs. That makes it relatively easy to figure out the correct value to use for any
92  particular algorithm.
93  """
94  raise NotImplementedError("All plugins must implement getExecutionOrder()")
95 
96  def __init__(self, config, name):
97  """!
98  Initialize the measurement object.
99 
100  @param[in] config An instance of this class's ConfigClass.
101  @param[in] name The string the plugin was registered with.
102  """
103  object.__init__(self)
104  self.config = config
105  self.name = name
106 
107  def fail(self, measRecord, error=None):
108  """!
109  Record a failure of the measure or measureN() method.
110 
111  When measure() raises an exception, the measurement framework
112  will call fail() to allow the plugin to set its failure flag
113  field(s). When measureN() raises an exception, fail() will be
114  called repeatedly with all the records that were being
115  measured.
116 
117  If the exception is a MeasurementError, it will be passed as
118  the error argument; in all other cases the error argument will
119  be None, and the failure will be logged by the measurement
120  framework as a warning.
121  """
122  traceback.print_exc()
123  message = ("The algorithm '%s' thinks it cannot fail, but it did; "
124  "please report this as a bug (the full traceback is above)."
125  % self.__class__.__name__)
126  raise NotImplementedError(message)
127 
128  @staticmethod
130  """!
131  Get the measurement transformation appropriate to this plugin.
132 
133  This returns a subclass of MeasurementTransform, which may be
134  instantiated with details of the algorithm configuration and then
135  called with information about calibration and WCS to convert from raw
136  measurement quantities to calibrated units. Calibrated data is then
137  provided in a separate output table.
138 
139  By default, we copy everything from the input to the output without
140  transformation.
141  """
142  return PassThroughTransform
143 
144 
145 class SourceSlotConfig(lsst.pex.config.Config):
146  """!
147  Slot configuration which assigns a particular named plugin to each of a set of
148  slots. Each slot allows a type of measurement to be fetched from the SourceTable
149  without knowing which algorithm was used to produced the data.
150 
151  NOTE: the default algorithm for each slot must be registered, even if the default is not used.
152  """
153 
154  centroid = lsst.pex.config.Field(dtype=str, default="base_SdssCentroid", optional=True,
155  doc="the name of the centroiding algorithm used to set source x,y")
156  shape = lsst.pex.config.Field(dtype=str, default="base_SdssShape", optional=True,
157  doc="the name of the algorithm used to set source moments parameters")
158  apFlux = lsst.pex.config.Field(dtype=str, default="base_CircularApertureFlux_3_0", optional=True,
159  doc="the name of the algorithm used to set the source aperture flux slot")
160  modelFlux = lsst.pex.config.Field(dtype=str, default="base_GaussianFlux", optional=True,
161  doc="the name of the algorithm used to set the source model flux slot")
162  psfFlux = lsst.pex.config.Field(dtype=str, default="base_PsfFlux", optional=True,
163  doc="the name of the algorithm used to set the source psf flux slot")
164  instFlux = lsst.pex.config.Field(dtype=str, default="base_GaussianFlux", optional=True,
165  doc="the name of the algorithm used to set the source inst flux slot")
166  calibFlux = lsst.pex.config.Field(dtype=str, default="base_CircularApertureFlux_12_0", optional=True,
167  doc="the name of the flux measurement algorithm used for calibration")
168 
169  def setupSchema(self, schema):
170  """Convenience method to setup a Schema's slots according to the config definition.
171 
172  This is defined in the Config class to support use in unit tests without needing
173  to construct a Task object.
174  """
175  aliases = schema.getAliasMap()
176  if self.centroid is not None: aliases.set("slot_Centroid", self.centroid)
177  if self.shape is not None: aliases.set("slot_Shape", self.shape)
178  if self.apFlux is not None: aliases.set("slot_ApFlux", self.apFlux)
179  if self.modelFlux is not None: aliases.set("slot_ModelFlux", self.modelFlux)
180  if self.psfFlux is not None: aliases.set("slot_PsfFlux", self.psfFlux)
181  if self.instFlux is not None: aliases.set("slot_InstFlux", self.instFlux)
182  if self.calibFlux is not None: aliases.set("slot_CalibFlux", self.calibFlux)
183 
184 class BaseMeasurementConfig(lsst.pex.config.Config):
185  """!
186  Base config class for all measurement driver tasks.
187  """
188 
189  slots = lsst.pex.config.ConfigField(
190  dtype = SourceSlotConfig,
191  doc="Mapping from algorithms to special aliases in Source."
192  )
193 
194  doReplaceWithNoise = lsst.pex.config.Field(dtype=bool, default=True, optional=False,
195  doc='When measuring, replace other detected footprints with noise?')
196 
197  noiseReplacer = lsst.pex.config.ConfigField(
198  dtype=NoiseReplacerConfig,
199  doc="configuration that sets how to replace neighboring sources with noise"
200  )
201 
202  doApplyApCorr = lsst.pex.config.ChoiceField(
203  dtype = str,
204  doc = "Apply aperture corrections? Silently ignored if endOrder <= lsst.meas.base.APCORR_ORDER"
205  " when calling run",
206  default = "noButWarn",
207  allowed = {
208  "yes": "apply aperture corrections; fail if data not available",
209  "yesOrWarn": "apply aperture corrections if data available, else warn",
210  "noButWarn": "do not apply aperture corrections, but warn if data available"
211  " (since aperture corrections could have been applied)",
212  "no": "do not apply aperture corrections",
213  },
214  )
215 
216  applyApCorr = lsst.pex.config.ConfigurableField(
217  target = ApplyApCorrTask,
218  doc = "subtask to apply aperture corrections",
219  )
220 
221  def validate(self):
222  lsst.pex.config.Config.validate(self)
223  if self.slots.centroid is not None and self.slots.centroid not in self.plugins.names:
224  raise ValueError("source centroid slot algorithm is not being run.")
225  if self.slots.shape is not None and self.slots.shape not in self.plugins.names:
226  raise ValueError("source shape slot algorithm '%s' is not being run." % self.slots.shape)
227  for slot in (self.slots.psfFlux, self.slots.apFlux, self.slots.modelFlux,
228  self.slots.instFlux, self.slots.calibFlux):
229  if slot is not None:
230  for name in self.plugins.names:
231  if len(name) <= len(slot) and name == slot[:len(name)]:
232  break
233  else:
234  raise ValueError("source flux slot algorithm '%s' is not being run." % slot)
235 
236 ## @addtogroup LSST_task_documentation
237 ## @{
238 ## @page baseMeasurementTask
239 ## BaseMeasurementTask @copybrief BaseMeasurementTask
240 ## @}
241 
242 class BaseMeasurementTask(lsst.pipe.base.Task):
243  """!
244  Ultimate base class for all measurement tasks.
245 
246  This base class for SingleFrameMeasurementTask and ForcedMeasurementTask mostly exists to share
247  code between the two, and generally should not be used directly.
248 
249  @note Tasks that use this task should usually set the default value of config parameter doApplyApCorr
250  to "yes" or "no", depending if aperture corrections are wanted. The default value of "noButWarn"
251  is intended to alert users who forget, and is appropriate for unit tests and temporary scripts
252  that do not need aperture corrections.
253  """
254 
255  ConfigClass = BaseMeasurementConfig
256  _DefaultName = "measurement"
257 
258  def __init__(self, algMetadata=None, **kwds):
259  """!
260  Constructor; only called by derived classes.
261 
262  @param[in] algMetadata An lsst.daf.base.PropertyList that will be filled with metadata
263  about the plugins being run. If None, an empty PropertyList will
264  be created.
265  @param[in] **kwds Additional arguments passed to lsst.pipe.base.Task.__init__.
266 
267  This attaches two public attributes to the class for use by derived classes and parent tasks:
268  - plugins: an empty PluginMap, which will eventually contain all active plugins that will by
269  invoked by the run() method (to be filled by subclasses). This should be considered read-only.
270  - algMetadata: a lsst.daf.base.PropertyList that will contain additional information about the
271  active plugins to be saved with the output catalog (to be filled by subclasses).
272  """
273  super(BaseMeasurementTask, self).__init__(**kwds)
274  self.plugins = PluginMap()
275  if algMetadata is None:
276  algMetadata = lsst.daf.base.PropertyList()
277  self.algMetadata = algMetadata
278 
279  def initializePlugins(self, **kwds):
280  """Initialize the plugins (and slots) according to the configuration.
281 
282  Derived class constructors should call this method to fill the self.plugins
283  attribute and add correspond output fields and slot aliases to the output schema.
284 
285  In addition to the attributes added by BaseMeasurementTask.__init__, a self.schema
286  attribute holding the output schema must also be present before this method is called, .
287 
288  Keyword arguments are forwarded directly to plugin constructors, allowing derived
289  classes to use plugins with different signatures.
290  """
291  # Make a place at the beginning for the centroid plugin to run first (because it's an OrderedDict,
292  # adding an empty element in advance means it will get run first when it's reassigned to the
293  # actual Plugin).
294  if self.config.slots.centroid != None:
295  self.plugins[self.config.slots.centroid] = None
296  # Init the plugins, sorted by execution order. At the same time add to the schema
297  for executionOrder, name, config, PluginClass in sorted(self.config.plugins.apply()):
298  self.plugins[name] = PluginClass(config, name, metadata=self.algMetadata, **kwds)
299  # In rare circumstances (usually tests), the centroid slot not be coming from an algorithm,
300  # which means we'll have added something we don't want to the plugins map, and we should
301  # remove it.
302  if self.config.slots.centroid is not None and self.plugins[self.config.slots.centroid] is None:
303  del self.plugins[self.config.slots.centroid]
304 
305  def callMeasure(self, measRecord, *args, **kwds):
306  """!
307  Call the measure() method on all plugins, handling exceptions in a consistent way.
308 
309  @param[in,out] measRecord lsst.afw.table.SourceRecord that corresponds to the object being
310  measured, and where outputs should be written.
311  @param[in] *args Positional arguments forwarded to Plugin.measure()
312  @param[in] **kwds Keyword arguments. Two are handled locally:
313  - beginOrder: beginning execution order (inclusive): measurements with
314  executionOrder < beginOrder are not executed. None for no limit.
315  - endOrder: ending execution order (exclusive): measurements with
316  executionOrder >= endOrder are not executed. None for no limit.
317  the rest are forwarded to Plugin.measure()
318 
319  This method can be used with plugins that have different signatures; the only requirement is that
320  'measRecord' be the first argument. Subsequent positional arguments and keyword arguments are
321  forwarded directly to the plugin.
322 
323  This method should be considered "protected"; it is intended for use by derived classes, not users.
324  """
325  beginOrder = kwds.pop("beginOrder", None)
326  endOrder = kwds.pop("endOrder", None)
327  for plugin in self.plugins.iter():
328  if beginOrder is not None and plugin.getExecutionOrder() < beginOrder:
329  continue
330  if endOrder is not None and plugin.getExecutionOrder() >= endOrder:
331  break
332  try:
333  plugin.measure(measRecord, *args, **kwds)
334  except FATAL_EXCEPTIONS:
335  raise
336  except MeasurementError as error:
337  plugin.fail(measRecord, error)
338  except Exception as error:
339  self.log.warn("Error in %s.measure on record %s: %s"
340  % (plugin.name, measRecord.getId(), error))
341  plugin.fail(measRecord)
342 
343  def callMeasureN(self, measCat, *args, **kwds):
344  """!
345  Call the measureN() method on all plugins, handling exceptions in a consistent way.
346 
347  @param[in,out] measCat lsst.afw.table.SourceCatalog containing records for just
348  the source family to be measured, and where outputs should
349  be written.
350  @param[in] beginOrder beginning execution order (inclusive): measurements with
351  executionOrder < beginOrder are not executed. None for no limit.
352  @param[in] endOrder ending execution order (exclusive): measurements with
353  executionOrder >= endOrder are not executed. None for no limit.
354  @param[in] *args Positional arguments forwarded to Plugin.measure()
355  @param[in] **kwds Keyword arguments. Two are handled locally:
356  - beginOrder: beginning execution order (inclusive): measurements with
357  executionOrder < beginOrder are not executed. None for no limit.
358  - endOrder: ending execution order (exclusive): measurements with
359  executionOrder >= endOrder are not executed. None for no limit.
360  the rest are forwarded to Plugin.measure()
361 
362  This method can be used with plugins that have different signatures; the only requirement is that
363  'measRecord' be the first argument. Subsequent positional arguments and keyword arguments are
364  forwarded directly to the plugin.
365 
366  This method should be considered "protected"; it is intended for use by derived classes, not users.
367  """
368  beginOrder = kwds.pop("beginOrder", None)
369  endOrder = kwds.pop("endOrder", None)
370  for plugin in self.plugins.iterN():
371  if beginOrder is not None and plugin.getExecutionOrder() < beginOrder:
372  continue
373  if endOrder is not None and plugin.getExecutionOrder() >= endOrder:
374  break
375  try:
376  plugin.measureN(measCat, *args, **kwds)
377  except FATAL_EXCEPTIONS:
378  raise
379  except MeasurementError as error:
380  for measRecord in measCat:
381  plugin.fail(measRecord, error)
382  except Exception as error:
383  for measRecord in measCat:
384  plugin.fail(measRecord)
385  self.log.warn("Error in %s.measureN on records %s-%s: %s"
386  % (plugin.name, measCat[0].getId(), measCat[-1].getId(), error))
387 
388  def _applyApCorrIfWanted(self, sources, apCorrMap, endOrder):
389  """!Apply aperture corrections to a catalog, if wanted
390 
391  This method is intended to be called at the end of every subclass's run method or other
392  measurement sequence. This is a thin wrapper around self.applyApCorr.run.
393 
394  @param[in,out] sources catalog of sources to which to apply aperture corrections
395  @param[in] apCorrMap aperture correction map (lsst.afw.image.ApCorrMap) or None;
396  typically found in an lsst.afw.image.ExposureInfo
397  if provided then it must contain two entries for each flux field:
398  - flux field (e.g. base_PsfFlux_flux): 2d model
399  - flux sigma field (e.g. base_PsfFlux_fluxSigma): 2d model of error
400  @param[in] endOrder ending execution order, or None; if provided then aperture corrections
401  are only wanted if endOrder > lsst.meas.base.BasePlugin.APCORR_ORDER
402  @return the results from applyApCorr if run, else None
403 
404  @throw lsst.pipe.base.TaskError if aperture corrections are wanted and the exposure does not contain
405  an aperture correction map.
406  """
407  if endOrder is not None and endOrder <= BasePlugin.APCORR_ORDER:
408  # it is not appropriate to apply aperture corrections
409  return
410 
411  if self.config.doApplyApCorr.startswith("yes"):
412  if apCorrMap is not None:
413  self.applyApCorr.run(catalog=sources, apCorrMap=apCorrMap)
414  else:
415  errMsg = "Cannot apply aperture corrections; apCorrMap is None"
416  if self.config.doApplyApCorr == "yesOrWarn":
417  self.log.warn(errMsg)
418  else:
419  raise lsst.pipe.base.TaskError(errMsg)
420  elif self.config.doApplyApCorr == "noButWarn":
421  if apCorrMap is not None:
422  self.log.warn("Aperture corrections are disabled but the data to apply them is available;"
423  " change doApplyApCorr to suppress this warning")
424 
425 
def __init__
Constructor; only called by derived classes.
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
def getTransformClass
Get the measurement transformation appropriate to this plugin.
Base class measurement Plugin config classes.
def __init__
Initialize the measurement object.
def callMeasure
Call the measure() method on all plugins, handling exceptions in a consistent way.
def fail
Record a failure of the measure or measureN() method.
def _applyApCorrIfWanted
Apply aperture corrections to a catalog, if wanted.
Slot configuration which assigns a particular named plugin to each of a set of slots.
Base class for measurement plugins.
Ultimate base class for all measurement tasks.
Base config class for all measurement driver tasks.
def callMeasureN
Call the measureN() method on all plugins, handling exceptions in a consistent way.