LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
measurement.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2011 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import math
23 import lsst.pex.config as pexConfig
24 import lsst.pex.exceptions as pexExceptions
25 import lsst.afw.table as afwTable
26 import lsst.pipe.base as pipeBase
27 import lsst.afw.display.ds9 as ds9
28 
29 from . import algorithmsLib
30 from .algorithmRegistry import *
31 from .replaceWithNoise import *
32 
33 __all__ = "SourceSlotConfig", "SourceMeasurementConfig", "SourceMeasurementTask"
34 
35 class SourceSlotConfig(pexConfig.Config):
36 
37  centroid = pexConfig.Field(dtype=str, default="centroid.sdss", optional=True,
38  doc="the name of the centroiding algorithm used to set source x,y")
39  shape = pexConfig.Field(dtype=str, default="shape.sdss", optional=True,
40  doc="the name of the algorithm used to set source moments parameters")
41  apFlux = pexConfig.Field(dtype=str, default="flux.sinc", optional=True,
42  doc="the name of the algorithm used to set the source aperture flux slot")
43  modelFlux = pexConfig.Field(dtype=str, default="flux.gaussian", optional=True,
44  doc="the name of the algorithm used to set the source model flux slot")
45  psfFlux = pexConfig.Field(dtype=str, default="flux.psf", optional=True,
46  doc="the name of the algorithm used to set the source psf flux slot")
47  instFlux = pexConfig.Field(dtype=str, default="flux.gaussian", optional=True,
48  doc="the name of the algorithm used to set the source inst flux slot")
49 
50  def setupTable(self, table, prefix=None):
51  """Convenience method to setup a table's slots according to the config definition.
52 
53  This is defined in the Config class to support use in unit tests without needing
54  to construct a Task object.
55  """
56  if prefix is None: prefix = ""
57  if self.centroid is not None: table.defineCentroid(prefix + self.centroid)
58  if self.shape is not None: table.defineShape(prefix + self.shape)
59  if self.apFlux is not None: table.defineApFlux(prefix + self.apFlux)
60  if self.modelFlux is not None: table.defineModelFlux(prefix + self.modelFlux)
61  if self.psfFlux is not None: table.definePsfFlux(prefix + self.psfFlux)
62  if self.instFlux is not None: table.defineInstFlux(prefix + self.instFlux)
63 
64 class SourceMeasurementConfig(pexConfig.Config):
65  """
66  Configuration for SourceMeasurementTask.
67  A configured instance of MeasureSources can be created using the
68  makeMeasureSources method.
69  """
70 
71  slots = pexConfig.ConfigField(
72  dtype = SourceSlotConfig,
73  doc="Mapping from algorithms to special aliases in Source.\n"
74  )
75 
76  algorithms = AlgorithmRegistry.all.makeField(
77  multi=True,
78  default=["flags.pixel",
79  "centroid.gaussian", "centroid.naive",
80  "shape.sdss",
81  "flux.gaussian", "flux.naive", "flux.psf", "flux.sinc",
82  "correctfluxes",
83  "classification.extendedness",
84  "skycoord",
85  ],
86  doc="Algorithms that will be run by default."
87  )
88 
89  centroider = AlgorithmRegistry.filter(CentroidConfig).makeField(
90  multi=False, default="centroid.sdss", optional=True,
91  doc="Configuration for the initial centroid algorithm used to\n"\
92  "feed center points to other algorithms.\n\n"\
93  "Note that this is in addition to the centroider listed in\n"\
94  "the 'algorithms' field; the same name should not appear in\n"\
95  "both.\n\n"\
96  "This field DOES NOT set which field name will be used to define\n"\
97  "the alias for source.getX(), source.getY(), etc.\n"
98  )
99 
100  doReplaceWithNoise = pexConfig.Field(dtype=bool, default=True, optional=False,
101  doc='When measuring, replace other detected footprints with noise?')
102 
103  replaceWithNoise = pexConfig.ConfigurableField(
104  target = ReplaceWithNoiseTask,
105  doc = ("Task for replacing other sources by noise when measuring sources; run when " +
106  "'doReplaceWithNoise' is set."),
107  )
108 
109  prefix = pexConfig.Field(dtype=str, optional=True, default=None, doc="prefix for all measurement fields")
110 
111  def validate(self):
112  pexConfig.Config.validate(self)
113  if self.centroider.name in self.algorithms.names:
114  raise ValueError("The algorithm in the 'centroider' field must not also appear in the "\
115  "'algorithms' field.")
116  if self.slots.centroid is not None and (self.slots.centroid not in self.algorithms.names
117  and self.slots.centroid != self.centroider.name):
118  raise ValueError("source centroid slot algorithm '%s' is not being run." % self.slots.astrom)
119  if self.slots.shape is not None and self.slots.shape not in self.algorithms.names:
120  raise ValueError("source shape slot algorithm '%s' is not being run." % self.slots.shape)
121  for slot in (self.slots.psfFlux, self.slots.apFlux, self.slots.modelFlux, self.slots.instFlux):
122  if slot is not None:
123  for name in self.algorithms.names:
124  if len(name) <= len(slot) and name == slot[:len(name)]:
125  break
126  else:
127  raise ValueError("source flux slot algorithm '%s' is not being run." % slot)
128 
129 
130  def makeMeasureSources(self, schema, metadata=None):
131  """ Convenience method to make a MeasureSources instance and
132  fill it with the configured algorithms.
133 
134  This is defined in the Config class to support use in unit tests without needing
135  to construct a Task object.
136  """
137  builder = algorithmsLib.MeasureSourcesBuilder(self.prefix if self.prefix is not None else "")
138  if self.centroider.name is not None:
139  builder.setCentroider(self.centroider.apply())
140  builder.addAlgorithms(self.algorithms.apply())
141  return builder.build(schema, metadata)
142 
143 ## \addtogroup LSST_task_documentation
144 ## \{
145 ## \page sourceMeasurementTask
146 ## \ref SourceMeasurementTask_ "SourceMeasurementTask"
147 ## \copybrief SourceMeasurementTask
148 ## \}
149 
150 class SourceMeasurementTask(pipeBase.Task):
151  """!
152 \anchor SourceMeasurementTask_
153 \brief Measure the properties of sources on a single exposure.
154 
155 \section meas_algorithms_measurement_Contents Contents
156 
157  - \ref meas_algorithms_measurement_Purpose
158  - \ref meas_algorithms_measurement_Initialize
159  - \ref meas_algorithms_measurement_Invoke
160  - \ref meas_algorithms_measurement_Config
161  - \ref meas_algorithms_measurement_Debug
162  - \ref meas_algorithms_measurement_Example
163 
164 \section meas_algorithms_measurement_Purpose Description
165 
166 \copybrief SourceMeasurementTask
167 
168 \section meas_algorithms_measurement_Initialize Task initialisation
169 
170 \copydoc init
171 
172 \section meas_algorithms_measurement_Invoke Invoking the Task
173 
174 \deprecated This Task's \c run method is currently called \c measure
175 
176 \copydoc measure
177 
178 \subsection SourceMeasurementTask_Hooks Hooks called by measure
179 
180 There are some additional methods available which are typically used to provide extra debugging information.
181 Schematically:
182 \code
183  def measure(self, exposure, sources, ...):
184  self.preMeasureHook(exposure, sources)
185 
186  self.preSingleMeasureHook(exposure, sources, -1)
187  for i, source in enumerate(sources):
188  self.preSingleMeasureHook(exposure, sources, i)
189  self.measurer.apply(source, exposure) # Do the actual measuring
190  self.postSingleMeasureHook(exposure, sources, i)
191 
192  self.postMeasureHook(exposure, sources)
193 \endcode
194 
195 See SourceMeasurementTask.preMeasureHook, SourceMeasurementTask.preSingleMeasureHook,
196 SourceMeasurementTask.preSingleMeasureHook, SourceMeasurementTask.postSingleMeasureHook, and
197 SourceMeasurementTask.postMeasureHook.
198 
199 \section meas_algorithms_measurement_Config Configuration parameters
200 
201 See \ref SourceMeasurementConfig
202 
203 \section meas_algorithms_measurement_Debug Debug variables
204 
205 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
206 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
207 
208 The available variables in SourceMeasurementTask are:
209 <DL>
210  <DT> \c display
211  <DD>
212  - If True, display the exposure on ds9's frame 0. +ve detections in blue, -ve detections in cyan
213  - Measured sources are labelled:
214  - Objects deblended as PSFs with a * and other objects with a +
215  - Brightest peak in red if parent else magenta
216  - All other peaks in yellow
217  - If display > 1, instead label each point by its ID and draw an error ellipse for its centroid
218  - If display > 2, also print a table of (id, ix, iy) for all measured sources
219 </DL>
220 
221 \section meas_algorithms_measurement_Example A complete example of using SourceMeasurementTask
222 
223 This code is in \link measAlgTasks.py\endlink in the examples directory, and can be run as \em e.g.
224 \code
225 examples/measAlgTasks.py --ds9
226 \endcode
227 \dontinclude measAlgTasks.py
228 
229 See \ref meas_algorithms_detection_Example for a few more details on the DetectionTask.
230 
231 Import the tasks (there are some other standard imports; read the file if you're confused)
232 \skip SourceDetectionTask
233 \until SourceMeasurementTask
234 
235 We need to create our tasks before processing any data as the task constructors
236 can add extra columns to the schema. First the detection task
237 \skipline makeMinimalSchema
238 \skip SourceDetectionTask.ConfigClass
239 \until detectionTask
240 and then the measurement task using the default algorithms (as set by SourceMeasurementConfig.algorithms):
241 \skipline SourceMeasurementTask.ConfigClass
242 \skip algMetadata
243 \until measureTask
244 (\c algMetadata is used to return information about the active algorithms).
245 
246 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
247 task objects). First create the output table and process the image to find sources:
248 \skipline afwTable
249 \skip result
250 \until sources
251 
252 Then measure them:
253 \skipline measure
254 
255 We then might plot the results (\em e.g. if you set \c --ds9 on the command line)
256 \skip display
257 \until RED
258 
259 \dontinclude measAlgTasks.py
260 Rather than accept a default set you can select which algorithms should be run.
261 First create the Config object:
262 \skipline SourceMeasurementTask.ConfigClass
263 Then specify which algorithms we're interested in and set any needed parameters:
264 \until radii
265 
266 Unfortunately that won't quite work as there are still "slots" (mappings between measurements like PSF fluxes
267 and the algorithms that calculate them) pointing to some of the discarded algorithms (see SourceSlotConfig,
268 \em e.g. SourceSlotConfig.psfFlux), so:
269 
270 \skip instFlux
271 \until psfFlux
272 and create the task as before:
273 \skipline measureTask
274 We can find out what aperture radii were chosen with
275 \skipline radii
276 and add them to the display code:
277 \skip s in sources
278 \until YELLOW
279 
280 and end up with something like
281 \image html measAlgTasks-ds9.png
282 
283 <HR>
284 To investigate the \ref meas_algorithms_measurement_Debug, put something like
285 \code{.py}
286  import lsstDebug
287  def DebugInfo(name):
288  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
289  if name == "lsst.meas.algorithms.measurement":
290  di.display = 1
291 
292  return di
293 
294  lsstDebug.Info = DebugInfo
295 \endcode
296 into your debug.py file and run measAlgTasks.py with the \c --debug flag.
297  """
298  ConfigClass = SourceMeasurementConfig
299  _DefaultName = "sourceMeasurement"
300  tableVersion = 0
301 
302  def init(self, schema, algMetadata=None, **kwds):
303  """!Create the task, adding necessary fields to the given schema.
304 
305  \param[in,out] schema Schema object for measurement fields; will be modified in-place.
306  \param[in,out] algMetadata Passed to MeasureSources object to be filled with initialization
307  metadata by algorithms (e.g. radii for aperture photometry).
308  \param **kwds Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
309  """
310  self.__init__(schema, algMetadata, **kwds)
311 
312  def __init__(self, schema, algMetadata=None, **kwds):
313  """!Create the measurement task. See SourceMeasurementTask.init for documentation
314  """
315  pipeBase.Task.__init__(self, **kwds)
316  self.measurer = self.config.makeMeasureSources(schema, algMetadata)
317  if self.config.doReplaceWithNoise:
318  self.makeSubtask('replaceWithNoise')
319 
320  def preMeasureHook(self, exposure, sources):
321  '''!A hook, for debugging purposes, that is called at the start of the
322  measure() method (before any noise replacement has occurred)
323  \param exposure The Exposure being measured
324  \param sources The afwTable of Sources to set
325  '''
326 
327  # pipe_base's Task provides self._display.
328  if self._display:
329  frame = 0
330  ds9.mtv(exposure, title="input", frame=frame)
331 
332  def postMeasureHook(self, exposure, sources):
333  '''!A hook, for debugging purposes, that is called at the end of the
334  measure() method, after the sources have been returned to the Exposure.
335  \param exposure The Exposure we just measured
336  \param sources The afwTable of Sources we just measured
337  '''
338  pass
339 
340  def preSingleMeasureHook(self, exposure, sources, i):
341  '''!A hook, for debugging purposes, that is called immediately
342  before the measurement algorithms for each source (after the Source's Footprint
343  has been inserted into the Exposure)
344 
345  \param exposure The Exposure being measured
346  \param sources The afwTable of Sources being measured
347  \param i The index into sources of the Source we're about to measure
348 
349  Note that this will also be called with i=-1 just before entering the
350  loop over measuring sources, *after* the sources have been replaced by noise (if noiseout is True).
351 
352  '''
353 
354  if self._display:
355  if i < 0:
356  # First time...
357  try:
358  self.deblendAsPsfKey = sources.getSchema().find("deblend.deblended-as-psf").getKey()
359  except KeyError:
360  self.deblendAsPsfKey = None
361 
362  if self._display > 2 and i >= 0:
363  peak = sources[i].getFootprint().getPeaks()[0]
364  print "%-9d %4d %4d" % (sources[i].getId(), peak.getIx(), peak.getIy())
365 
366  def postSingleMeasureHook(self, exposure, sources, i):
367  '''!A hook, for debugging purposes, that is called immediately after
368  the measurement algorithms (before the Source has once again been replaced by noise)
369 
370  \param exposure The Exposure being measured
371  \param sources The afwTable of Sources being measured
372  \param i The index into sources of the Source we just measured
373  '''
374  self.postSingleMeasurementDisplay(exposure, sources[i])
375 
376  def postSingleMeasurementDisplay(self, exposure, source):
377  '''!A hook, for debugging purposes, called by postSingleMeasureHook
378 
379  \param exposure The Exposure being measured
380  \param source The Source we just measured
381  '''
382  if self._display:
383  if self._display > 1:
384  ds9.dot(str(source.getId()), source.getX() + 2, source.getY(),
385  size=3, ctype=ds9.RED)
386  cov = source.getCentroidErr()
387  ds9.dot(("@:%.1f,%.1f,%1f" % (cov[0,0], cov[0,1], cov[1,1])),
388  *source.getCentroid(), size=3, ctype=ds9.RED)
389  symb = "%d" % source.getId()
390  else:
391  symb = "*" if self.deblendAsPsfKey and source.get(self.deblendAsPsfKey) else "+"
392  ds9.dot(symb, *source.getCentroid(), size=3,
393  ctype=ds9.RED if source.get("parent") == 0 else ds9.MAGENTA)
394 
395  for p in source.getFootprint().getPeaks():
396  ds9.dot("+", *p.getF(), size=0.5, ctype=ds9.YELLOW)
397 
398  @pipeBase.timeMethod
399  def measure(self, exposure, sources, noiseImage=None, noiseMeanVar=None, references=None, refWcs=None):
400  """!Measure sources on an exposure, with no aperture correction.
401 
402  \param[in] exposure Exposure to process
403  \param[in,out] sources SourceCatalog containing sources detected on this exposure.
404  \param[in] noiseImage If 'config.doReplaceWithNoise = True', you can pass in
405  an Image containing noise. This overrides the "config.noiseSource" setting.
406  \param[in] noiseMeanVar: if 'config.doReplaceWithNoise = True', you can specify
407  the mean and variance of the Gaussian noise that will be added, by passing
408  a tuple of (mean, variance) floats. This overrides the "config.noiseSource"
409  setting (but is overridden by noiseImage).
410  \param[in] references SourceCatalog containing reference sources detected on reference exposure.
411  \param[in] refWcs Wcs for the reference exposure.
412  \return None
413  """
414  if references is None:
415  references = [None] * len(sources)
416  if len(sources) != len(references):
417  raise RuntimeError("Number of sources (%d) and references (%d) don't match" %
418  (len(sources), len(references)))
419 
420  if self.config.doReplaceWithNoise and not hasattr(self, 'replaceWithNoise'):
421  self.makeSubtask('replaceWithNoise')
422 
423  self.log.info("Measuring %d sources" % len(sources))
424  self.config.slots.setupTable(sources.table, prefix=self.config.prefix)
425 
426  self.preMeasureHook(exposure, sources)
427 
428  # "noiseout": we will replace all the pixels within detected
429  # Footprints with noise, and then add sources in one at a
430  # time, measure them, then replace with noise again. The idea
431  # is that measurement algorithms might look outside the
432  # Footprint, and we don't want other sources to interfere with
433  # the measurements. The faint wings of sources are still
434  # there, but that's life.
435  noiseout = self.config.doReplaceWithNoise
436  if noiseout:
437  self.replaceWithNoise.begin(exposure, sources, noiseImage, noiseMeanVar)
438  # At this point the whole image should just look like noise.
439 
440  # Call the hook, with source id = -1, before we measure anything.
441  # (this is *after* the sources have been replaced by noise, if noiseout)
442  self.preSingleMeasureHook(exposure, sources, -1)
443 
444  with ds9.Buffering():
445  for i, (source, ref) in enumerate(zip(sources, references)):
446  if noiseout:
447  self.replaceWithNoise.insertSource(exposure, i)
448 
449  self.preSingleMeasureHook(exposure, sources, i)
450 
451  # Make the measurement
452  if ref is None:
453  self.measurer.apply(source, exposure)
454  else:
455  self.measurer.apply(source, exposure, ref, refWcs)
456 
457  self.postSingleMeasureHook(exposure, sources, i)
458 
459  if noiseout:
460  # Replace this source's pixels by noise again.
461  self.replaceWithNoise.removeSource(exposure, sources, source)
462 
463  if noiseout:
464  # Put the exposure back the way it was
465  self.replaceWithNoise.end(exposure, sources)
466 
467  self.postMeasureHook(exposure, sources)
468 
469  # Alias for backwards compatibility
470  run = measure
def preMeasureHook
A hook, for debugging purposes, that is called at the start of the measure() method (before any noise...
Definition: measurement.py:320
def postSingleMeasurementDisplay
A hook, for debugging purposes, called by postSingleMeasureHook.
Definition: measurement.py:376
def preSingleMeasureHook
A hook, for debugging purposes, that is called immediately before the measurement algorithms for each...
Definition: measurement.py:340
def postSingleMeasureHook
A hook, for debugging purposes, that is called immediately after the measurement algorithms (before t...
Definition: measurement.py:366
def init
Create the task, adding necessary fields to the given schema.
Definition: measurement.py:302
def measure
Measure sources on an exposure, with no aperture correction.
Definition: measurement.py:399
Measure the properties of sources on a single exposure.
Definition: measurement.py:150
def postMeasureHook
A hook, for debugging purposes, that is called at the end of the measure() method, after the sources have been returned to the Exposure.
Definition: measurement.py:332