LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
sfm.py
Go to the documentation of this file.
1 # This file is part of meas_base.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
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 GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 
22 r"""Base classes for single-frame measurement plugins and the associated task.
23 
24 In single-frame measurement, we assume that detection and probably deblending
25 have already been run on the same frame, so a `~lsst.afw.table.SourceCatalog`
26 has already been created with `lsst.afw.detection.Footprint`\ s (which may be
27 "heavy" — that is, include pixel data). Measurements are generally recorded in
28 the coordinate system of the image being measured (and all slot-eligible
29 fields must be), but non-slot fields may be recorded in other coordinate
30 systems if necessary to avoid information loss (this should, of course, be
31 indicated in the field documentation).
32 """
33 
34 import lsst.pipe.base as pipeBase
35 import lsst.pex.config
36 import time
37 
38 from .pluginRegistry import PluginRegistry
39 from .baseMeasurement import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
40  BaseMeasurementConfig, BaseMeasurementTask)
41 from .noiseReplacer import NoiseReplacer, DummyNoiseReplacer
42 
43 __all__ = ("SingleFramePluginConfig", "SingleFramePlugin",
44  "SingleFrameMeasurementConfig", "SingleFrameMeasurementTask")
45 
46 
48  """Base class for single-frame plugin configuration classes.
49  """
50  pass
51 
52 
54  """Base class for single-frame measurement plugin.
55 
56  Parameters
57  ----------
58  config : `SingleFramePlugin.ConfigClass`
59  Configuration for this plugin.
60  name : `str`
61  The string with which the plugin was registered.
62  schema : `lsst.afw.table.Schema`
63  The schema for the source table . New fields are added here to
64  hold measurements produced by this plugin.
65  metadata : `lsst.daf.base.PropertySet`
66  Plugin metadata that will be attached to the output catalog
67  logName : `str`, optional
68  Name to use when logging errors.
69 
70  Notes
71  -----
72  New plugins can be created in Python by inheriting directly from this
73  class and implementing the `measure`, `fail` (from `BasePlugin`), and
74  optionally `__init__` and `measureN` methods. Plugins can also be defined
75  in C++ via the `WrappedSingleFramePlugin` class.
76  """
77 
78  registry = PluginRegistry(SingleFramePluginConfig)
79  """Registry of subclasses of `SingleFramePlugin` (`PluginRegistry`).
80  """
81 
82  ConfigClass = SingleFramePluginConfig
83 
84  def __init__(self, config, name, schema, metadata, logName=None, **kwds):
85  BaseMeasurementPlugin.__init__(self, config, name, logName=logName)
86 
87  def measure(self, measRecord, exposure):
88  """Measure the properties of a source on a single image.
89 
90  The image may be from a single epoch, or it may be a coadd.
91 
92  Parameters
93  ----------
94  measRecord : `lsst.afw.table.SourceRecord`
95  Record describing the object being measured. Previously-measured
96  quantities may be retrieved from here, and it will be updated
97  in-place tih the outputs of this plugin.
98  exposure : `lsst.afw.image.ExposureF`
99  The pixel data to be measured, together with the associated PSF,
100  WCS, etc. All other sources in the image should have been replaced
101  by noise according to deblender outputs.
102  """
103  raise NotImplementedError()
104 
105  def measureN(self, measCat, exposure):
106  """Measure the properties of blended sources on a single image.
107 
108  This operates on all members of a blend family at once. The image may
109  be from a single epoch, or it may be a coadd.
110 
111  Parameters
112  ----------
113  measCat : `lsst.afw.table.SourceCatalog`
114  Catalog describing the objects (and only those objects) being
115  measured. Previously-measured quantities will be retrieved from
116  here, and it will be updated in-place with the outputs of this
117  plugin.
118  exposure : `lsst.afw.image.ExposureF`
119  The pixel data to be measured, together with the associated PSF,
120  WCS, etc. All other sources in the image should have been replaced
121  by noise according to deblender outputs.
122 
123  Notes
124  -----
125  Derived classes that do not implement ``measureN`` should just inherit
126  this disabled version. Derived classes that do implement ``measureN``
127  should additionally add a bool doMeasureN config field to their config
128  class to signal that measureN-mode is available.
129  """
130  raise NotImplementedError()
131 
132 
134  """Config class for single frame measurement driver task.
135  """
136 
137  plugins = SingleFramePlugin.registry.makeField(
138  multi=True,
139  default=["base_PixelFlags",
140  "base_SdssCentroid",
141  "base_NaiveCentroid",
142  "base_SdssShape",
143  "base_GaussianFlux",
144  "base_PsfFlux",
145  "base_CircularApertureFlux",
146  "base_SkyCoord",
147  "base_Variance",
148  "base_Blendedness",
149  "base_LocalBackground",
150  ],
151  doc="Plugins to be run and their configuration"
152  )
153  algorithms = property(lambda self: self.pluginsplugins, doc="backwards-compatibility alias for plugins")
154  undeblended = SingleFramePlugin.registry.makeField(
155  multi=True,
156  default=[],
157  doc="Plugins to run on undeblended image"
158  )
159  loggingInterval = lsst.pex.config.Field(
160  dtype=int,
161  default=600,
162  doc="Interval (in seconds) to log messages (at VERBOSE level) while running measurement plugins."
163  )
164 
165 
167  """A subtask for measuring the properties of sources on a single exposure.
168 
169  Parameters
170  ----------
171  schema : `lsst.afw.table.Schema`
172  Schema of the output resultant catalog. Will be updated to provide
173  fields to accept the outputs of plugins which will be executed by this
174  task.
175  algMetadata : `lsst.daf.base.PropertyList`, optional
176  Used to record metadaa about algorithm execution. An empty
177  `lsst.daf.base.PropertyList` will be created if `None`.
178  **kwds
179  Keyword arguments forwarded to `BaseMeasurementTask`.
180  """
181 
182  ConfigClass = SingleFrameMeasurementConfig
183 
184  NOISE_SEED_MULTIPLIER = "NOISE_SEED_MULTIPLIER"
185  """Name by which the noise seed multiplier is recorded in metadata ('str').
186  """
187 
188  NOISE_SOURCE = "NOISE_SOURCE"
189  """Name by which the noise source is recorded in metadata ('str').
190  """
191 
192  NOISE_OFFSET = "NOISE_OFFSET"
193  """Name by which the noise offset is recorded in metadata ('str').
194  """
195 
196  NOISE_EXPOSURE_ID = "NOISE_EXPOSURE_ID"
197  """Name by which the noise exposire ID is recorded in metadata ('str').
198  """
199 
200  def __init__(self, schema, algMetadata=None, **kwds):
201  super(SingleFrameMeasurementTask, self).__init__(algMetadata=algMetadata, **kwds)
202  self.schemaschema = schema
203  self.config.slots.setupSchema(self.schemaschema)
204  self.initializePluginsinitializePlugins(schema=self.schemaschema)
205 
206  # Check to see if blendedness is one of the plugins
207  if 'base_Blendedness' in self.pluginsplugins:
208  self.doBlendednessdoBlendedness = True
209  self.blendPluginblendPlugin = self.pluginsplugins['base_Blendedness']
210  else:
211  self.doBlendednessdoBlendedness = False
212 
213  @pipeBase.timeMethod
214  def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None):
215  r"""Run single frame measurement over an exposure and source catalog.
216 
217  Parameters
218  ----------
219  measCat : `lsst.afw.table.SourceCatalog`
220  Catalog to be filled with the results of measurement. Must contain
221  all the `lsst.afw.table.SourceRecord`\ s to be measured (with
222  `lsst.afw.detection.Footprint`\ s attached), and have a schema
223  that is a superset of ``self.schema``.
224  exposure : `lsst.afw.image.ExposureF`
225  Image containing the pixel data to be measured together with
226  associated PSF, WCS, etc.
227  noiseImage : `lsst.afw.image.ImageF`, optional
228  Can be used to specify the a predictable noise replacement field
229  for testing purposes.
230  exposureId : `int`, optional
231  Unique exposure identifier used to calculate the random number
232  generator seed during noise replacement.
233  beginOrder : `float`, optional
234  Start execution order (inclusive): measurements with
235  ``executionOrder < beginOrder`` are not executed. `None` for no
236  limit.
237  endOrder : `float`, optional
238  Final execution order (exclusive): measurements with
239  ``executionOrder >= endOrder`` are not executed. `None` for no
240  limit.
241  """
242  assert measCat.getSchema().contains(self.schemaschema)
243  footprints = {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
244  for measRecord in measCat}
245 
246  # noiseReplacer is used to fill the footprints with noise and save
247  # heavy footprints of the source pixels so that they can be restored
248  # one at a time for measurement. After the NoiseReplacer is
249  # constructed, all pixels in the exposure.getMaskedImage() which
250  # belong to objects in measCat will be replaced with noise
251 
252  if self.config.doReplaceWithNoise:
253  noiseReplacer = NoiseReplacer(self.config.noiseReplacer, exposure, footprints,
254  noiseImage=noiseImage, log=self.log, exposureId=exposureId)
255  algMetadata = measCat.getMetadata()
256  if algMetadata is not None:
257  algMetadata.addInt(self.NOISE_SEED_MULTIPLIERNOISE_SEED_MULTIPLIER, self.config.noiseReplacer.noiseSeedMultiplier)
258  algMetadata.addString(self.NOISE_SOURCENOISE_SOURCE, self.config.noiseReplacer.noiseSource)
259  algMetadata.addDouble(self.NOISE_OFFSETNOISE_OFFSET, self.config.noiseReplacer.noiseOffset)
260  if exposureId is not None:
261  algMetadata.addLong(self.NOISE_EXPOSURE_IDNOISE_EXPOSURE_ID, exposureId)
262  else:
263  noiseReplacer = DummyNoiseReplacer()
264 
265  self.runPluginsrunPlugins(noiseReplacer, measCat, exposure, beginOrder, endOrder)
266 
267  def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None):
268  r"""Call the configured measument plugins on an image.
269 
270  Parameters
271  ----------
272  noiseReplacer : `NoiseReplacer`
273  Used to fill sources not being measured with noise.
274  measCat : `lsst.afw.table.SourceCatalog`
275  Catalog to be filled with the results of measurement. Must contain
276  all the `lsst.afw.table.SourceRecord`\ s to be measured (with
277  `lsst.afw.detection.Footprint`\ s attached), and have a schema
278  that is a superset of ``self.schema``.
279  exposure : `lsst.afw.image.ExposureF`
280  Image containing the pixel data to be measured together with
281  associated PSF, WCS, etc.
282  beginOrder : `float`, optional
283  Start execution order (inclusive): measurements with
284  ``executionOrder < beginOrder`` are not executed. `None` for no
285  limit.
286  endOrder : `float`, optional
287  Final execution order (exclusive): measurements with
288  ``executionOrder >= endOrder`` are not executed. `None` for no
289  limit.
290  """
291  # First, create a catalog of all parentless sources. Loop through all
292  # the parent sources, first processing the children, then the parent.
293  measParentCat = measCat.getChildren(0)
294 
295  nMeasCat = len(measCat)
296  nMeasParentCat = len(measParentCat)
297  self.log.info("Measuring %d source%s (%d parent%s, %d child%s) ",
298  nMeasCat, ("" if nMeasCat == 1 else "s"),
299  nMeasParentCat, ("" if nMeasParentCat == 1 else "s"),
300  nMeasCat - nMeasParentCat, ("" if nMeasCat - nMeasParentCat == 1 else "ren"))
301  nextLogTime = time.time() + self.config.loggingInterval
302 
303  childrenIter = measCat.getChildren([measParentRecord.getId() for measParentRecord in measParentCat])
304  for parentIdx, (measParentRecord, measChildCat) in enumerate(zip(measParentCat, childrenIter)):
305  # first get all the children of this parent, insert footprint in
306  # turn, and measure
307  # TODO: skip this loop if there are no plugins configured for
308  # single-object mode
309  for measChildRecord in measChildCat:
310  noiseReplacer.insertSource(measChildRecord.getId())
311  self.callMeasurecallMeasure(measChildRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
312 
313  if self.doBlendednessdoBlendedness:
314  self.blendPluginblendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measChildRecord)
315 
316  noiseReplacer.removeSource(measChildRecord.getId())
317 
318  # Then insert the parent footprint, and measure that
319  noiseReplacer.insertSource(measParentRecord.getId())
320  self.callMeasurecallMeasure(measParentRecord, exposure, beginOrder=beginOrder, endOrder=endOrder)
321 
322  if self.doBlendednessdoBlendedness:
323  self.blendPluginblendPlugin.cpp.measureChildPixels(exposure.getMaskedImage(), measParentRecord)
324 
325  # Finally, process both parent and child set through measureN
326  self.callMeasureNcallMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
327  beginOrder=beginOrder, endOrder=endOrder)
328  self.callMeasureNcallMeasureN(measChildCat, exposure, beginOrder=beginOrder, endOrder=endOrder)
329  noiseReplacer.removeSource(measParentRecord.getId())
330  # Log a message if it has been a while since the last log.
331  if (currentTime := time.time()) > nextLogTime:
332  self.log.verbose("Measurement complete for %d parents (and their children) out of %d",
333  parentIdx + 1, nMeasParentCat)
334  nextLogTime = currentTime + self.config.loggingInterval
335 
336  # When done, restore the exposure to its original state
337  noiseReplacer.end()
338 
339  # Undeblended plugins only fire if we're running everything
340  if endOrder is None:
341  for sourceIndex, source in enumerate(measCat):
342  for plugin in self.undeblendedPluginsundeblendedPlugins.iter():
343  self.doMeasurementdoMeasurement(plugin, source, exposure)
344  if (currentTime := time.time()) > nextLogTime:
345  self.log.verbose("Undeblended measurement complete for %d sources out of %d",
346  sourceIndex + 1, nMeasCat)
347  nextLogTime = currentTime + self.config.loggingInterval
348 
349  # Now we loop over all of the sources one more time to compute the
350  # blendedness metrics
351  if self.doBlendednessdoBlendedness:
352  for source in measCat:
353  self.blendPluginblendPlugin.cpp.measureParentPixels(exposure.getMaskedImage(), source)
354 
355  def measure(self, measCat, exposure):
356  """Backwards-compatibility alias for `run`.
357  """
358  self.runrun(measCat, exposure)
def doMeasurement(self, plugin, measRecord, *args, **kwds)
def callMeasure(self, measRecord, *args, **kwds)
def callMeasureN(self, measCat, *args, **kwds)
def measure(self, measCat, exposure)
Definition: sfm.py:355
def __init__(self, schema, algMetadata=None, **kwds)
Definition: sfm.py:200
def runPlugins(self, noiseReplacer, measCat, exposure, beginOrder=None, endOrder=None)
Definition: sfm.py:267
def run(self, measCat, exposure, noiseImage=None, exposureId=None, beginOrder=None, endOrder=None)
Definition: sfm.py:214
def measureN(self, measCat, exposure)
Definition: sfm.py:105
def __init__(self, config, name, schema, metadata, logName=None, **kwds)
Definition: sfm.py:84
def measure(self, measRecord, exposure)
Definition: sfm.py:87