192 def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
193 """Determine a PCA PSF model for an exposure given a list of PSF candidates.
197 exposure : `lsst.afw.image.Exposure`
198 Exposure containing the psf candidates.
199 psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate`
200 A sequence of PSF candidates typically obtained by detecting sources
201 and then running them through a star selector.
202 metadata : `lsst.daf.base import PropertyList` or `None`, optional
203 A home for interesting tidbits of information.
204 flagKey : `str`, optional
205 Schema key used to mark sources actually used in PSF determination.
209 psf : `lsst.meas.algorithms.PcaPsf`
211 psfCellSet : `lsst.afw.math.SpatialCellSet`
217 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
219 displayPsfComponents =
lsstDebug.Info(__name__).displayPsfComponents
223 matchKernelAmplitudes =
lsstDebug.Info(__name__).matchKernelAmplitudes
225 keepMatplotlibPlots =
lsstDebug.Info(__name__).keepMatplotlibPlots
226 displayPsfSpatialModel =
lsstDebug.Info(__name__).displayPsfSpatialModel
233 afwDisplay.setDefaultMaskTransparency(75)
237 mi = exposure.getMaskedImage()
239 if len(psfCandidateList) == 0:
240 raise RuntimeError(
"No PSF candidates supplied.")
244 psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY)
246 for i, psfCandidate
in enumerate(psfCandidateList):
247 if psfCandidate.getSource().getPsfFluxFlag():
251 psfCellSet.insertCandidate(psfCandidate)
252 except Exception
as e:
253 self.log.debug(
"Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
255 source = psfCandidate.getSource()
257 quad = afwGeom.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
258 axes = afwEll.Axes(quad)
259 sizes.append(axes.getA())
261 raise RuntimeError(
"No usable PSF candidates supplied")
262 nEigenComponents = self.config.nEigenComponents
264 actualKernelSize = int(self.config.stampSize)
267 print(
"Median size=%s" % (numpy.median(sizes),))
269 self.log.trace(
"Kernel size=%s", actualKernelSize)
271 if actualKernelSize > psfCandidateList[0].getWidth():
272 self.log.warning(
"Using a region (%d x %d) larger than kernelSize (%d) set while making PSF "
273 "candidates. Consider setting a larger value for kernelSize for "
274 "`makePsfCandidates` to avoid this warning.",
275 actualKernelSize, actualKernelSize, psfCandidateList[0].getWidth())
277 if self.config.doRejectBlends:
279 blendedCandidates = []
281 if len(cand.getSource().getFootprint().getPeaks()) > 1:
282 blendedCandidates.append((cell, cand))
285 print(
"Removing %d blended Psf candidates" % len(blendedCandidates))
286 for cell, cand
in blendedCandidates:
287 cell.removeCandidate(cand)
289 raise RuntimeError(
"All PSF candidates removed as blends")
293 disp = afwDisplay.Display(frame=0)
294 disp.mtv(exposure, title=
"psf determination")
295 utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, symb=
"o",
296 ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
297 size=4, display=disp)
303 for iterNum
in range(self.config.nIterForPsf):
304 if display
and displayPsfCandidates:
307 for cell
in psfCellSet.getCellList():
308 for cand
in cell.begin(
not showBadCandidates):
310 im = cand.getMaskedImage()
312 chi2 = cand.getChi2()
316 stamps.append((im,
"%d%s" %
317 (utils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
323 print(
"WARNING: No PSF candidates to show; try setting showBadCandidates=True")
325 mos = afwDisplay.utils.Mosaic()
326 for im, label, status
in stamps:
327 im = type(im)(im,
True)
329 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
330 except NotImplementedError:
333 mos.append(im, label,
334 (afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
335 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else
338 disp8 = afwDisplay.Display(frame=8)
339 mos.makeMosaic(display=disp8, title=
"Psf Candidates")
348 psf, eigenValues, nEigenComponents, fitChi2 = \
349 self.
_fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
354 for cell
in psfCellSet.getCellList():
356 for cand
in cell.begin(
False):
357 cand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN)
358 rchi2 = cand.getChi2()
359 if not numpy.isfinite(rchi2)
or rchi2 <= 0:
361 awfulCandidates.append(cand)
363 self.log.debug(
"chi^2=%s; id=%s",
364 cand.getChi2(), cand.getSource().getId())
365 for cand
in awfulCandidates:
367 print(
"Removing bad candidate: id=%d, chi^2=%f" %
368 (cand.getSource().getId(), cand.getChi2()))
369 cell.removeCandidate(cand)
374 badCandidates = list()
375 for cell
in psfCellSet.getCellList():
376 for cand
in cell.begin(
False):
377 rchi2 = cand.getChi2()
379 if rchi2 > self.config.reducedChi2ForPsfCandidates:
380 badCandidates.append(cand)
382 badCandidates.sort(key=
lambda x: x.getChi2(), reverse=
True)
384 self.config.nIterForPsf)
385 for i, c
in zip(range(numBad), badCandidates):
391 print(
"Chi^2 clipping %-4d %.2g" % (c.getSource().getId(), chi2))
392 c.setStatus(afwMath.SpatialCellCandidate.BAD)
405 kernel = psf.getKernel()
406 noSpatialKernel = psf.getKernel()
407 for cell
in psfCellSet.getCellList():
408 for cand
in cell.begin(
False):
411 im = cand.getMaskedImage(actualKernelSize, actualKernelSize)
415 fit = fitKernelParamsToImage(noSpatialKernel, im, candCenter)
419 for p, k
in zip(params, kernels):
422 predict = [kernel.getSpatialFunction(k)(candCenter.getX(), candCenter.getY())
for
423 k
in range(kernel.getNKernelParameters())]
425 residuals.append([a/amp - p
for a, p
in zip(params, predict)])
426 candidates.append(cand)
428 residuals = numpy.array(residuals)
430 for k
in range(kernel.getNKernelParameters()):
433 mean = residuals[:, k].mean()
434 rms = residuals[:, k].
std()
437 sr = numpy.sort(residuals[:, k])
438 mean = (sr[int(0.5*len(sr))]
if len(sr)%2
else
439 0.5*(sr[int(0.5*len(sr))] + sr[int(0.5*len(sr)) + 1]))
440 rms = 0.74*(sr[int(0.75*len(sr))] - sr[int(0.25*len(sr))])
442 stats = afwMath.makeStatistics(residuals[:, k], afwMath.MEANCLIP | afwMath.STDEVCLIP)
443 mean = stats.getValue(afwMath.MEANCLIP)
444 rms = stats.getValue(afwMath.STDEVCLIP)
446 rms = max(1.0e-4, rms)
449 print(
"Mean for component %d is %f" % (k, mean))
450 print(
"RMS for component %d is %f" % (k, rms))
451 badCandidates = list()
452 for i, cand
in enumerate(candidates):
453 if numpy.fabs(residuals[i, k] - mean) > self.config.spatialReject*rms:
454 badCandidates.append(i)
456 badCandidates.sort(key=
lambda x: numpy.fabs(residuals[x, k] - mean), reverse=
True)
459 self.config.nIterForPsf)
461 for i, c
in zip(range(min(len(badCandidates), numBad)), badCandidates):
464 print(
"Spatial clipping %d (%f,%f) based on %d: %f vs %f" %
465 (cand.getSource().getId(), cand.getXCenter(), cand.getYCenter(), k,
466 residuals[badCandidates[i], k], self.config.spatialReject*rms))
467 cand.setStatus(afwMath.SpatialCellCandidate.BAD)
472 if display
and displayIterations:
476 utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, showChi2=
True,
477 symb=
"o", size=8, display=disp, ctype=afwDisplay.YELLOW,
478 ctypeBad=afwDisplay.RED, ctypeUnused=afwDisplay.MAGENTA)
479 if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
480 utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
481 symb=
"o", size=10, display=disp,
482 ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED)
486 disp4 = afwDisplay.Display(frame=4)
487 utils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
488 normalize=normalizeResiduals,
489 showBadCandidates=showBadCandidates)
490 disp5 = afwDisplay.Display(frame=5)
491 utils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp5,
492 normalize=normalizeResiduals,
493 showBadCandidates=showBadCandidates,
496 if not showBadCandidates:
497 showBadCandidates =
True
501 if displayPsfComponents:
502 disp6 = afwDisplay.Display(frame=6)
503 utils.showPsf(psf, eigenValues, display=disp6)
505 disp7 = afwDisplay.Display(frame=7)
506 utils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=
True)
507 disp7.scale(
'linear', 0, 1)
508 if displayPsfSpatialModel:
509 utils.plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=
True,
510 matchKernelAmplitudes=matchKernelAmplitudes,
511 keepPlots=keepMatplotlibPlots)
516 reply = input(
"Next iteration? [ynchpqQs] ").strip()
520 reply = reply.split()
522 reply, args = reply[0], reply[1:]
526 if reply
in (
"",
"c",
"h",
"n",
"p",
"q",
"Q",
"s",
"y"):
530 print(
"c[ontinue without prompting] h[elp] n[o] p[db] q[uit displaying] "
531 "s[ave fileName] y[es]")
541 fileName = args.pop(0)
543 print(
"Please provide a filename")
546 print(
"Saving to %s" % fileName)
547 utils.saveSpatialCellSet(psfCellSet, fileName=fileName)
551 print(
"Unrecognised response: %s" % reply, file=sys.stderr)
557 psf, eigenValues, nEigenComponents, fitChi2 = \
558 self.
_fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
563 if display
and reply !=
"n":
564 disp = afwDisplay.Display(frame=0)
566 utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, showChi2=
True,
567 symb=
"o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
568 size=8, display=disp)
569 if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
570 utils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
571 symb=
"o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
572 size=10, display=disp)
574 disp4 = afwDisplay.Display(frame=4)
575 utils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
576 normalize=normalizeResiduals,
577 showBadCandidates=showBadCandidates)
579 if displayPsfComponents:
580 disp6 = afwDisplay.Display(frame=6)
581 utils.showPsf(psf, eigenValues, display=disp6)
584 disp7 = afwDisplay.Display(frame=7)
585 utils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=
True)
586 disp7.scale(
"linear", 0, 1)
587 if displayPsfSpatialModel:
588 utils.plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=
True,
589 matchKernelAmplitudes=matchKernelAmplitudes,
590 keepPlots=keepMatplotlibPlots)
602 for cell
in psfCellSet.getCellList():
603 for cand
in cell.begin(
False):
606 for cand
in cell.begin(
True):
607 src = cand.getSource()
608 if flagKey
is not None:
609 src.set(flagKey,
True)
617 if metadata
is not None:
618 metadata[
"spatialFitChi2"] = fitChi2
619 metadata[
"numGoodStars"] = numGoodStars
620 metadata[
"numAvailStars"] = numAvailStars
621 metadata[
"avgX"] = avgX
622 metadata[
"avgY"] = avgY
626 return psf, psfCellSet