LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
pcaPsfDeterminer.py
Go to the documentation of this file.
1 from __future__ import print_function
2 from builtins import input
3 from builtins import zip
4 from builtins import range
5 #
6 # LSST Data Management System
7 # Copyright 2008, 2009, 2010 LSST Corporation.
8 #
9 # This product includes software developed by the
10 # LSST Project (http://www.lsst.org/).
11 #
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the LSST License Statement and
23 # the GNU General Public License along with this program. If not,
24 # see <http://www.lsstcorp.org/LegalNotices/>.
25 #
26 import math
27 import sys
28 
29 import numpy
30 
31 import lsst.pex.config as pexConfig
32 import lsst.pex.exceptions as pexExceptions
33 import lsst.afw.geom as afwGeom
34 import lsst.afw.geom.ellipses as afwEll
35 import lsst.afw.display.ds9 as ds9
36 import lsst.afw.math as afwMath
37 from .psfDeterminer import BasePsfDeterminerTask, psfDeterminerRegistry
38 from . import algorithmsLib
39 from . import utils as maUtils
40 
41 __all__ = ["PcaPsfDeterminerConfig", "PcaPsfDeterminerTask"]
42 
43 
44 class PcaPsfDeterminerConfig(BasePsfDeterminerTask.ConfigClass):
45  nonLinearSpatialFit = pexConfig.Field(
46  doc="Use non-linear fitter for spatial variation of Kernel",
47  dtype=bool,
48  default=False,
49  )
50  nEigenComponents = pexConfig.Field(
51  doc="number of eigen components for PSF kernel creation",
52  dtype=int,
53  default=4,
54  )
55  spatialOrder = pexConfig.Field(
56  doc="specify spatial order for PSF kernel creation",
57  dtype=int,
58  default=2,
59  )
60  sizeCellX = pexConfig.Field(
61  doc="size of cell used to determine PSF (pixels, column direction)",
62  dtype=int,
63  default=256,
64  # minValue = 10,
65  check=lambda x: x >= 10,
66  )
67  sizeCellY = pexConfig.Field(
68  doc="size of cell used to determine PSF (pixels, row direction)",
69  dtype=int,
70  default=sizeCellX.default,
71  # minValue = 10,
72  check=lambda x: x >= 10,
73  )
74  nStarPerCell = pexConfig.Field(
75  doc="number of stars per psf cell for PSF kernel creation",
76  dtype=int,
77  default=3,
78  )
79  borderWidth = pexConfig.Field(
80  doc="Number of pixels to ignore around the edge of PSF candidate postage stamps",
81  dtype=int,
82  default=0,
83  )
84  nStarPerCellSpatialFit = pexConfig.Field(
85  doc="number of stars per psf Cell for spatial fitting",
86  dtype=int,
87  default=5,
88  )
89  constantWeight = pexConfig.Field(
90  doc="Should each PSF candidate be given the same weight, independent of magnitude?",
91  dtype=bool,
92  default=True,
93  )
94  nIterForPsf = pexConfig.Field(
95  doc="number of iterations of PSF candidate star list",
96  dtype=int,
97  default=3,
98  )
99  tolerance = pexConfig.Field(
100  doc="tolerance of spatial fitting",
101  dtype=float,
102  default=1e-2,
103  )
104  lam = pexConfig.Field(
105  doc="floor for variance is lam*data",
106  dtype=float,
107  default=0.05,
108  )
109  reducedChi2ForPsfCandidates = pexConfig.Field(
110  doc="for psf candidate evaluation",
111  dtype=float,
112  default=2.0,
113  )
114  spatialReject = pexConfig.Field(
115  doc="Rejection threshold (stdev) for candidates based on spatial fit",
116  dtype=float,
117  default=3.0,
118  )
119  pixelThreshold = pexConfig.Field(
120  doc="Threshold (stdev) for rejecting extraneous pixels around candidate; applied if positive",
121  dtype=float,
122  default=0.0,
123  )
124  doRejectBlends = pexConfig.Field(
125  doc="Reject candidates that are blended?",
126  dtype=bool,
127  default=False,
128  )
129  doMaskBlends = pexConfig.Field(
130  doc="Mask blends in image?",
131  dtype=bool,
132  default=True,
133  )
134 
135 
136 class PcaPsfDeterminerTask(BasePsfDeterminerTask):
137  """!
138  A measurePsfTask psf estimator
139  """
140  ConfigClass = PcaPsfDeterminerConfig
141 
142  def _fitPsf(self, exposure, psfCellSet, kernelSize, nEigenComponents):
143  algorithmsLib.PsfCandidateF.setPixelThreshold(self.config.pixelThreshold)
144  algorithmsLib.PsfCandidateF.setMaskBlends(self.config.doMaskBlends)
145  #
146  # Loop trying to use nEigenComponents, but allowing smaller numbers if necessary
147  #
148  for nEigen in range(nEigenComponents, 0, -1):
149  # Determine KL components
150  try:
151  kernel, eigenValues = algorithmsLib.createKernelFromPsfCandidates(
152  psfCellSet, exposure.getDimensions(), exposure.getXY0(), nEigen,
153  self.config.spatialOrder, kernelSize, self.config.nStarPerCell,
154  bool(self.config.constantWeight))
155 
156  break # OK, we can get nEigen components
157  except pexExceptions.LengthError as e:
158  if nEigen == 1: # can't go any lower
159  raise IndexError("No viable PSF candidates survive")
160 
161  self.log.warn("%s: reducing number of eigen components" % e.what())
162  #
163  # We got our eigen decomposition so let's use it
164  #
165  # Express eigenValues in units of reduced chi^2 per star
166  size = kernelSize + 2*self.config.borderWidth
167  nu = size*size - 1 # number of degrees of freedom/star for chi^2
168  eigenValues = [l/float(algorithmsLib.countPsfCandidates(psfCellSet, self.config.nStarPerCell)*nu)
169  for l in eigenValues]
170 
171  # Fit spatial model
172  status, chi2 = algorithmsLib.fitSpatialKernelFromPsfCandidates(
173  kernel, psfCellSet, bool(self.config.nonLinearSpatialFit),
174  self.config.nStarPerCellSpatialFit, self.config.tolerance, self.config.lam)
175 
176  psf = algorithmsLib.PcaPsf(kernel)
177 
178  return psf, eigenValues, nEigen, chi2
179 
180  def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
181  """!Determine a PCA PSF model for an exposure given a list of PSF candidates
182 
183  \param[in] exposure exposure containing the psf candidates (lsst.afw.image.Exposure)
184  \param[in] psfCandidateList a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate);
185  typically obtained by detecting sources and then running them through a star selector
186  \param[in,out] metadata a home for interesting tidbits of information
187  \param[in] flagKey schema key used to mark sources actually used in PSF determination
188 
189  \return a list of
190  - psf: the measured PSF, an lsst.meas.algorithms.PcaPsf
191  - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
192  """
193  import lsstDebug
194  display = lsstDebug.Info(__name__).display
195  displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells
196  displayPsfCandidates = lsstDebug.Info(__name__).displayPsfCandidates # show the viable candidates
197  displayIterations = lsstDebug.Info(__name__).displayIterations # display on each PSF iteration
198  displayPsfComponents = lsstDebug.Info(__name__).displayPsfComponents # show the PCA components
199  displayResiduals = lsstDebug.Info(__name__).displayResiduals # show residuals
200  displayPsfMosaic = lsstDebug.Info(__name__).displayPsfMosaic # show mosaic of reconstructed PSF(x,y)
201  # match Kernel amplitudes for spatial plots
202  matchKernelAmplitudes = lsstDebug.Info(__name__).matchKernelAmplitudes
203  # Keep matplotlib alive post mortem
204  keepMatplotlibPlots = lsstDebug.Info(__name__).keepMatplotlibPlots
205  displayPsfSpatialModel = lsstDebug.Info(__name__).displayPsfSpatialModel # Plot spatial model?
206  showBadCandidates = lsstDebug.Info(__name__).showBadCandidates # Include bad candidates
207  # Normalize residuals by object amplitude
208  normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals
209  pause = lsstDebug.Info(__name__).pause # Prompt user after each iteration?
210 
211  if display > 1:
212  pause = True
213 
214  mi = exposure.getMaskedImage()
215 
216  if len(psfCandidateList) == 0:
217  raise RuntimeError("No PSF candidates supplied.")
218 
219  # construct and populate a spatial cell set
220  bbox = mi.getBBox()
221  psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY)
222  sizes = []
223  for i, psfCandidate in enumerate(psfCandidateList):
224  if psfCandidate.getSource().getPsfFluxFlag(): # bad measurement
225  continue
226 
227  try:
228  psfCellSet.insertCandidate(psfCandidate)
229  except Exception as e:
230  self.log.debug("Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
231  continue
232  source = psfCandidate.getSource()
233 
234  quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
235  axes = afwEll.Axes(quad)
236  sizes.append(axes.getA())
237  if len(sizes) == 0:
238  raise RuntimeError("No usable PSF candidates supplied")
239  nEigenComponents = self.config.nEigenComponents # initial version
240 
241  if self.config.kernelSize >= 15:
242  self.log.warn("WARNING: NOT scaling kernelSize by stellar quadrupole moment " +
243  "because config.kernelSize=%s >= 15; using config.kernelSize as as the width, instead",
244  self.config.kernelSize)
245  actualKernelSize = int(self.config.kernelSize)
246  else:
247  medSize = numpy.median(sizes)
248  actualKernelSize = 2 * int(self.config.kernelSize * math.sqrt(medSize) + 0.5) + 1
249  if actualKernelSize < self.config.kernelSizeMin:
250  actualKernelSize = self.config.kernelSizeMin
251  if actualKernelSize > self.config.kernelSizeMax:
252  actualKernelSize = self.config.kernelSizeMax
253 
254  if display:
255  print("Median size=%s" % (medSize,))
256  self.log.trace("Kernel size=%s", actualKernelSize)
257 
258  # Set size of image returned around candidate
259  psfCandidateList[0].setHeight(actualKernelSize)
260  psfCandidateList[0].setWidth(actualKernelSize)
261 
262  if self.config.doRejectBlends:
263  # Remove blended candidates completely
264  blendedCandidates = [] # Candidates to remove; can't do it while iterating
265  for cell, cand in candidatesIter(psfCellSet, False):
266  if len(cand.getSource().getFootprint().getPeaks()) > 1:
267  blendedCandidates.append((cell, cand))
268  continue
269  if display:
270  print("Removing %d blended Psf candidates" % len(blendedCandidates))
271  for cell, cand in blendedCandidates:
272  cell.removeCandidate(cand)
273  if sum(1 for cand in candidatesIter(psfCellSet, False)) == 0:
274  raise RuntimeError("All PSF candidates removed as blends")
275 
276  if display:
277  frame = 0
278  if displayExposure:
279  ds9.mtv(exposure, frame=frame, title="psf determination")
280  maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell,
281  symb="o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
282  size=4, frame=frame)
283 
284  #
285  # Do a PCA decomposition of those PSF candidates
286  #
287  reply = "y" # used in interactive mode
288  for iter in range(self.config.nIterForPsf):
289  if display and displayPsfCandidates: # Show a mosaic of usable PSF candidates
290  #
291  import lsst.afw.display.utils as displayUtils
292 
293  stamps = []
294  for cell in psfCellSet.getCellList():
295  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
296  cand = algorithmsLib.cast_PsfCandidateF(cand)
297 
298  try:
299  im = cand.getMaskedImage()
300 
301  chi2 = cand.getChi2()
302  if chi2 > 1e100:
303  chi2 = numpy.nan
304 
305  stamps.append((im, "%d%s" %
306  (maUtils.splitId(cand.getSource().getId(), True)["objId"], chi2),
307  cand.getStatus()))
308  except Exception as e:
309  continue
310 
311  if len(stamps) == 0:
312  print("WARNING: No PSF candidates to show; try setting showBadCandidates=True")
313  else:
314  mos = displayUtils.Mosaic()
315  for im, label, status in stamps:
316  im = type(im)(im, True)
317  try:
318  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
319  except NotImplementedError:
320  pass
321 
322  mos.append(im, label,
323  ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
324  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
325 
326  mos.makeMosaic(frame=8, title="Psf Candidates")
327 
328  # Re-fit until we don't have any candidates with naughty chi^2 values influencing the fit
329  cleanChi2 = False # Any naughty (negative/NAN) chi^2 values?
330  while not cleanChi2:
331  cleanChi2 = True
332  #
333  # First, estimate the PSF
334  #
335  psf, eigenValues, nEigenComponents, fitChi2 = \
336  self._fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
337  #
338  # In clipping, allow all candidates to be innocent until proven guilty on this iteration.
339  # Throw out any prima facie guilty candidates (naughty chi^2 values)
340  #
341  for cell in psfCellSet.getCellList():
342  awfulCandidates = []
343  for cand in cell.begin(False): # include bad candidates
344  cand = algorithmsLib.cast_PsfCandidateF(cand)
345  cand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN) # until proven guilty
346  rchi2 = cand.getChi2()
347  if not numpy.isfinite(rchi2) or rchi2 <= 0:
348  # Guilty prima facie
349  awfulCandidates.append(cand)
350  cleanChi2 = False
351  self.log.debug("chi^2=%s; id=%s",
352  cand.getChi2(), cand.getSource().getId())
353  for cand in awfulCandidates:
354  if display:
355  print("Removing bad candidate: id=%d, chi^2=%f" % \
356  (cand.getSource().getId(), cand.getChi2()))
357  cell.removeCandidate(cand)
358 
359  #
360  # Clip out bad fits based on reduced chi^2
361  #
362  badCandidates = list()
363  for cell in psfCellSet.getCellList():
364  for cand in cell.begin(False): # include bad candidates
365  cand = algorithmsLib.cast_PsfCandidateF(cand)
366  rchi2 = cand.getChi2() # reduced chi^2 when fitting PSF to candidate
367  assert rchi2 > 0
368  if rchi2 > self.config.reducedChi2ForPsfCandidates:
369  badCandidates.append(cand)
370 
371  badCandidates.sort(key=lambda x: x.getChi2(), reverse=True)
372  numBad = int(len(badCandidates) * (iter + 1) / self.config.nIterForPsf + 0.5)
373  for i, c in zip(range(numBad), badCandidates):
374  if display:
375  chi2 = c.getChi2()
376  if chi2 > 1e100:
377  chi2 = numpy.nan
378 
379  print("Chi^2 clipping %-4d %.2g" % (c.getSource().getId(), chi2))
380  c.setStatus(afwMath.SpatialCellCandidate.BAD)
381 
382  #
383  # Clip out bad fits based on spatial fitting.
384  #
385  # This appears to be better at getting rid of sources that have a single dominant kernel component
386  # (other than the zeroth; e.g., a nearby contaminant) because the surrounding sources (which help
387  # set the spatial model) don't contain that kernel component, and so the spatial modeling
388  # downweights the component.
389  #
390 
391  residuals = list()
392  candidates = list()
393  kernel = psf.getKernel()
394  noSpatialKernel = afwMath.cast_LinearCombinationKernel(psf.getKernel())
395  for cell in psfCellSet.getCellList():
396  for cand in cell.begin(False):
397  cand = algorithmsLib.cast_PsfCandidateF(cand)
398  candCenter = afwGeom.PointD(cand.getXCenter(), cand.getYCenter())
399  try:
400  im = cand.getMaskedImage(kernel.getWidth(), kernel.getHeight())
401  except Exception as e:
402  continue
403 
404  fit = algorithmsLib.fitKernelParamsToImage(noSpatialKernel, im, candCenter)
405  params = fit[0]
406  kernels = fit[1]
407  amp = 0.0
408  for p, k in zip(params, kernels):
409  amp += p * afwMath.cast_FixedKernel(k).getSum()
410 
411  predict = [kernel.getSpatialFunction(k)(candCenter.getX(), candCenter.getY()) for
412  k in range(kernel.getNKernelParameters())]
413 
414  #print cand.getSource().getId(), [a / amp for a in params], predict
415 
416  residuals.append([a / amp - p for a, p in zip(params, predict)])
417  candidates.append(cand)
418 
419  residuals = numpy.array(residuals)
420 
421  for k in range(kernel.getNKernelParameters()):
422  if False:
423  # Straight standard deviation
424  mean = residuals[:, k].mean()
425  rms = residuals[:, k].std()
426  elif False:
427  # Using interquartile range
428  sr = numpy.sort(residuals[:, k])
429  mean = sr[int(0.5*len(sr))] if len(sr) % 2 else \
430  0.5 * (sr[int(0.5*len(sr))] + sr[int(0.5*len(sr))+1])
431  rms = 0.74 * (sr[int(0.75*len(sr))] - sr[int(0.25*len(sr))])
432  else:
433  stats = afwMath.makeStatistics(residuals[:, k], afwMath.MEANCLIP | afwMath.STDEVCLIP)
434  mean = stats.getValue(afwMath.MEANCLIP)
435  rms = stats.getValue(afwMath.STDEVCLIP)
436 
437  rms = max(1.0e-4, rms) # Don't trust RMS below this due to numerical issues
438 
439  if display:
440  print("Mean for component %d is %f" % (k, mean))
441  print("RMS for component %d is %f" % (k, rms))
442  badCandidates = list()
443  for i, cand in enumerate(candidates):
444  if numpy.fabs(residuals[i, k] - mean) > self.config.spatialReject * rms:
445  badCandidates.append(i)
446 
447  badCandidates.sort(key=lambda x: numpy.fabs(residuals[x, k] - mean), reverse=True)
448 
449  numBad = int(len(badCandidates) * (iter + 1) / self.config.nIterForPsf + 0.5)
450 
451  for i, c in zip(range(min(len(badCandidates), numBad)), badCandidates):
452  cand = candidates[c]
453  if display:
454  print("Spatial clipping %d (%f,%f) based on %d: %f vs %f" % \
455  (cand.getSource().getId(), cand.getXCenter(), cand.getYCenter(), k,
456  residuals[badCandidates[i], k], self.config.spatialReject * rms))
457  cand.setStatus(afwMath.SpatialCellCandidate.BAD)
458 
459  #
460  # Display results
461  #
462  if display and displayIterations:
463  if displayExposure:
464  if iter > 0:
465  ds9.erase(frame=frame)
466  maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, showChi2=True,
467  symb="o", size=8, frame=frame,
468  ctype=ds9.YELLOW, ctypeBad=ds9.RED, ctypeUnused=ds9.MAGENTA)
469  if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
470  maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
471  symb="o", size=10, frame=frame,
472  ctype=ds9.YELLOW, ctypeBad=ds9.RED)
473  if displayResiduals:
474  while True:
475  try:
476  maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
477  normalize=normalizeResiduals,
478  showBadCandidates=showBadCandidates)
479  maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=5,
480  normalize=normalizeResiduals,
481  showBadCandidates=showBadCandidates,
482  variance=True)
483  except:
484  if not showBadCandidates:
485  showBadCandidates = True
486  continue
487  break
488 
489  if displayPsfComponents:
490  maUtils.showPsf(psf, eigenValues, frame=6)
491  if displayPsfMosaic:
492  maUtils.showPsfMosaic(exposure, psf, frame=7, showFwhm=True)
493  ds9.scale('linear', 0, 1, frame=7)
494  if displayPsfSpatialModel:
495  maUtils.plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True,
496  matchKernelAmplitudes=matchKernelAmplitudes,
497  keepPlots=keepMatplotlibPlots)
498 
499  if pause:
500  while True:
501  try:
502  reply = input("Next iteration? [ynchpqQs] ").strip()
503  except EOFError:
504  reply = "n"
505 
506  reply = reply.split()
507  if reply:
508  reply, args = reply[0], reply[1:]
509  else:
510  reply = ""
511 
512  if reply in ("", "c", "h", "n", "p", "q", "Q", "s", "y"):
513  if reply == "c":
514  pause = False
515  elif reply == "h":
516  print("c[ontinue without prompting] h[elp] n[o] p[db] q[uit displaying] " \
517  "s[ave fileName] y[es]")
518  continue
519  elif reply == "p":
520  import pdb
521  pdb.set_trace()
522  elif reply == "q":
523  display = False
524  elif reply == "Q":
525  sys.exit(1)
526  elif reply == "s":
527  fileName = args.pop(0)
528  if not fileName:
529  print("Please provide a filename")
530  continue
531 
532  print("Saving to %s" % fileName)
533  maUtils.saveSpatialCellSet(psfCellSet, fileName=fileName)
534  continue
535  break
536  else:
537  print("Unrecognised response: %s" % reply, file=sys.stderr)
538 
539  if reply == "n":
540  break
541 
542  # One last time, to take advantage of the last iteration
543  psf, eigenValues, nEigenComponents, fitChi2 = \
544  self._fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
545 
546  #
547  # Display code for debugging
548  #
549  if display and reply != "n":
550  if displayExposure:
551  maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, showChi2=True,
552  symb="o", ctype=ds9.YELLOW, ctypeBad=ds9.RED, size=8, frame=frame)
553  if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
554  maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
555  symb="o", ctype=ds9.YELLOW, ctypeBad=ds9.RED,
556  size=10, frame=frame)
557  if displayResiduals:
558  maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
559  normalize=normalizeResiduals,
560  showBadCandidates=showBadCandidates)
561 
562  if displayPsfComponents:
563  maUtils.showPsf(psf, eigenValues, frame=6)
564 
565  if displayPsfMosaic:
566  maUtils.showPsfMosaic(exposure, psf, frame=7, showFwhm=True)
567  ds9.scale("linear", 0, 1, frame=7)
568  if displayPsfSpatialModel:
569  maUtils.plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True,
570  matchKernelAmplitudes=matchKernelAmplitudes,
571  keepPlots=keepMatplotlibPlots)
572  #
573  # Generate some QA information
574  #
575  # Count PSF stars
576  #
577  numGoodStars = 0
578  numAvailStars = 0
579 
580  avgX = 0.0
581  avgY = 0.0
582 
583  for cell in psfCellSet.getCellList():
584  for cand in cell.begin(False): # don't ignore BAD stars
585  numAvailStars += 1
586 
587  for cand in cell.begin(True): # do ignore BAD stars
588  cand = algorithmsLib.cast_PsfCandidateF(cand)
589  src = cand.getSource()
590  if flagKey is not None:
591  src.set(flagKey, True)
592  avgX += src.getX()
593  avgY += src.getY()
594  numGoodStars += 1
595 
596  avgX /= numGoodStars
597  avgY /= numGoodStars
598 
599  if metadata is not None:
600  metadata.set("spatialFitChi2", fitChi2)
601  metadata.set("numGoodStars", numGoodStars)
602  metadata.set("numAvailStars", numAvailStars)
603  metadata.set("avgX", avgX)
604  metadata.set("avgY", avgY)
605 
606  psf = algorithmsLib.PcaPsf(psf.getKernel(), afwGeom.Point2D(avgX, avgY))
607 
608  return psf, psfCellSet
609 
610 
611 def candidatesIter(psfCellSet, ignoreBad=True):
612  """!Generator for Psf candidates
613 
614  This allows two 'for' loops to be reduced to one.
615 
616  \param psfCellSet SpatialCellSet of PSF candidates
617  \param ignoreBad Ignore candidates flagged as BAD?
618  \return SpatialCell, PsfCandidate
619  """
620  for cell in psfCellSet.getCellList():
621  for cand in cell.begin(ignoreBad):
622  yield (cell, algorithmsLib.cast_PsfCandidateF(cand))
623 
624 psfDeterminerRegistry.register("pca", PcaPsfDeterminerTask)
STL namespace.
A collection of SpatialCells covering an entire image.
Definition: SpatialCell.h:378
def determinePsf
Determine a PCA PSF model for an exposure given a list of PSF candidates.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1107
def candidatesIter
Generator for Psf candidates.