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