lsst.pipe.tasks  16.0-16-gebdc7379+2
measurePsf.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 lsst.afw.math as afwMath
23 import lsst.afw.display.ds9 as ds9
24 import lsst.meas.algorithms as measAlg
25 import lsst.meas.algorithms.utils as maUtils
26 import lsst.pex.config as pexConfig
27 import lsst.pipe.base as pipeBase
28 
29 
30 class MeasurePsfConfig(pexConfig.Config):
31  starSelector = measAlg.sourceSelectorRegistry.makeField(
32  "Star selection algorithm",
33  default="objectSize"
34  )
35  makePsfCandidates = pexConfig.ConfigurableField(
36  target=measAlg.MakePsfCandidatesTask,
37  doc="Task to make psf candidates from selected stars.",
38  )
39  psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
40  "PSF Determination algorithm",
41  default="pca"
42  )
43  reserve = pexConfig.ConfigurableField(
44  target=measAlg.ReserveSourcesTask,
45  doc="Reserve sources from fitting"
46  )
47 
48 
54 
55 
56 class MeasurePsfTask(pipeBase.Task):
57  r"""!
58 @anchor MeasurePsfTask_
59 
60 @brief Measure the PSF
61 
62 @section pipe_tasks_measurePsf_Contents Contents
63 
64  - @ref pipe_tasks_measurePsf_Purpose
65  - @ref pipe_tasks_measurePsf_Initialize
66  - @ref pipe_tasks_measurePsf_IO
67  - @ref pipe_tasks_measurePsf_Config
68  - @ref pipe_tasks_measurePsf_Debug
69  - @ref pipe_tasks_measurePsf_Example
70 
71 @section pipe_tasks_measurePsf_Purpose Description
72 
73 A task that selects stars from a catalog of sources and uses those to measure the PSF.
74 
75 The star selector is a subclass of
76 @ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
77 and the PSF determiner is a sublcass of
78 @ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
79 
80 @warning
81 There is no establised set of configuration parameters for these algorithms, so once you start modifying
82 parameters (as we do in @ref pipe_tasks_measurePsf_Example) your code is no longer portable.
83 
84 @section pipe_tasks_measurePsf_Initialize Task initialisation
85 
86 @copydoc \_\_init\_\_
87 
88 @section pipe_tasks_measurePsf_IO Invoking the Task
89 
90 @copydoc run
91 
92 @section pipe_tasks_measurePsf_Config Configuration parameters
93 
94 See @ref MeasurePsfConfig.
95 
96 @section pipe_tasks_measurePsf_Debug Debug variables
97 
98 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
99 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py files.
100 
101 <DL>
102  <DT> @c display
103  <DD> If True, display debugging plots
104  <DT> displayExposure
105  <DD> display the Exposure + spatialCells
106  <DT> displayPsfCandidates
107  <DD> show mosaic of candidates
108  <DT> showBadCandidates
109  <DD> Include bad candidates
110  <DT> displayPsfMosaic
111  <DD> show mosaic of reconstructed PSF(xy)
112  <DT> displayResiduals
113  <DD> show residuals
114  <DT> normalizeResiduals
115  <DD> Normalise residuals by object amplitude
116 </DL>
117 
118 Additionally you can enable any debug outputs that your chosen star selector and psf determiner support.
119 
120 @section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
121 
122 This code is in @link measurePsfTask.py@endlink in the examples directory, and can be run as @em e.g.
123 @code
124 examples/measurePsfTask.py --ds9
125 @endcode
126 @dontinclude measurePsfTask.py
127 
128 The example also runs SourceDetectionTask and SourceMeasurementTask;
129 see @ref meas_algorithms_measurement_Example for more explanation.
130 
131 Import the tasks (there are some other standard imports; read the file to see them all):
132 
133 @skip SourceDetectionTask
134 @until MeasurePsfTask
135 
136 We need to create the tasks before processing any data as the task constructor
137 can add an extra column to the schema, but first we need an almost-empty
138 Schema:
139 
140 @skipline makeMinimalSchema
141 
142 We can now call the constructors for the tasks we need to find and characterize candidate
143 PSF stars:
144 
145 @skip SourceDetectionTask.ConfigClass
146 @until measureTask
147 
148 Note that we've chosen a minimal set of measurement plugins: we need the
149 outputs of @c base_SdssCentroid, @c base_SdssShape and @c base_CircularApertureFlux as
150 inputs to the PSF measurement algorithm, while @c base_PixelFlags identifies
151 and flags bad sources (e.g. with pixels too close to the edge) so they can be
152 removed later.
153 
154 Now we can create and configure the task that we're interested in:
155 
156 @skip MeasurePsfTask
157 @until measurePsfTask
158 
159 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
160 task objects). First create the output table:
161 
162 @skipline afwTable
163 
164 And process the image:
165 
166 @skip sources =
167 @until result
168 
169 We can then unpack and use the results:
170 
171 @skip psf
172 @until cellSet
173 
174 If you specified @c --ds9 you can see the PSF candidates:
175 
176 @skip display
177 @until RED
178 
179 <HR>
180 
181 To investigate the @ref pipe_tasks_measurePsf_Debug, put something like
182 @code{.py}
183  import lsstDebug
184  def DebugInfo(name):
185  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
186 
187  if name == "lsst.pipe.tasks.measurePsf" :
188  di.display = True
189  di.displayExposure = False # display the Exposure + spatialCells
190  di.displayPsfCandidates = True # show mosaic of candidates
191  di.displayPsfMosaic = True # show mosaic of reconstructed PSF(xy)
192  di.displayResiduals = True # show residuals
193  di.showBadCandidates = True # Include bad candidates
194  di.normalizeResiduals = False # Normalise residuals by object amplitude
195 
196  return di
197 
198  lsstDebug.Info = DebugInfo
199 @endcode
200 into your debug.py file and run measurePsfTask.py with the @c --debug flag.
201  """
202  ConfigClass = MeasurePsfConfig
203  _DefaultName = "measurePsf"
204 
205  def __init__(self, schema=None, **kwargs):
206  """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
207 
208  @param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
209  @param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
210 
211  If schema is not None, 'calib_psf_candidate' and 'calib_psf_used' fields will be added to
212  identify which stars were employed in the PSF estimation.
213 
214  @note This task can add fields to the schema, so any code calling this task must ensure that
215  these fields are indeed present in the input table.
216  """
217 
218  pipeBase.Task.__init__(self, **kwargs)
219  if schema is not None:
220  self.candidateKey = schema.addField(
221  "calib_psf_candidate", type="Flag",
222  doc=("Flag set if the source was a candidate for PSF determination, "
223  "as determined by the star selector.")
224  )
225  self.usedKey = schema.addField(
226  "calib_psf_used", type="Flag",
227  doc=("Flag set if the source was actually used for PSF determination, "
228  "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
229  )
230  else:
231  self.candidateKey = None
232  self.usedKey = None
233  self.makeSubtask("starSelector")
234  self.makeSubtask("makePsfCandidates")
235  self.makeSubtask("psfDeterminer", schema=schema)
236  self.makeSubtask("reserve", columnName="calib_psf", schema=schema,
237  doc="set if source was reserved from PSF determination")
238 
239  @pipeBase.timeMethod
240  def run(self, exposure, sources, expId=0, matches=None):
241  """!Measure the PSF
242 
243  @param[in,out] exposure Exposure to process; measured PSF will be added.
244  @param[in,out] sources Measured sources on exposure; flag fields will be set marking
245  stars chosen by the star selector and the PSF determiner if a schema
246  was passed to the task constructor.
247  @param[in] expId Exposure id used for generating random seed.
248  @param[in] matches A list of lsst.afw.table.ReferenceMatch objects
249  (@em i.e. of lsst.afw.table.Match
250  with @c first being of type lsst.afw.table.SimpleRecord and @c second
251  type lsst.afw.table.SourceRecord --- the reference object and detected
252  object respectively) as returned by @em e.g. the AstrometryTask.
253  Used by star selectors that choose to refer to an external catalog.
254 
255  @return a pipe.base.Struct with fields:
256  - psf: The measured PSF (also set in the input exposure)
257  - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
258  as returned by the psf determiner.
259  """
260  self.log.info("Measuring PSF")
261 
262  import lsstDebug
263  display = lsstDebug.Info(__name__).display
264  displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells
265  displayPsfMosaic = lsstDebug.Info(__name__).displayPsfMosaic # show mosaic of reconstructed PSF(x,y)
266  displayPsfCandidates = lsstDebug.Info(__name__).displayPsfCandidates # show mosaic of candidates
267  displayResiduals = lsstDebug.Info(__name__).displayResiduals # show residuals
268  showBadCandidates = lsstDebug.Info(__name__).showBadCandidates # include bad candidates
269  normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals # normalise residuals by object peak
270 
271  #
272  # Run star selector
273  #
274  stars = self.starSelector.run(sourceCat=sources, matches=matches, exposure=exposure)
275  selectionResult = self.makePsfCandidates.run(stars.sourceCat, exposure=exposure)
276  reserveResult = self.reserve.run(selectionResult.goodStarCat, expId=expId)
277  psfCandidateList = [cand for cand, use
278  in zip(selectionResult.psfCandidates, reserveResult.use) if use]
279 
280  if psfCandidateList and self.candidateKey is not None:
281  for cand in psfCandidateList:
282  source = cand.getSource()
283  source.set(self.candidateKey, True)
284 
285  self.log.info("PSF star selector found %d candidates" % len(psfCandidateList))
286 
287  if display:
288  frame = display
289  if displayExposure:
290  ds9.mtv(exposure, frame=frame, title="psf determination")
291 
292  #
293  # Determine PSF
294  #
295  psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfCandidateList, self.metadata,
296  flagKey=self.usedKey)
297  self.log.info("PSF determination using %d/%d stars." %
298  (self.metadata.getScalar("numGoodStars"), self.metadata.getScalar("numAvailStars")))
299 
300  exposure.setPsf(psf)
301 
302  if display:
303  frame = display
304  if displayExposure:
305  showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=frame)
306  frame += 1
307 
308  if displayPsfCandidates: # Show a mosaic of PSF candidates
309  plotPsfCandidates(cellSet, showBadCandidates, frame)
310  frame += 1
311 
312  if displayResiduals:
313  frame = plotResiduals(exposure, cellSet,
314  showBadCandidates=showBadCandidates,
315  normalizeResiduals=normalizeResiduals,
316  frame=frame)
317  if displayPsfMosaic:
318  maUtils.showPsfMosaic(exposure, psf, frame=frame, showFwhm=True)
319  ds9.scale(0, 1, "linear", frame=frame)
320  frame += 1
321 
322  return pipeBase.Struct(
323  psf=psf,
324  cellSet=cellSet,
325  )
326 
327  @property
328  def usesMatches(self):
329  """Return True if this task makes use of the "matches" argument to the run method"""
330  return self.starSelector.usesMatches
331 
332 #
333 # Debug code
334 #
335 
336 
337 def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1):
338  maUtils.showPsfSpatialCells(exposure, cellSet,
339  symb="o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
340  size=4, frame=frame)
341  for cell in cellSet.getCellList():
342  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
343  status = cand.getStatus()
344  ds9.dot('+', *cand.getSource().getCentroid(), frame=frame,
345  ctype=ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
346  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
347 
348 
349 def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1):
350  import lsst.afw.display.utils as displayUtils
351 
352  stamps = []
353  for cell in cellSet.getCellList():
354  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
355  try:
356  im = cand.getMaskedImage()
357 
358  chi2 = cand.getChi2()
359  if chi2 < 1e100:
360  chi2 = "%.1f" % chi2
361  else:
362  chi2 = float("nan")
363 
364  stamps.append((im, "%d%s" %
365  (maUtils.splitId(cand.getSource().getId(), True)["objId"], chi2),
366  cand.getStatus()))
367  except Exception:
368  continue
369 
370  mos = displayUtils.Mosaic()
371  for im, label, status in stamps:
372  im = type(im)(im, True)
373  try:
374  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
375  except NotImplementedError:
376  pass
377 
378  mos.append(im, label,
379  ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
380  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
381 
382  if mos.images:
383  mos.makeMosaic(frame=frame, title="Psf Candidates")
384 
385 
386 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
387  psf = exposure.getPsf()
388  while True:
389  try:
390  maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
391  normalize=normalizeResiduals,
392  showBadCandidates=showBadCandidates)
393  frame += 1
394  maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
395  normalize=normalizeResiduals,
396  showBadCandidates=showBadCandidates,
397  variance=True)
398  frame += 1
399  except Exception:
400  if not showBadCandidates:
401  showBadCandidates = True
402  continue
403  break
404 
405  return frame
def run(self, exposure, sources, expId=0, matches=None)
Measure the PSF.
Definition: measurePsf.py:240
def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1)
Definition: measurePsf.py:349
def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2)
Definition: measurePsf.py:386
def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1)
Definition: measurePsf.py:337
def __init__(self, schema=None, kwargs)
Create the detection task.
Definition: measurePsf.py:205