LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
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 random
23 
24 import lsst.afw.math as afwMath
25 import lsst.afw.display.ds9 as ds9
26 import lsst.meas.algorithms as measAlg
27 import lsst.meas.algorithms.utils as maUtils
28 import lsst.pex.config as pexConfig
29 import lsst.pipe.base as pipeBase
30 
31 
32 class MeasurePsfConfig(pexConfig.Config):
33  starSelector = measAlg.starSelectorRegistry.makeField("Star selection algorithm", default="objectSize")
34  psfDeterminer = measAlg.psfDeterminerRegistry.makeField("PSF Determination algorithm", default="pca")
35  reserveFraction = pexConfig.Field(
36  dtype=float,
37  doc="Fraction of PSF candidates to reserve from fitting; none if <= 0",
38  default=-1.0,
39  )
40  reserveSeed = pexConfig.Field(
41  dtype = int,
42  doc = "This number will be multiplied by the exposure ID "
43  "to set the random seed for reserving candidates",
44  default = 1,
45  )
46 
47 ## \addtogroup LSST_task_documentation
48 ## \{
49 ## \page MeasurePsfTask
50 ## \ref MeasurePsfTask_ "MeasurePsfTask"
51 ## \copybrief MeasurePsfTask
52 ## \}
53 
54 
55 class MeasurePsfTask(pipeBase.Task):
56  r"""!
57 \anchor MeasurePsfTask_
58 
59 \brief Measure the PSF
60 
61 \section pipe_tasks_measurePsf_Contents Contents
62 
63  - \ref pipe_tasks_measurePsf_Purpose
64  - \ref pipe_tasks_measurePsf_Initialize
65  - \ref pipe_tasks_measurePsf_IO
66  - \ref pipe_tasks_measurePsf_Config
67  - \ref pipe_tasks_measurePsf_Debug
68  - \ref pipe_tasks_measurePsf_Example
69 
70 \section pipe_tasks_measurePsf_Purpose Description
71 
72 A task that selects stars from a catalog of sources and uses those to measure the PSF.
73 
74 The star selector is a subclass of
75 \ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
76 and the PSF determiner is a sublcass of
77 \ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
78 
79 \warning
80 There is no establised set of configuration parameters for these algorithms, so once you start modifying
81 parameters (as we do in \ref pipe_tasks_measurePsf_Example) your code is no longer portable.
82 
83 \section pipe_tasks_measurePsf_Initialize Task initialisation
84 
85 \copydoc \_\_init\_\_
86 
87 \section pipe_tasks_measurePsf_IO Invoking the Task
88 
89 \copydoc run
90 
91 \section pipe_tasks_measurePsf_Config Configuration parameters
92 
93 See \ref MeasurePsfConfig.
94 
95 \section pipe_tasks_measurePsf_Debug Debug variables
96 
97 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
98 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
99 
100 <DL>
101  <DT> \c display
102  <DD> If True, display debugging plots
103  <DT> displayExposure
104  <DD> display the Exposure + spatialCells
105  <DT> displayPsfCandidates
106  <DD> show mosaic of candidates
107  <DT> showBadCandidates
108  <DD> Include bad candidates
109  <DT> displayPsfMosaic
110  <DD> show mosaic of reconstructed PSF(xy)
111  <DT> displayResiduals
112  <DD> show residuals
113  <DT> normalizeResiduals
114  <DD> Normalise residuals by object amplitude
115 </DL>
116 
117 Additionally you can enable any debug outputs that your chosen star selector and psf determiner support.
118 
119 \section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
120 
121 This code is in \link measurePsfTask.py\endlink in the examples directory, and can be run as \em e.g.
122 \code
123 examples/measurePsfTask.py --ds9
124 \endcode
125 \dontinclude measurePsfTask.py
126 
127 The example also runs SourceDetectionTask and SourceMeasurementTask;
128 see \ref meas_algorithms_measurement_Example for more explanation.
129 
130 Import the tasks (there are some other standard imports; read the file to see them all):
131 
132 \skip SourceDetectionTask
133 \until MeasurePsfTask
134 
135 We need to create the tasks before processing any data as the task constructor
136 can add an extra column to the schema, but first we need an almost-empty
137 Schema:
138 
139 \skipline makeMinimalSchema
140 
141 We can now call the constructors for the tasks we need to find and characterize candidate
142 PSF stars:
143 
144 \skip SourceDetectionTask.ConfigClass
145 \until measureTask
146 
147 Note that we've chosen a minimal set of measurement plugins: we need the
148 outputs of \c base_SdssCentroid, \c base_SdssShape and \c base_CircularApertureFlux as
149 inputs to the PSF measurement algorithm, while \c base_PixelFlags identifies
150 and flags bad sources (e.g. with pixels too close to the edge) so they can be
151 removed later.
152 
153 Now we can create and configure the task that we're interested in:
154 
155 \skip MeasurePsfTask
156 \until measurePsfTask
157 
158 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
159 task objects). First create the output table:
160 
161 \skipline afwTable
162 
163 And process the image:
164 
165 \skip sources =
166 \until result
167 
168 We can then unpack and use the results:
169 
170 \skip psf
171 \until cellSet
172 
173 If you specified \c --ds9 you can see the PSF candidates:
174 
175 \skip display
176 \until RED
177 
178 <HR>
179 
180 To investigate the \ref pipe_tasks_measurePsf_Debug, put something like
181 \code{.py}
182  import lsstDebug
183  def DebugInfo(name):
184  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
185 
186  if name == "lsst.pipe.tasks.measurePsf" :
187  di.display = True
188  di.displayExposure = False # display the Exposure + spatialCells
189  di.displayPsfCandidates = True # show mosaic of candidates
190  di.displayPsfMosaic = True # show mosaic of reconstructed PSF(xy)
191  di.displayResiduals = True # show residuals
192  di.showBadCandidates = True # Include bad candidates
193  di.normalizeResiduals = False # Normalise residuals by object amplitude
194 
195  return di
196 
197  lsstDebug.Info = DebugInfo
198 \endcode
199 into your debug.py file and run measurePsfTask.py with the \c --debug flag.
200  """
201  ConfigClass = MeasurePsfConfig
202  _DefaultName = "measurePsf"
203 
204  def __init__(self, schema=None, **kwargs):
205  """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
206 
207  \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
208  \param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
209 
210  If schema is not None, 'calib.psf.candidate' and 'calib.psf.used' fields will be added to
211  identify which stars were employed in the PSF estimation.
212 
213  \note This task can add fields to the schema, so any code calling this task must ensure that
214  these fields are indeed present in the input table.
215  """
216 
217  pipeBase.Task.__init__(self, **kwargs)
218  if schema is not None:
219  self.candidateKey = schema.addField(
220  "calib_psfCandidate", type="Flag",
221  doc=("Flag set if the source was a candidate for PSF determination, "
222  "as determined by the star selector.")
223  )
224  self.usedKey = schema.addField(
225  "calib_psfUsed", type="Flag",
226  doc=("Flag set if the source was actually used for PSF determination, "
227  "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
228  )
229  self.reservedKey = schema.addField(
230  "calib_psfReserved", type="Flag",
231  doc=("Flag set if the source was selected as a PSF candidate, but was "
232  "reserved from the PSF fitting."))
233  else:
234  self.candidateKey = None
235  self.usedKey = None
236  self.makeSubtask("starSelector", schema=schema)
237  self.makeSubtask("psfDeterminer", schema=schema)
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  #
273  # Run star selector
274  #
275  psfCandidateList = self.starSelector.run(exposure=exposure, sourceCat=sources,
276  matches=matches).psfCandidates
277  reserveList = []
278 
279  if self.config.reserveFraction > 0:
280  random.seed(self.config.reserveSeed*expId)
281  reserveList = random.sample(psfCandidateList,
282  int((self.config.reserveFraction)*len(psfCandidateList)))
283 
284  for cand in reserveList:
285  psfCandidateList.remove(cand)
286 
287  if reserveList and self.reservedKey is not None:
288  for cand in reserveList:
289  source = cand.getSource()
290  source.set(self.reservedKey, True)
291 
292  if psfCandidateList and self.candidateKey is not None:
293  for cand in psfCandidateList:
294  source = cand.getSource()
295  source.set(self.candidateKey, True)
296 
297  self.log.info("PSF star selector found %d candidates" % len(psfCandidateList))
298  if self.config.reserveFraction > 0:
299  self.log.info("Reserved %d candidates from the fitting" % len(reserveList))
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  @property
343  def usesMatches(self):
344  """Return True if this task makes use of the "matches" argument to the run method"""
345  return self.starSelector.usesMatches
346 
347 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
348 #
349 # Debug code
350 #
351 
352 
353 def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1):
354  maUtils.showPsfSpatialCells(exposure, cellSet,
355  symb="o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
356  size=4, frame=frame)
357  for cell in cellSet.getCellList():
358  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
359  cand = measAlg.cast_PsfCandidateF(cand)
360  status = cand.getStatus()
361  ds9.dot('+', *cand.getSource().getCentroid(), frame=frame,
362  ctype=ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
363  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
364 
365 
366 def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1):
367  import lsst.afw.display.utils as displayUtils
368 
369  stamps = []
370  for cell in cellSet.getCellList():
371  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
372  cand = measAlg.cast_PsfCandidateF(cand)
373 
374  try:
375  im = cand.getMaskedImage()
376 
377  chi2 = cand.getChi2()
378  if chi2 < 1e100:
379  chi2 = "%.1f" % chi2
380  else:
381  chi2 = float("nan")
382 
383  stamps.append((im, "%d%s" %
384  (maUtils.splitId(cand.getSource().getId(), True)["objId"], chi2),
385  cand.getStatus()))
386  except Exception:
387  continue
388 
389  mos = displayUtils.Mosaic()
390  for im, label, status in stamps:
391  im = type(im)(im, True)
392  try:
393  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
394  except NotImplementedError:
395  pass
396 
397  mos.append(im, label,
398  ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
399  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
400 
401  if mos.images:
402  mos.makeMosaic(frame=frame, title="Psf Candidates")
403 
404 
405 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
406  psf = exposure.getPsf()
407  while True:
408  try:
409  maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
410  normalize=normalizeResiduals,
411  showBadCandidates=showBadCandidates)
412  frame += 1
413  maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
414  normalize=normalizeResiduals,
415  showBadCandidates=showBadCandidates,
416  variance=True)
417  frame += 1
418  except Exception:
419  if not showBadCandidates:
420  showBadCandidates = True
421  continue
422  break
423 
424  return frame
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
def __init__
Create the detection task.
Definition: measurePsf.py:204
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1107