lsst.meas.base  13.0-26-g0f127ff+5
sfm.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 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 classes for single-frame measurement plugins and the driver task for these.
24 
25 In single-frame measurement, we assume that detection and probably deblending have already been run on
26 the same frame, so a SourceCatalog has already been created with Footprints (which may be HeavyFootprints).
27 Measurements are generally recorded in the coordinate system of the image being measured (and all
28 slot-eligible fields must be), but non-slot fields may be recorded in other coordinate systems if necessary
29 to avoid information loss (this should, of course, be indicated in the field documentation).
30 """
31 
32 from .pluginRegistry import PluginRegistry
33 from .baseMeasurement import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
34  BaseMeasurementConfig, BaseMeasurementTask)
35 from .noiseReplacer import NoiseReplacer, DummyNoiseReplacer
36 
37 __all__ = ("SingleFramePluginConfig", "SingleFramePlugin",
38  "SingleFrameMeasurementConfig", "SingleFrameMeasurementTask")
39 
40 
42  """!
43  Base class for configs of single-frame plugin algorithms.
44  """
45  pass
46 
47 
49  """!
50  Base class for single-frame plugin algorithms.
51 
52  New Plugins can be created in Python by inheriting directly from this class
53  and implementing measure(), fail() (from BasePlugin), and optionally __init__
54  and measureN(). Plugins can also be defined in C++ via the WrappedSingleFramePlugin
55  class.
56  """
57 
58  # All subclasses of SingleFramePlugin should be registered here
59  registry = PluginRegistry(SingleFramePluginConfig)
60  ConfigClass = SingleFramePluginConfig
61 
62  def __init__(self, config, name, schema, metadata, logName=None, **kwds):
63  """!
64  Initialize the measurement object.
65 
66  @param[in] config An instance of this class's ConfigClass.
67  @param[in] name The string the plugin was registered with.
68  @param[in,out] schema The Source schema. New fields should be added here to
69  hold measurements produced by this plugin.
70  @param[in] metadata Plugin metadata that will be attached to the output catalog
71  @param[in] logName May include logName to use for this plugin
72  """
73  BaseMeasurementPlugin.__init__(self, config, name, logName=logName)
74 
75  def measure(self, measRecord, exposure):
76  """!
77  Measure the properties of a source on a single image (single-epoch image or coadd).
78 
79  @param[in,out] measRecord lsst.afw.table.SourceRecord to be filled with outputs,
80  and from which previously-measured quantities can be
81  retreived.
82 
83  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
84  be measured and the associated Psf, Wcs, etc. All
85  other sources in the image will have been replaced by
86  noise according to deblender outputs.
87 
88  """
89  raise NotImplementedError()
90 
91  def measureN(self, measCat, exposure):
92  """!
93  Measure the properties of a group of blended sources on a single image
94  (single-epoch image or coadd).
95 
96  @param[in,out] measCat lsst.afw.table.SourceCatalog to be filled with outputs,
97  and from which previously-measured quantities can be
98  retrieved, containing only the sources that should be
99  measured together in this call.
100 
101  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
102  be measured and the associated Psf, Wcs, etc. Sources
103  not in the blended hierarchy to be measured will have
104  been replaced with noise using deblender outputs.
105 
106  Derived classes that do not implement measureN() should just inherit this
107  disabled version. Derived classes that do implement measureN() should additionally
108  add a bool doMeasureN config field to their config class to signal that measureN-mode
109  is available.
110  """
111  raise NotImplementedError()
112 
113 
115  """!
116  Config class for single frame measurement driver task.
117  """
118 
119  plugins = SingleFramePlugin.registry.makeField(
120  multi=True,
121  default=["base_PixelFlags",
122  "base_SdssCentroid",
123  "base_GaussianCentroid",
124  "base_NaiveCentroid",
125  "base_SdssShape",
126  "base_GaussianFlux",
127  "base_PsfFlux",
128  "base_CircularApertureFlux",
129  "base_SkyCoord",
130  "base_Variance",
131  "base_Blendedness",
132  ],
133  doc="Plugins to be run and their configuration"
134  )
135  algorithms = property(lambda self: self.plugins, doc="backwards-compatibility alias for plugins")
136  undeblended = SingleFramePlugin.registry.makeField(
137  multi=True,
138  default=[],
139  doc="Plugins to run on undeblended image"
140  )
141 
142 
148 
149 
151  """!
152  \anchor SingleFrameMeasurementTask_
153 
154  \brief A subtask for measuring the properties of sources on a single exposure.
155 
156  The task is configured with a list of "plugins": each plugin defines the values it
157  measures (i.e. the columns in a table it will fill) and conducts that measurement
158  on each detected source (see SingleFramePlugin). The job of the
159  measurement task is to initialize the set of plugins (which includes setting up the
160  catalog schema) from their configuration, and then invoke each plugin on each
161  source.
162 
163  When run after the deblender (see lsst.meas.deblender.SourceDeblendTask),
164  SingleFrameMeasurementTask also replaces each source's neighbors with noise before
165  measuring each source, utilizing the HeavyFootprints created by the deblender (see
166  NoiseReplacer).
167 
168  SingleFrameMeasurementTask has only two methods: __init__() and run(). For configuration
169  options, see SingleFrameMeasurementConfig.
170 
171  @section meas_base_sfm_Example A complete example of using SingleFrameMeasurementTask
172 
173  The code below is in examples/runSingleFrameTask.py
174 
175  @dontinclude runSingleFrameTask.py
176 
177  See meas_algorithms_detection_Example for more information on SourceDetectionTask.
178 
179  First, import the required tasks (there are some other standard imports;
180  read the file if you're confused):
181 
182  @skip SourceDetectionTask
183  @until SingleFrameMeasurementTask
184 
185  We need to create our tasks before processing any data as the task constructors
186  can add extra columns to the schema. The most important argument we pass these to these
187  is an lsst.afw.table.Schema object, which contains information about the fields (i.e. columns) of the
188  measurement catalog we'll create, including names, types, and additional documentation.
189  Tasks that operate on a catalog are typically passed a Schema upon construction, to which
190  they add the fields they'll fill later when run. We construct a mostly empty Schema that
191  contains just the fields required for a SourceCatalog like this:
192 
193  @skipline schema
194 
195  Now we can configure and create the SourceDetectionTask:
196 
197  @until detectionTask
198 
199  We then move on to configuring the measurement task:
200 
201  @until config
202 
203  While a reasonable set of plugins is configured by default, we'll customize the list.
204  We also need to unset one of the slots at the same time, because we're
205  not running the algorithm that it's set to by default, and that would cause problems later:
206 
207  @until psfFlux
208 
209  Now, finally, we can construct the measurement task:
210 
211  @skipline measureTask
212 
213  After constructing all the tasks, we can inspect the Schema we've created:
214 
215  @skipline print schema
216 
217  All of the fields in the
218  schema can be accessed via the get() method on a record object. See afwTable for more
219  information.
220 
221  We're now ready to process the data (we could loop over multiple exposures/catalogs using the same
222  task objects). First create the output table and process the image to find sources:
223 
224  @skipline afwTable
225  @skip result
226  @until sources
227 
228  Then measure them:
229 
230  @skipline measure
231 
232  We then might plot the results (@em e.g. if you set `--ds9` on the command line)
233 
234  @skip display
235  @until RED
236 
237  and end up with something like
238 
239  @image html runSingleFrameTask-ds9.png
240  """
241 
242  ConfigClass = SingleFrameMeasurementConfig
243 
244  NOISE_SEED_MULTIPLIER = "NOISE_SEED_MULTIPLIER"
245  NOISE_SOURCE = "NOISE_SOURCE"
246  NOISE_OFFSET = "NOISE_OFFSET"
247  NOISE_EXPOSURE_ID = "NOISE_EXPOSURE_ID"
248 
249  def __init__(self, schema, algMetadata=None, **kwds):
250  """!
251  Initialize the task. Set up the execution order of the plugins and initialize
252  the plugins, giving each plugin an opportunity to add its measurement fields to
253  the output schema and to record information in the task metadata.
254 
255  @param[in,out] schema lsst.afw.table.Schema, to be initialized to include the
256  measurement fields from the plugins already
257  @param[in,out] algMetadata lsst.daf.base.PropertyList used to record information about
258  each algorithm. An empty PropertyList will be created if None.
259  @param[in] **kwds Keyword arguments forwarded to lsst.pipe.base.Task.__init__
260  """
261 
262  super(SingleFrameMeasurementTask, self).__init__(algMetadata=algMetadata, **kwds)
263  self.schema = schema
264  self.config.slots.setupSchema(self.schema)
265  self.initializePlugins(schema=self.schema)
266 
267  # Check to see if blendedness is one of the plugins
268  if 'base_Blendedness' in self.plugins:
269  self.doBlendedness = True
270  self.blendPlugin = self.plugins['base_Blendedness']
271  else:
272  self.doBlendedness = False
273 
274  def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None):
275  """!
276  Run single frame measurement over an exposure and source catalog
277 
278  @param[in,out] measCat lsst.afw.table.SourceCatalog to be filled with outputs. Must
279  contain all the SourceRecords to be measured (with Footprints
280  attached), and have a schema that is a superset of self.schema.
281 
282  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
283  be measured and the associated Psf, Wcs, etc.
284  @param[in] noiseImage optional lsst.afw.image.ImageF for test which need to control
285  noiseReplacement
286  @param[in] exposureId optional unique exposureId used to calculate random number
287  generator seed in the NoiseReplacer.
288  @param[in] beginOrder beginning execution order (inclusive): measurements with
289  executionOrder < beginOrder are not executed. None for no limit.
290  @param[in] endOrder ending execution order (exclusive): measurements with
291  executionOrder >= endOrder are not executed. None for no limit.
292  """
293  assert measCat.getSchema().contains(self.schema)
294  footprints = {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
295  for measRecord in measCat}
296 
297  # noiseReplacer is used to fill the footprints with noise and save heavy footprints
298  # of the source pixels so that they can be restored one at a time for measurement.
299  # After the NoiseReplacer is constructed, all pixels in the exposure.getMaskedImage()
300  # which belong to objects in measCat will be replaced with noise
301  if self.config.doReplaceWithNoise:
302  noiseReplacer = NoiseReplacer(self.config.noiseReplacer, exposure, footprints,
303  noiseImage=noiseImage, log=self.log, exposureId=exposureId)
304  algMetadata = measCat.getMetadata()
305  if algMetadata is not None:
306  algMetadata.addInt(self.NOISE_SEED_MULTIPLIER, self.config.noiseReplacer.noiseSeedMultiplier)
307  algMetadata.addString(self.NOISE_SOURCE, self.config.noiseReplacer.noiseSource)
308  algMetadata.addDouble(self.NOISE_OFFSET, self.config.noiseReplacer.noiseOffset)
309  if exposureId is not None:
310  algMetadata.addLong(self.NOISE_EXPOSURE_ID, exposureId)
311  else:
312  noiseReplacer = DummyNoiseReplacer()
313 
314  self.runPlugins(noiseReplacer, measCat, exposure, beginOrder, endOrder)
315 
316  def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None):
317  """Function which calls the defined measument plugins on an exposure
318 
319  Parameters
320  ----------
321  noiseReplacer : lsst.meas.base.NoiseReplacer
322  noiseReplacer to fill sources not being measured with noise.
323 
324  measCat : lsst.afw.table.SourceCatalog
325  SourceCatalog to be filled with outputs. Must contain all the SourceRecords to be measured (with
326  Footprints attached), and have a schema that is a superset of self.schema.
327 
328  exposure : lsst.afw.image.ExposureF
329  Exposure contaning the pixel data to be measured and the associated PSF, WCS, etc.
330 
331  beginOrder : float
332  beginning execution order (inclusive): measurements with executionOrder < beginOrder are not
333  executed. None for no limit.
334 
335  endOrder : float
336  ending execution order (exclusive): measurements with executionOrder >= endOrder are not
337  executed. None for no limit.
338  """
339  # First, create a catalog of all parentless sources
340  # Loop through all the parent sources, first processing the children, then the parent
341  measParentCat = measCat.getChildren(0)
342 
343  self.log.info("Measuring %d sources (%d parents, %d children) "
344  % (len(measCat), len(measParentCat), len(measCat) - len(measParentCat)))
345 
346  for parentIdx, measParentRecord in enumerate(measParentCat):
347  # first get all the children of this parent, insert footprint in turn, and measure
348  measChildCat = measCat.getChildren(measParentRecord.getId())
349  # TODO: skip this loop if there are no plugins configured for single-object mode
350  for measChildRecord in measChildCat:
351  noiseReplacer.insertSource(measChildRecord.getId())
352  self.callMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
353 
354  if self.doBlendedness:
355  self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
356 
357  noiseReplacer.removeSource(measChildRecord.getId())
358 
359  # Then insert the parent footprint, and measure that
360  noiseReplacer.insertSource(measParentRecord.getId())
361  self.callMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
362 
363  if self.doBlendedness:
364  self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
365 
366  # Finally, process both the parent and the child set through measureN
367  self.callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
368  beginOrder=beginOrder, endOrder=endOrder)
369  self.callMeasureN(measChildCat, exposure, beginOrder=beginOrder, endOrder=endOrder)
370  noiseReplacer.removeSource(measParentRecord.getId())
371  # when done, restore the exposure to its original state
372  noiseReplacer.end()
373 
374  # Undeblended plugins only fire if we're running everything
375  if endOrder is None:
376  for source in measCat:
377  for plugin in self.undeblendedPlugins.iter():
378  self.doMeasurement(plugin, source, exposure)
379 
380  # Now we loop over all of the sources one more time to compute the blendedness metrics
381  if self.doBlendedness:
382  for source in measCat:
383  self.blendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
384 
385  def measure(self, measCat, exposure):
386  """!
387  Backwards-compatibility alias for run()
388  """
389  self.run(measCat, exposure)
Base config class for all measurement plugins.
def callMeasure(self, measRecord, args, kwds)
Call the measure() method on all plugins, handling exceptions in a consistent way.
def measure(self, measRecord, exposure)
Measure the properties of a source on a single image (single-epoch image or coadd).
Definition: sfm.py:75
A subtask for measuring the properties of sources on a single exposure.
Definition: sfm.py:150
def measureN(self, measCat, exposure)
Measure the properties of a group of blended sources on a single image (single-epoch image or coadd)...
Definition: sfm.py:91
def measure(self, measCat, exposure)
Backwards-compatibility alias for run()
Definition: sfm.py:385
def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None)
Definition: sfm.py:316
def callMeasureN(self, measCat, args, kwds)
Call the measureN() method on all plugins, handling exceptions in a consistent way.
Base class for configs of single-frame plugin algorithms.
Definition: sfm.py:41
int iter
Base class for single-frame plugin algorithms.
Definition: sfm.py:48
def __init__(self, config, name, schema, metadata, logName=None, kwds)
Initialize the measurement object.
Definition: sfm.py:62
def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None)
Run single frame measurement over an exposure and source catalog.
Definition: sfm.py:274
Config class for single frame measurement driver task.
Definition: sfm.py:114
Base class for plugin registries.
A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer.
def __init__(self, schema, algMetadata=None, kwds)
Initialize the task.
Definition: sfm.py:249
Class that handles replacing sources with noise during measurement.
Ultimate base class for all measurement tasks.
Base config class for all measurement driver tasks.
def doMeasurement(self, plugin, measRecord, args, kwds)
Call the measure() method on the nominated plugin, handling exceptions in a consistent way...