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