LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
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 
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  self.log.info("PSF star selector found %d candidates" % len(selectionResult.psfCandidates))
277  reserveResult = self.reserve.run(selectionResult.goodStarCat, expId=expId)
278  # Make list of psf candidates to send to the determiner (omitting those marked as reserved)
279  psfDeterminerList = [cand for cand, use
280  in zip(selectionResult.psfCandidates, reserveResult.use) if use]
281 
282  if selectionResult.psfCandidates and self.candidateKey is not None:
283  for cand in selectionResult.psfCandidates:
284  source = cand.getSource()
285  source.set(self.candidateKey, True)
286 
287  self.log.info("Sending %d candidates to PSF determiner" % len(psfDeterminerList))
288 
289  if display:
290  frame = display
291  if displayExposure:
292  ds9.mtv(exposure, frame=frame, title="psf determination")
293 
294  #
295  # Determine PSF
296  #
297  psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
298  flagKey=self.usedKey)
299  self.log.info("PSF determination using %d/%d stars." %
300  (self.metadata.getScalar("numGoodStars"), self.metadata.getScalar("numAvailStars")))
301 
302  exposure.setPsf(psf)
303 
304  if display:
305  frame = display
306  if displayExposure:
307  showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=frame)
308  frame += 1
309 
310  if displayPsfCandidates: # Show a mosaic of PSF candidates
311  plotPsfCandidates(cellSet, showBadCandidates, frame)
312  frame += 1
313 
314  if displayResiduals:
315  frame = plotResiduals(exposure, cellSet,
316  showBadCandidates=showBadCandidates,
317  normalizeResiduals=normalizeResiduals,
318  frame=frame)
319  if displayPsfMosaic:
320  maUtils.showPsfMosaic(exposure, psf, frame=frame, showFwhm=True)
321  ds9.scale(0, 1, "linear", frame=frame)
322  frame += 1
323 
324  return pipeBase.Struct(
325  psf=psf,
326  cellSet=cellSet,
327  )
328 
329  @property
330  def usesMatches(self):
331  """Return True if this task makes use of the "matches" argument to the run method"""
332  return self.starSelector.usesMatches
333 
334 #
335 # Debug code
336 #
337 
338 
339 def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1):
340  maUtils.showPsfSpatialCells(exposure, cellSet,
341  symb="o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
342  size=4, frame=frame)
343  for cell in cellSet.getCellList():
344  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
345  status = cand.getStatus()
346  ds9.dot('+', *cand.getSource().getCentroid(), frame=frame,
347  ctype=ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
348  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
349 
350 
351 def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1):
352  import lsst.afw.display.utils as displayUtils
353 
354  stamps = []
355  for cell in cellSet.getCellList():
356  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
357  try:
358  im = cand.getMaskedImage()
359 
360  chi2 = cand.getChi2()
361  if chi2 < 1e100:
362  chi2 = "%.1f" % chi2
363  else:
364  chi2 = float("nan")
365 
366  stamps.append((im, "%d%s" %
367  (maUtils.splitId(cand.getSource().getId(), True)["objId"], chi2),
368  cand.getStatus()))
369  except Exception:
370  continue
371 
372  mos = displayUtils.Mosaic()
373  for im, label, status in stamps:
374  im = type(im)(im, True)
375  try:
376  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
377  except NotImplementedError:
378  pass
379 
380  mos.append(im, label,
381  ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
382  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
383 
384  if mos.images:
385  mos.makeMosaic(frame=frame, title="Psf Candidates")
386 
387 
388 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
389  psf = exposure.getPsf()
390  while True:
391  try:
392  maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
393  normalize=normalizeResiduals,
394  showBadCandidates=showBadCandidates)
395  frame += 1
396  maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
397  normalize=normalizeResiduals,
398  showBadCandidates=showBadCandidates,
399  variance=True)
400  frame += 1
401  except Exception:
402  if not showBadCandidates:
403  showBadCandidates = True
404  continue
405  break
406 
407  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:351
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
Definition: Statistics.h:520
def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2)
Definition: measurePsf.py:388
table::Key< int > type
Definition: Detector.cc:164
def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1)
Definition: measurePsf.py:339
def __init__(self, schema=None, kwargs)
Create the detection task.
Definition: measurePsf.py:205