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