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