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
pcaPsfDeterminer.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 __all__ = ["PcaPsfDeterminerConfig", "PcaPsfDeterminerTask"]
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.geom
34 import lsst.afw.geom as afwGeom
35 import lsst.afw.geom.ellipses as afwEll
36 import lsst.afw.display.ds9 as ds9
37 import lsst.afw.math as afwMath
38 from .psfDeterminer import BasePsfDeterminerTask, psfDeterminerRegistry
39 from .psfCandidate import PsfCandidateF
40 from .spatialModelPsf import createKernelFromPsfCandidates, countPsfCandidates, \
41  fitSpatialKernelFromPsfCandidates, fitKernelParamsToImage
42 from .pcaPsf import PcaPsf
43 from . import utils
44 
45 
46 def numCandidatesToReject(numBadCandidates, numIter, totalIter):
47  """Return the number of PSF candidates to be rejected.
48 
49  The number of candidates being rejected on each iteration gradually
50  increases, so that on the Nth of M iterations we reject N/M of the bad
51  candidates.
52 
53  Parameters
54  ----------
55  numBadCandidates : int
56  Number of bad candidates under consideration.
57 
58  numIter : int
59  The number of the current PSF iteration.
60 
61  totalIter : int
62  The total number of PSF iterations.
63 
64  Returns
65  -------
66  int
67  Number of candidates to reject.
68  """
69  return int(numBadCandidates * (numIter + 1) // totalIter + 0.5)
70 
71 
72 class PcaPsfDeterminerConfig(BasePsfDeterminerTask.ConfigClass):
73  nonLinearSpatialFit = pexConfig.Field(
74  doc="Use non-linear fitter for spatial variation of Kernel",
75  dtype=bool,
76  default=False,
77  )
78  nEigenComponents = pexConfig.Field(
79  doc="number of eigen components for PSF kernel creation",
80  dtype=int,
81  default=4,
82  )
83  spatialOrder = pexConfig.Field(
84  doc="specify spatial order for PSF kernel creation",
85  dtype=int,
86  default=2,
87  )
88  sizeCellX = pexConfig.Field(
89  doc="size of cell used to determine PSF (pixels, column direction)",
90  dtype=int,
91  default=256,
92  # minValue = 10,
93  check=lambda x: x >= 10,
94  )
95  sizeCellY = pexConfig.Field(
96  doc="size of cell used to determine PSF (pixels, row direction)",
97  dtype=int,
98  default=sizeCellX.default,
99  # minValue = 10,
100  check=lambda x: x >= 10,
101  )
102  nStarPerCell = pexConfig.Field(
103  doc="number of stars per psf cell for PSF kernel creation",
104  dtype=int,
105  default=3,
106  )
107  borderWidth = pexConfig.Field(
108  doc="Number of pixels to ignore around the edge of PSF candidate postage stamps",
109  dtype=int,
110  default=0,
111  )
112  nStarPerCellSpatialFit = pexConfig.Field(
113  doc="number of stars per psf Cell for spatial fitting",
114  dtype=int,
115  default=5,
116  )
117  constantWeight = pexConfig.Field(
118  doc="Should each PSF candidate be given the same weight, independent of magnitude?",
119  dtype=bool,
120  default=True,
121  )
122  nIterForPsf = pexConfig.Field(
123  doc="number of iterations of PSF candidate star list",
124  dtype=int,
125  default=3,
126  )
127  tolerance = pexConfig.Field(
128  doc="tolerance of spatial fitting",
129  dtype=float,
130  default=1e-2,
131  )
132  lam = pexConfig.Field(
133  doc="floor for variance is lam*data",
134  dtype=float,
135  default=0.05,
136  )
137  reducedChi2ForPsfCandidates = pexConfig.Field(
138  doc="for psf candidate evaluation",
139  dtype=float,
140  default=2.0,
141  )
142  spatialReject = pexConfig.Field(
143  doc="Rejection threshold (stdev) for candidates based on spatial fit",
144  dtype=float,
145  default=3.0,
146  )
147  pixelThreshold = pexConfig.Field(
148  doc="Threshold (stdev) for rejecting extraneous pixels around candidate; applied if positive",
149  dtype=float,
150  default=0.0,
151  )
152  doRejectBlends = pexConfig.Field(
153  doc="Reject candidates that are blended?",
154  dtype=bool,
155  default=False,
156  )
157  doMaskBlends = pexConfig.Field(
158  doc="Mask blends in image?",
159  dtype=bool,
160  default=True,
161  )
162 
163 
165  """!
166  A measurePsfTask psf estimator
167  """
168  ConfigClass = PcaPsfDeterminerConfig
169 
170  def _fitPsf(self, exposure, psfCellSet, kernelSize, nEigenComponents):
171  PsfCandidateF.setPixelThreshold(self.config.pixelThreshold)
172  PsfCandidateF.setMaskBlends(self.config.doMaskBlends)
173  #
174  # Loop trying to use nEigenComponents, but allowing smaller numbers if necessary
175  #
176  for nEigen in range(nEigenComponents, 0, -1):
177  # Determine KL components
178  try:
179  kernel, eigenValues = createKernelFromPsfCandidates(
180  psfCellSet, exposure.getDimensions(), exposure.getXY0(), nEigen,
181  self.config.spatialOrder, kernelSize, self.config.nStarPerCell,
182  bool(self.config.constantWeight))
183 
184  break # OK, we can get nEigen components
185  except pexExceptions.LengthError as e:
186  if nEigen == 1: # can't go any lower
187  raise IndexError("No viable PSF candidates survive")
188 
189  self.log.warn("%s: reducing number of eigen components" % e.what())
190  #
191  # We got our eigen decomposition so let's use it
192  #
193  # Express eigenValues in units of reduced chi^2 per star
194  size = kernelSize + 2*self.config.borderWidth
195  nu = size*size - 1 # number of degrees of freedom/star for chi^2
196  eigenValues = [l/float(countPsfCandidates(psfCellSet, self.config.nStarPerCell)*nu)
197  for l in eigenValues]
198 
199  # Fit spatial model
200  status, chi2 = fitSpatialKernelFromPsfCandidates(
201  kernel, psfCellSet, bool(self.config.nonLinearSpatialFit),
202  self.config.nStarPerCellSpatialFit, self.config.tolerance, self.config.lam)
203 
204  psf = PcaPsf(kernel)
205 
206  return psf, eigenValues, nEigen, chi2
207 
208  def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
209  """!Determine a PCA PSF model for an exposure given a list of PSF candidates
210 
211  @param[in] exposure exposure containing the psf candidates (lsst.afw.image.Exposure)
212  @param[in] psfCandidateList a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate);
213  typically obtained by detecting sources and then running them through a star selector
214  @param[in,out] metadata a home for interesting tidbits of information
215  @param[in] flagKey schema key used to mark sources actually used in PSF determination
216 
217  @return a list of
218  - psf: the measured PSF, an lsst.meas.algorithms.PcaPsf
219  - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
220  """
221  import lsstDebug
222  display = lsstDebug.Info(__name__).display
223  displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells
224  displayPsfCandidates = lsstDebug.Info(__name__).displayPsfCandidates # show the viable candidates
225  displayIterations = lsstDebug.Info(__name__).displayIterations # display on each PSF iteration
226  displayPsfComponents = lsstDebug.Info(__name__).displayPsfComponents # show the PCA components
227  displayResiduals = lsstDebug.Info(__name__).displayResiduals # show residuals
228  displayPsfMosaic = lsstDebug.Info(__name__).displayPsfMosaic # show mosaic of reconstructed PSF(x,y)
229  # match Kernel amplitudes for spatial plots
230  matchKernelAmplitudes = lsstDebug.Info(__name__).matchKernelAmplitudes
231  # Keep matplotlib alive post mortem
232  keepMatplotlibPlots = lsstDebug.Info(__name__).keepMatplotlibPlots
233  displayPsfSpatialModel = lsstDebug.Info(__name__).displayPsfSpatialModel # Plot spatial model?
234  showBadCandidates = lsstDebug.Info(__name__).showBadCandidates # Include bad candidates
235  # Normalize residuals by object amplitude
236  normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals
237  pause = lsstDebug.Info(__name__).pause # Prompt user after each iteration?
238 
239  if display > 1:
240  pause = True
241 
242  mi = exposure.getMaskedImage()
243 
244  if len(psfCandidateList) == 0:
245  raise RuntimeError("No PSF candidates supplied.")
246 
247  # construct and populate a spatial cell set
248  bbox = mi.getBBox()
249  psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY)
250  sizes = []
251  for i, psfCandidate in enumerate(psfCandidateList):
252  if psfCandidate.getSource().getPsfFluxFlag(): # bad measurement
253  continue
254 
255  try:
256  psfCellSet.insertCandidate(psfCandidate)
257  except Exception as e:
258  self.log.debug("Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
259  continue
260  source = psfCandidate.getSource()
261 
262  quad = afwGeom.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
263  axes = afwEll.Axes(quad)
264  sizes.append(axes.getA())
265  if len(sizes) == 0:
266  raise RuntimeError("No usable PSF candidates supplied")
267  nEigenComponents = self.config.nEigenComponents # initial version
268 
269  if self.config.kernelSize >= 15:
270  self.log.warn("WARNING: NOT scaling kernelSize by stellar quadrupole moment "
271  "because config.kernelSize=%s >= 15; "
272  "using config.kernelSize as as the width, instead",
273  self.config.kernelSize)
274  actualKernelSize = int(self.config.kernelSize)
275  else:
276  medSize = numpy.median(sizes)
277  actualKernelSize = 2 * int(self.config.kernelSize * math.sqrt(medSize) + 0.5) + 1
278  if actualKernelSize < self.config.kernelSizeMin:
279  actualKernelSize = self.config.kernelSizeMin
280  if actualKernelSize > self.config.kernelSizeMax:
281  actualKernelSize = self.config.kernelSizeMax
282 
283  if display:
284  print("Median size=%s" % (medSize,))
285  self.log.trace("Kernel size=%s", actualKernelSize)
286 
287  # Set size of image returned around candidate
288  psfCandidateList[0].setHeight(actualKernelSize)
289  psfCandidateList[0].setWidth(actualKernelSize)
290 
291  if self.config.doRejectBlends:
292  # Remove blended candidates completely
293  blendedCandidates = [] # Candidates to remove; can't do it while iterating
294  for cell, cand in candidatesIter(psfCellSet, False):
295  if len(cand.getSource().getFootprint().getPeaks()) > 1:
296  blendedCandidates.append((cell, cand))
297  continue
298  if display:
299  print("Removing %d blended Psf candidates" % len(blendedCandidates))
300  for cell, cand in blendedCandidates:
301  cell.removeCandidate(cand)
302  if sum(1 for cand in candidatesIter(psfCellSet, False)) == 0:
303  raise RuntimeError("All PSF candidates removed as blends")
304 
305  if display:
306  frame = 0
307  if displayExposure:
308  ds9.mtv(exposure, frame=frame, title="psf determination")
309  utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell,
310  symb="o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
311  size=4, frame=frame)
312 
313  #
314  # Do a PCA decomposition of those PSF candidates
315  #
316  reply = "y" # used in interactive mode
317  for iterNum in range(self.config.nIterForPsf):
318  if display and displayPsfCandidates: # Show a mosaic of usable PSF candidates
319  #
320  import lsst.afw.display.utils as displayUtils
321 
322  stamps = []
323  for cell in psfCellSet.getCellList():
324  for cand in cell.begin(not showBadCandidates): # maybe include bad candidates
325  try:
326  im = cand.getMaskedImage()
327 
328  chi2 = cand.getChi2()
329  if chi2 > 1e100:
330  chi2 = numpy.nan
331 
332  stamps.append((im, "%d%s" %
333  (utils.splitId(cand.getSource().getId(), True)["objId"], chi2),
334  cand.getStatus()))
335  except Exception:
336  continue
337 
338  if len(stamps) == 0:
339  print("WARNING: No PSF candidates to show; try setting showBadCandidates=True")
340  else:
341  mos = displayUtils.Mosaic()
342  for im, label, status in stamps:
343  im = type(im)(im, True)
344  try:
345  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
346  except NotImplementedError:
347  pass
348 
349  mos.append(im, label,
350  ds9.GREEN if status == afwMath.SpatialCellCandidate.GOOD else
351  ds9.YELLOW if status == afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)
352 
353  mos.makeMosaic(frame=8, title="Psf Candidates")
354 
355  # Re-fit until we don't have any candidates with naughty chi^2 values influencing the fit
356  cleanChi2 = False # Any naughty (negative/NAN) chi^2 values?
357  while not cleanChi2:
358  cleanChi2 = True
359  #
360  # First, estimate the PSF
361  #
362  psf, eigenValues, nEigenComponents, fitChi2 = \
363  self._fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
364  #
365  # In clipping, allow all candidates to be innocent until proven guilty on this iteration.
366  # Throw out any prima facie guilty candidates (naughty chi^2 values)
367  #
368  for cell in psfCellSet.getCellList():
369  awfulCandidates = []
370  for cand in cell.begin(False): # include bad candidates
371  cand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN) # until proven guilty
372  rchi2 = cand.getChi2()
373  if not numpy.isfinite(rchi2) or rchi2 <= 0:
374  # Guilty prima facie
375  awfulCandidates.append(cand)
376  cleanChi2 = False
377  self.log.debug("chi^2=%s; id=%s",
378  cand.getChi2(), cand.getSource().getId())
379  for cand in awfulCandidates:
380  if display:
381  print("Removing bad candidate: id=%d, chi^2=%f" %
382  (cand.getSource().getId(), cand.getChi2()))
383  cell.removeCandidate(cand)
384 
385  #
386  # Clip out bad fits based on reduced chi^2
387  #
388  badCandidates = list()
389  for cell in psfCellSet.getCellList():
390  for cand in cell.begin(False): # include bad candidates
391  rchi2 = cand.getChi2() # reduced chi^2 when fitting PSF to candidate
392  assert rchi2 > 0
393  if rchi2 > self.config.reducedChi2ForPsfCandidates:
394  badCandidates.append(cand)
395 
396  badCandidates.sort(key=lambda x: x.getChi2(), reverse=True)
397  numBad = numCandidatesToReject(len(badCandidates), iterNum,
398  self.config.nIterForPsf)
399  for i, c in zip(range(numBad), badCandidates):
400  if display:
401  chi2 = c.getChi2()
402  if chi2 > 1e100:
403  chi2 = numpy.nan
404 
405  print("Chi^2 clipping %-4d %.2g" % (c.getSource().getId(), chi2))
406  c.setStatus(afwMath.SpatialCellCandidate.BAD)
407 
408  #
409  # Clip out bad fits based on spatial fitting.
410  #
411  # This appears to be better at getting rid of sources that have a single dominant kernel component
412  # (other than the zeroth; e.g., a nearby contaminant) because the surrounding sources (which help
413  # set the spatial model) don't contain that kernel component, and so the spatial modeling
414  # downweights the component.
415  #
416 
417  residuals = list()
418  candidates = list()
419  kernel = psf.getKernel()
420  noSpatialKernel = psf.getKernel()
421  for cell in psfCellSet.getCellList():
422  for cand in cell.begin(False):
423  candCenter = lsst.geom.PointD(cand.getXCenter(), cand.getYCenter())
424  try:
425  im = cand.getMaskedImage(kernel.getWidth(), kernel.getHeight())
426  except Exception:
427  continue
428 
429  fit = 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 * 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  utils.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  utils.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  utils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
503  normalize=normalizeResiduals,
504  showBadCandidates=showBadCandidates)
505  utils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=5,
506  normalize=normalizeResiduals,
507  showBadCandidates=showBadCandidates,
508  variance=True)
509  except Exception:
510  if not showBadCandidates:
511  showBadCandidates = True
512  continue
513  break
514 
515  if displayPsfComponents:
516  utils.showPsf(psf, eigenValues, frame=6)
517  if displayPsfMosaic:
518  utils.showPsfMosaic(exposure, psf, frame=7, showFwhm=True)
519  ds9.scale('linear', 0, 1, frame=7)
520  if displayPsfSpatialModel:
521  utils.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  utils.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  utils.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  utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
581  symb="o", ctype=ds9.YELLOW, ctypeBad=ds9.RED,
582  size=10, frame=frame)
583  if displayResiduals:
584  utils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
585  normalize=normalizeResiduals,
586  showBadCandidates=showBadCandidates)
587 
588  if displayPsfComponents:
589  utils.showPsf(psf, eigenValues, frame=6)
590 
591  if displayPsfMosaic:
592  utils.showPsfMosaic(exposure, psf, frame=7, showFwhm=True)
593  ds9.scale("linear", 0, 1, frame=7)
594  if displayPsfSpatialModel:
595  utils.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  src = cand.getSource()
615  if flagKey is not None:
616  src.set(flagKey, True)
617  avgX += src.getX()
618  avgY += src.getY()
619  numGoodStars += 1
620 
621  avgX /= numGoodStars
622  avgY /= numGoodStars
623 
624  if metadata is not None:
625  metadata.set("spatialFitChi2", fitChi2)
626  metadata.set("numGoodStars", numGoodStars)
627  metadata.set("numAvailStars", numAvailStars)
628  metadata.set("avgX", avgX)
629  metadata.set("avgY", avgY)
630 
631  psf = PcaPsf(psf.getKernel(), lsst.geom.Point2D(avgX, avgY))
632 
633  return psf, psfCellSet
634 
635 
636 def candidatesIter(psfCellSet, ignoreBad=True):
637  """!Generator for Psf candidates
638 
639  This allows two 'for' loops to be reduced to one.
640 
641  @param psfCellSet SpatialCellSet of PSF candidates
642  @param ignoreBad Ignore candidates flagged as BAD?
643  @return SpatialCell, PsfCandidate
644  """
645  for cell in psfCellSet.getCellList():
646  for cand in cell.begin(ignoreBad):
647  yield (cell, cand)
648 
649 
650 psfDeterminerRegistry.register("pca", PcaPsfDeterminerTask)
An ellipse core with quadrupole moments as parameters.
Definition: Quadrupole.h:47
def numCandidatesToReject(numBadCandidates, numIter, totalIter)
int countPsfCandidates(afw::math::SpatialCellSet const &psfCells, int const nStarPerCell=-1)
Count the number of candidates in use.
Reports attempts to exceed implementation-defined length limits for some classes. ...
Definition: Runtime.h:76
STL namespace.
def candidatesIter(psfCellSet, ignoreBad=True)
Generator for Psf candidates.
int min
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
std::pair< bool, double > fitSpatialKernelFromPsfCandidates(afw::math::Kernel *kernel, afw::math::SpatialCellSet const &psfCells, int const nStarPerCell=-1, double const tolerance=1e-5, double const lambda=0.0)
Fit spatial kernel using full-nonlinear optimization to estimate candidate amplitudes.
A collection of SpatialCells covering an entire image.
Definition: SpatialCell.h:387
table::Key< int > type
Definition: Detector.cc:164
def _fitPsf(self, exposure, psfCellSet, kernelSize, nEigenComponents)
int max
std::pair< std::shared_ptr< afw::math::LinearCombinationKernel >, std::vector< double > > createKernelFromPsfCandidates(afw::math::SpatialCellSet const &psfCells, geom::Extent2I const &dims, geom::Point2I const &xy0, int const nEigenComponents, int const spatialOrder, int const ksize, int const nStarPerCell=-1, bool const constantWeight=true, int const border=3)
Return a Kernel pointer and a list of eigenvalues resulting from analysing the provided SpatialCellSe...
def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None)
Determine a PCA PSF model for an exposure given a list of PSF candidates.
daf::base::PropertyList * list
Definition: fits.cc:833
std::pair< std::vector< double >, afw::math::KernelList > fitKernelParamsToImage(afw::math::LinearCombinationKernel const &kernel, Image const &image, geom::Point2D const &pos)
Fit a LinearCombinationKernel to an Image, allowing the coefficients of the components to vary...
bool strip
Definition: fits.cc:831