34 from .
import algorithmsLib
35 from .
import utils
as maUtils
38 nonLinearSpatialFit = pexConfig.Field(
39 doc =
"Use non-linear fitter for spatial variation of Kernel",
43 nEigenComponents = pexConfig.Field(
44 doc =
"number of eigen components for PSF kernel creation",
48 spatialOrder = pexConfig.Field(
49 doc =
"specify spatial order for PSF kernel creation",
53 sizeCellX = pexConfig.Field(
54 doc =
"size of cell used to determine PSF (pixels, column direction)",
58 check =
lambda x: x >= 10,
60 sizeCellY = pexConfig.Field(
61 doc =
"size of cell used to determine PSF (pixels, row direction)",
63 default = sizeCellX.default,
65 check =
lambda x: x >= 10,
67 nStarPerCell = pexConfig.Field(
68 doc =
"number of stars per psf cell for PSF kernel creation",
72 kernelSize = pexConfig.Field(
73 doc =
"radius of the kernel to create, relative to the square root of the stellar quadrupole moments",
77 kernelSizeMin = pexConfig.Field(
78 doc =
"Minimum radius of the kernel",
82 kernelSizeMax = pexConfig.Field(
83 doc =
"Maximum radius of the kernel",
87 borderWidth = pexConfig.Field(
88 doc =
"Number of pixels to ignore around the edge of PSF candidate postage stamps",
92 nStarPerCellSpatialFit = pexConfig.Field(
93 doc =
"number of stars per psf Cell for spatial fitting",
97 constantWeight = pexConfig.Field(
98 doc =
"Should each PSF candidate be given the same weight, independent of magnitude?",
102 nIterForPsf = pexConfig.Field(
103 doc =
"number of iterations of PSF candidate star list",
107 tolerance = pexConfig.Field(
108 doc =
"tolerance of spatial fitting",
112 lam = pexConfig.Field(
113 doc =
"floor for variance is lam*data",
117 reducedChi2ForPsfCandidates = pexConfig.Field(
118 doc =
"for psf candidate evaluation",
122 spatialReject = pexConfig.Field(
123 doc =
"Rejection threshold (stdev) for candidates based on spatial fit",
127 pixelThreshold = pexConfig.Field(
128 doc =
"Threshold (stdev) for rejecting extraneous pixels around candidate; applied if positive",
132 doRejectBlends = pexConfig.Field(
133 doc =
"Reject candidates that are blended?",
137 doMaskBlends = pexConfig.Field(
138 doc =
"Mask blends in image?",
145 A measurePsfTask psf estimator
147 ConfigClass = PcaPsfDeterminerConfig
150 """!Construct a PCA PSF Fitter
152 \param[in] config instance of PcaPsfDeterminerConfig
160 def _fitPsf(self, exposure, psfCellSet, kernelSize, nEigenComponents):
161 algorithmsLib.PsfCandidateF.setPixelThreshold(self.config.pixelThreshold)
162 algorithmsLib.PsfCandidateF.setMaskBlends(self.config.doMaskBlends)
166 for nEigen
in range(nEigenComponents, 0, -1):
169 kernel, eigenValues = algorithmsLib.createKernelFromPsfCandidates(
170 psfCellSet, exposure.getDimensions(), exposure.getXY0(), nEigen,
171 self.config.spatialOrder, kernelSize, self.config.nStarPerCell,
172 bool(self.config.constantWeight))
175 except pexExceptions.LengthError
as e:
179 self.warnLog.log(pexLog.Log.WARN,
"%s: reducing number of eigen components" % e.what())
184 size = kernelSize + 2*self.config.borderWidth
186 eigenValues = [l/float(algorithmsLib.countPsfCandidates(psfCellSet, self.config.nStarPerCell)*nu)
187 for l
in eigenValues]
190 status, chi2 = algorithmsLib.fitSpatialKernelFromPsfCandidates(
191 kernel, psfCellSet, bool(self.config.nonLinearSpatialFit),
192 self.config.nStarPerCellSpatialFit, self.config.tolerance, self.config.lam)
194 psf = algorithmsLib.PcaPsf(kernel)
196 return psf, eigenValues, nEigen, chi2
199 def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
200 """!Determine a PCA PSF model for an exposure given a list of PSF candidates
202 \param[in] exposure exposure containing the psf candidates (lsst.afw.image.Exposure)
203 \param[in] psfCandidateList a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate);
204 typically obtained by detecting sources and then running them through a star selector
205 \param[in,out] metadata a home for interesting tidbits of information
206 \param[in] flagKey schema key used to mark sources actually used in PSF determination
209 - psf: the measured PSF, an lsst.meas.algorithms.PcaPsf
210 - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
215 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
217 displayPsfComponents =
lsstDebug.Info(__name__).displayPsfComponents
220 matchKernelAmplitudes =
lsstDebug.Info(__name__).matchKernelAmplitudes
221 keepMatplotlibPlots =
lsstDebug.Info(__name__).keepMatplotlibPlots
222 displayPsfSpatialModel =
lsstDebug.Info(__name__).displayPsfSpatialModel
230 mi = exposure.getMaskedImage()
232 if len(psfCandidateList) == 0:
233 raise RuntimeError(
"No PSF candidates supplied.")
239 for i, psfCandidate
in enumerate(psfCandidateList):
241 psfCellSet.insertCandidate(psfCandidate)
243 self.debugLog.debug(2,
"Skipping PSF candidate %d of %d: %s" % (i, len(psfCandidateList), e))
245 source = psfCandidate.getSource()
247 quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
248 axes = afwEll.Axes(quad)
249 sizes.append(axes.getA())
251 raise RuntimeError(
"No usable PSF candidates supplied")
252 nEigenComponents = self.config.nEigenComponents
254 if self.config.kernelSize >= 15:
255 self.debugLog.debug(1, \
256 "WARNING: NOT scaling kernelSize by stellar quadrupole moment " +
257 "because config.kernelSize=%s >= 15; using config.kernelSize as as the width, instead" \
258 % (self.config.kernelSize,)
260 actualKernelSize = int(self.config.kernelSize)
262 medSize = numpy.median(sizes)
263 actualKernelSize = 2 * int(self.config.kernelSize * math.sqrt(medSize) + 0.5) + 1
264 if actualKernelSize < self.config.kernelSizeMin:
265 actualKernelSize = self.config.kernelSizeMin
266 if actualKernelSize > self.config.kernelSizeMax:
267 actualKernelSize = self.config.kernelSizeMax
270 print "Median size=%s" % (medSize,)
271 self.debugLog.debug(3,
"Kernel size=%s" % (actualKernelSize,))
274 psfCandidateList[0].setHeight(actualKernelSize)
275 psfCandidateList[0].setWidth(actualKernelSize)
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")
294 ds9.mtv(exposure, frame=frame, title=
"psf determination")
295 maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell,
296 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
303 for iter
in range(self.config.nIterForPsf):
304 if display
and displayPsfCandidates:
309 for cell
in psfCellSet.getCellList():
310 for cand
in cell.begin(
not showBadCandidates):
311 cand = algorithmsLib.cast_PsfCandidateF(cand)
314 im = cand.getMaskedImage()
316 chi2 = cand.getChi2()
320 stamps.append((im,
"%d%s" %
321 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
326 mos = displayUtils.Mosaic()
327 for im, label, status
in stamps:
328 im = type(im)(im,
True)
331 except NotImplementedError:
334 mos.append(im, label,
335 ds9.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
336 ds9.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else ds9.RED)
340 mos.makeMosaic(frame=8, title=
"Psf Candidates")
349 psf, eigenValues, nEigenComponents, fitChi2 = \
350 self.
_fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
355 for cell
in psfCellSet.getCellList():
357 for cand
in cell.begin(
False):
358 cand = algorithmsLib.cast_PsfCandidateF(cand)
359 cand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN)
360 rchi2 = cand.getChi2()
361 if not numpy.isfinite(rchi2)
or rchi2 <= 0:
363 awfulCandidates.append(cand)
365 self.debugLog.debug(2,
"chi^2=%s; id=%s" %
366 (cand.getChi2(), cand.getSource().getId()))
367 for cand
in awfulCandidates:
369 print "Removing bad candidate: id=%d, chi^2=%f" % \
370 (cand.getSource().getId(), cand.getChi2())
371 cell.removeCandidate(cand)
376 badCandidates = list()
377 for cell
in psfCellSet.getCellList():
378 for cand
in cell.begin(
False):
379 cand = algorithmsLib.cast_PsfCandidateF(cand)
380 rchi2 = cand.getChi2()
382 if rchi2 > self.config.reducedChi2ForPsfCandidates:
383 badCandidates.append(cand)
385 badCandidates.sort(key=
lambda x: x.getChi2(), reverse=
True)
386 numBad = int(len(badCandidates) * (iter + 1) / self.config.nIterForPsf + 0.5)
387 for i, c
in zip(range(numBad), badCandidates):
393 print "Chi^2 clipping %-4d %.2g" % (c.getSource().getId(), chi2)
394 c.setStatus(afwMath.SpatialCellCandidate.BAD)
407 kernel = psf.getKernel()
408 noSpatialKernel = afwMath.cast_LinearCombinationKernel(psf.getKernel())
409 for cell
in psfCellSet.getCellList():
410 for cand
in cell.begin(
False):
411 cand = algorithmsLib.cast_PsfCandidateF(cand)
414 im = cand.getMaskedImage(kernel.getWidth(), kernel.getHeight())
418 fit = algorithmsLib.fitKernelParamsToImage(noSpatialKernel, im, candCenter)
422 for p, k
in zip(params, kernels):
423 amp += p * afwMath.cast_FixedKernel(k).getSum()
425 predict = [kernel.getSpatialFunction(k)(candCenter.getX(), candCenter.getY())
for
426 k
in range(kernel.getNKernelParameters())]
430 residuals.append([a / amp - p
for a, p
in zip(params, predict)])
431 candidates.append(cand)
433 residuals = numpy.array(residuals)
435 for k
in range(kernel.getNKernelParameters()):
438 mean = residuals[:,k].mean()
439 rms = residuals[:,k].
std()
442 sr = numpy.sort(residuals[:,k])
443 mean = sr[int(0.5*len(sr))]
if len(sr) % 2
else \
444 0.5 * (sr[int(0.5*len(sr))] + sr[int(0.5*len(sr))+1])
445 rms = 0.74 * (sr[int(0.75*len(sr))] - sr[int(0.25*len(sr))])
448 mean = stats.getValue(afwMath.MEANCLIP)
449 rms = stats.getValue(afwMath.STDEVCLIP)
451 rms = max(1.0e-4, rms)
454 print "Mean for component %d is %f" % (k, mean)
455 print "RMS for component %d is %f" % (k, rms)
456 badCandidates = list()
457 for i, cand
in enumerate(candidates):
458 if numpy.fabs(residuals[i,k] - mean) > self.config.spatialReject * rms:
459 badCandidates.append(i)
461 badCandidates.sort(key=
lambda x: numpy.fabs(residuals[x,k] - mean), reverse=
True)
463 numBad = int(len(badCandidates) * (iter + 1) / self.config.nIterForPsf + 0.5)
465 for i, c
in zip(range(min(len(badCandidates), numBad)), badCandidates):
468 print "Spatial clipping %d (%f,%f) based on %d: %f vs %f" % \
469 (cand.getSource().getId(), cand.getXCenter(), cand.getYCenter(), k,
470 residuals[badCandidates[i],k], self.config.spatialReject * rms)
471 cand.setStatus(afwMath.SpatialCellCandidate.BAD)
476 if display
and displayIterations:
479 ds9.erase(frame=frame)
480 maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, showChi2=
True,
481 symb=
"o", size=8, frame=frame,
482 ctype=ds9.YELLOW, ctypeBad=ds9.RED, ctypeUnused=ds9.MAGENTA)
483 if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
484 maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
485 symb=
"o", size=10, frame=frame,
486 ctype=ds9.YELLOW, ctypeBad=ds9.RED)
490 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
491 normalize=normalizeResiduals,
492 showBadCandidates=showBadCandidates)
493 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=5,
494 normalize=normalizeResiduals,
495 showBadCandidates=showBadCandidates,
498 if not showBadCandidates:
499 showBadCandidates =
True
503 if displayPsfComponents:
504 maUtils.showPsf(psf, eigenValues, frame=6)
506 maUtils.showPsfMosaic(exposure, psf, frame=7, showFwhm=
True)
507 ds9.scale(
'linear', 0, 1, frame=7)
508 if displayPsfSpatialModel:
509 maUtils.plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=
True,
510 matchKernelAmplitudes=matchKernelAmplitudes,
511 keepPlots=keepMatplotlibPlots)
516 reply = raw_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] s[ave fileName] y[es]"
533 import pdb; pdb.set_trace()
539 fileName = args.pop(0)
541 print "Please provide a filename"
544 print "Saving to %s" % fileName
545 maUtils.saveSpatialCellSet(psfCellSet, fileName=fileName)
549 print >> sys.stderr,
"Unrecognised response: %s" % reply
555 psf, eigenValues, nEigenComponents, fitChi2 = \
556 self.
_fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
561 if display
and reply !=
"n":
563 maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCell, showChi2=
True,
564 symb=
"o", ctype=ds9.YELLOW, ctypeBad=ds9.RED, size=8, frame=frame)
565 if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
566 maUtils.showPsfSpatialCells(exposure, psfCellSet, self.config.nStarPerCellSpatialFit,
567 symb=
"o", ctype=ds9.YELLOW, ctypeBad=ds9.RED,
568 size=10, frame=frame)
570 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
571 normalize=normalizeResiduals,
572 showBadCandidates=showBadCandidates)
574 if displayPsfComponents:
575 maUtils.showPsf(psf, eigenValues, frame=6)
578 maUtils.showPsfMosaic(exposure, psf, frame=7, showFwhm=
True)
579 ds9.scale(
"linear", 0, 1, frame=7)
580 if displayPsfSpatialModel:
581 maUtils.plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=
True,
582 matchKernelAmplitudes=matchKernelAmplitudes,
583 keepPlots=keepMatplotlibPlots)
595 for cell
in psfCellSet.getCellList():
596 for cand
in cell.begin(
False):
599 for cand
in cell.begin(
True):
600 cand = algorithmsLib.cast_PsfCandidateF(cand)
601 src = cand.getSource()
602 if flagKey
is not None:
603 src.set(flagKey,
True)
612 metadata.set(
"spatialFitChi2", fitChi2)
613 metadata.set(
"numGoodStars", numGoodStars)
614 metadata.set(
"numAvailStars", numAvailStars)
615 metadata.set(
"avgX", avgX)
616 metadata.set(
"avgY", avgY)
618 psf = algorithmsLib.PcaPsf(psf.getKernel(),
afwGeom.Point2D(avgX, avgY))
620 return psf, psfCellSet
624 """!Generator for Psf candidates
626 This allows two 'for' loops to be reduced to one.
628 \param psfCellSet SpatialCellSet of PSF candidates
629 \param ignoreBad Ignore candidates flagged as BAD?
630 \return SpatialCell, PsfCandidate
632 for cell
in psfCellSet.getCellList():
633 for cand
in cell.begin(ignoreBad):
634 yield (cell, algorithmsLib.cast_PsfCandidateF(cand))
a place to record messages and descriptions of the state of processing.
boost::enable_if< typename ExpressionTraits< Scalar >::IsScalar, Scalar >::type sum(Scalar const &scalar)
A collection of SpatialCells covering an entire image.
def determinePsf
Determine a PCA PSF model for an exposure given a list of PSF candidates.
A measurePsfTask psf estimator.
def __init__
Construct a PCA PSF Fitter.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
def candidatesIter
Generator for Psf candidates.