LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
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 lsst.pex.config import Field, ListField
33 
34 from .pluginRegistry import PluginRegistry, PluginMap
35 from .baseMeasurement import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
36  BaseMeasurementConfig, BaseMeasurementTask)
37 from .noiseReplacer import NoiseReplacer, DummyNoiseReplacer
38 
39 __all__ = ("SingleFramePluginConfig", "SingleFramePlugin",
40  "SingleFrameMeasurementConfig", "SingleFrameMeasurementTask")
41 
42 
43 class SingleFramePluginConfig(BaseMeasurementPluginConfig):
44  """!
45  Base class for configs of single-frame plugin algorithms.
46  """
47  pass
48 
49 
50 class SingleFramePlugin(BaseMeasurementPlugin):
51  """!
52  Base class for single-frame plugin algorithms.
53 
54  New Plugins can be created in Python by inheriting directly from this class
55  and implementing measure(), fail() (from BasePlugin), and optionally __init__
56  and measureN(). Plugins can also be defined in C++ via the WrappedSingleFramePlugin
57  class.
58  """
59 
60  # All subclasses of SingleFramePlugin should be registered here
61  registry = PluginRegistry(SingleFramePluginConfig)
62  ConfigClass = SingleFramePluginConfig
63 
64  def __init__(self, config, name, schema, metadata):
65  """!
66  Initialize the measurement object.
67 
68  @param[in] config An instance of this class's ConfigClass.
69  @param[in] name The string the plugin was registered with.
70  @param[in,out] schema The Source schema. New fields should be added here to
71  hold measurements produced by this plugin.
72  @param[in] metadata Plugin metadata that will be attached to the output catalog
73  """
74  BaseMeasurementPlugin.__init__(self, config, name)
75 
76  def measure(self, measRecord, exposure):
77  """!
78  Measure the properties of a source on a single image (single-epoch image or coadd).
79 
80  @param[in,out] measRecord lsst.afw.table.SourceRecord to be filled with outputs,
81  and from which previously-measured quantities can be
82  retreived.
83 
84  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
85  be measured and the associated Psf, Wcs, etc. All
86  other sources in the image will have been replaced by
87  noise according to deblender outputs.
88 
89  """
90  raise NotImplementedError()
91 
92  def measureN(self, measCat, exposure):
93  """!
94  Measure the properties of a group of blended sources on a single image
95  (single-epoch image or coadd).
96 
97  @param[in,out] measCat lsst.afw.table.SourceCatalog to be filled with outputs,
98  and from which previously-measured quantities can be
99  retrieved, containing only the sources that should be
100  measured together in this call.
101 
102  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
103  be measured and the associated Psf, Wcs, etc. Sources
104  not in the blended hierarchy to be measured will have
105  been replaced with noise using deblender outputs.
106 
107  Derived classes that do not implement measureN() should just inherit this
108  disabled version. Derived classes that do implement measureN() should additionally
109  add a bool doMeasureN config field to their config class to signal that measureN-mode
110  is available.
111  """
112  raise NotImplementedError()
113 
114 
115 class SingleFrameMeasurementConfig(BaseMeasurementConfig):
116  """!
117  Config class for single frame measurement driver task.
118  """
119 
120  plugins = SingleFramePlugin.registry.makeField(
121  multi=True,
122  default=["base_PixelFlags",
123  "base_SdssCentroid",
124  "base_GaussianCentroid",
125  "base_NaiveCentroid",
126  "base_SdssShape",
127  "base_GaussianFlux",
128  "base_PsfFlux",
129  "base_CircularApertureFlux",
130  "base_SkyCoord",
131  "base_Variance",
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 ## \addtogroup LSST_task_documentation
143 ## \{
144 ## \page SingleFrameMeasurementTask
145 ## \ref SingleFrameMeasurementTask_ "SingleFrameMeasurementTask"
146 ## \copybrief SingleFrameMeasurementTask
147 ## \}
148 
149 
150 class SingleFrameMeasurementTask(BaseMeasurementTask):
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  def __init__(self, schema, algMetadata=None, **kwds):
245  """!
246  Initialize the task. Set up the execution order of the plugins and initialize
247  the plugins, giving each plugin an opportunity to add its measurement fields to
248  the output schema and to record information in the task metadata.
249 
250  @param[in,out] schema lsst.afw.table.Schema, to be initialized to include the
251  measurement fields from the plugins already
252  @param[in,out] algMetadata lsst.daf.base.PropertyList used to record information about
253  each algorithm. An empty PropertyList will be created if None.
254  @param[in] **kwds Keyword arguments forwarded to lsst.pipe.base.Task.__init__
255  """
256  super(SingleFrameMeasurementTask, self).__init__(algMetadata=algMetadata, **kwds)
257  self.schema = schema
258  self.config.slots.setupSchema(self.schema)
259  self.initializePlugins(schema=self.schema)
260 
261  # Check to see if blendedness is one of the plugins
262  if 'base_Blendedness' in self.plugins:
263  self.doBlendedness = True
264  self.blendPlugin = self.plugins['base_Blendedness']
265  else:
266  self.doBlendedness = False
267 
268  def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None):
269  """!
270  Run single frame measurement over an exposure and source catalog
271 
272  @param[in,out] measCat lsst.afw.table.SourceCatalog to be filled with outputs. Must
273  contain all the SourceRecords to be measured (with Footprints
274  attached), and have a schema that is a superset of self.schema.
275 
276  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
277  be measured and the associated Psf, Wcs, etc.
278  @param[in] noiseImage optional lsst.afw.image.ImageF for test which need to control
279  noiseReplacement
280  @param[in] exposureId optional unique exposureId used to calculate random number
281  generator seed in the NoiseReplacer.
282  @param[in] beginOrder beginning execution order (inclusive): measurements with
283  executionOrder < beginOrder are not executed. None for no limit.
284  @param[in] endOrder ending execution order (exclusive): measurements with
285  executionOrder >= endOrder are not executed. None for no limit.
286  """
287  assert measCat.getSchema().contains(self.schema)
288  footprints = {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
289  for measRecord in measCat}
290 
291  # noiseReplacer is used to fill the footprints with noise and save heavy footprints
292  # of the source pixels so that they can be restored one at a time for measurement.
293  # After the NoiseReplacer is constructed, all pixels in the exposure.getMaskedImage()
294  # which belong to objects in measCat will be replaced with noise
295  if self.config.doReplaceWithNoise:
296  noiseReplacer = NoiseReplacer(self.config.noiseReplacer, exposure, footprints,
297  noiseImage=noiseImage, log=self.log, exposureId=exposureId)
298  algMetadata = measCat.getMetadata()
299  if algMetadata is not None:
300  algMetadata.addInt("NOISE_SEED_MULTIPLIER", self.config.noiseReplacer.noiseSeedMultiplier)
301  algMetadata.addString("NOISE_SOURCE", self.config.noiseReplacer.noiseSource)
302  algMetadata.addDouble("NOISE_OFFSET", self.config.noiseReplacer.noiseOffset)
303  if exposureId is not None:
304  algMetadata.addLong("NOISE_EXPOSURE_ID", exposureId)
305  else:
306  noiseReplacer = DummyNoiseReplacer()
307 
308  # First, create a catalog of all parentless sources
309  # Loop through all the parent sources, first processing the children, then the parent
310  measParentCat = measCat.getChildren(0)
311 
312  self.log.info("Measuring %d sources (%d parents, %d children) "
313  % (len(measCat), len(measParentCat), len(measCat) - len(measParentCat)))
314 
315  for parentIdx, measParentRecord in enumerate(measParentCat):
316  # first get all the children of this parent, insert footprint in turn, and measure
317  measChildCat = measCat.getChildren(measParentRecord.getId())
318  # TODO: skip this loop if there are no plugins configured for single-object mode
319  for measChildRecord in measChildCat:
320  noiseReplacer.insertSource(measChildRecord.getId())
321  self.callMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
322 
323  if self.doBlendedness:
324  self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
325 
326  noiseReplacer.removeSource(measChildRecord.getId())
327 
328  # Then insert the parent footprint, and measure that
329  noiseReplacer.insertSource(measParentRecord.getId())
330  self.callMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
331 
332  if self.doBlendedness:
333  self.blendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
334 
335  # Finally, process both the parent and the child set through measureN
336  self.callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
337  beginOrder=beginOrder, endOrder=endOrder)
338  self.callMeasureN(measChildCat, exposure, beginOrder=beginOrder, endOrder=endOrder)
339  noiseReplacer.removeSource(measParentRecord.getId())
340  # when done, restore the exposure to its original state
341  noiseReplacer.end()
342 
343  # Undeblended plugins only fire if we're running everything
344  if endOrder is None:
345  for source in measCat:
346  for plugin in self.undeblendedPlugins.iter():
347  self.doMeasurement(plugin, source, exposure)
348 
349  # Now we loop over all of the sources one more time to compute the blendedness metrics
350  if self.doBlendedness:
351  for source in measCat:
352  self.blendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
353 
354  def measure(self, measCat, exposure):
355  """!
356  Backwards-compatibility alias for run()
357  """
358  self.run(measCat, exposure)
def run
Run single frame measurement over an exposure and source catalog.
Definition: sfm.py:268
def __init__
Initialize the task.
Definition: sfm.py:244
A subtask for measuring the properties of sources on a single exposure.
Definition: sfm.py:150
Base class for configs of single-frame plugin algorithms.
Definition: sfm.py:43
Base class for single-frame plugin algorithms.
Definition: sfm.py:50
Config class for single frame measurement driver task.
Definition: sfm.py:115
def __init__
Initialize the measurement object.
Definition: sfm.py:64
def measureN
Measure the properties of a group of blended sources on a single image (single-epoch image or coadd)...
Definition: sfm.py:92
def measure
Backwards-compatibility alias for run()
Definition: sfm.py:354
def measure
Measure the properties of a source on a single image (single-epoch image or coadd).
Definition: sfm.py:76