22 __all__ = [
"PiffPsfDeterminerConfig",
"PiffPsfDeterminerTask"]
31 from .piffPsf
import PiffPsf
35 spatialOrder = pexConfig.Field(
36 doc=
"specify spatial order for PSF kernel creation",
40 samplingSize = pexConfig.Field(
41 doc=
"Resolution of the internal PSF model relative to the pixel size; "
42 "e.g. 0.5 is equal to 2x oversampling",
46 outlierNSigma = pexConfig.Field(
47 doc=
"n sigma for chisq outlier rejection",
51 outlierMaxRemove = pexConfig.Field(
52 doc=
"Max fraction of stars to remove as outliers each iteration",
56 maxSNR = pexConfig.Field(
57 doc=
"Rescale the weight of bright stars such that their SNR is less "
70 """Derive a weight map without Poisson variance component due to signal.
74 maskedImage : `afw.image.MaskedImage`
75 PSF candidate postage stamp
77 Maximum SNR applying variance floor.
82 Array to use for weight.
84 imArr = maskedImage.image.array
85 varArr = maskedImage.variance.array
86 good = (varArr != 0) & np.isfinite(varArr) & np.isfinite(imArr)
91 fit = np.polyfit(imArr[good], varArr[good], deg=1)
93 weightArr = np.zeros_like(imArr, dtype=float)
94 weightArr[good] = 1./fit[1]
101 """Rescale weight of bright stars to cap the computed SNR.
106 Signal (image) array of stamp.
107 weightArr : `ndarray`
108 Weight map array. May be rescaled in place.
110 Index array of pixels to use when computing SNR.
112 Threshold for adjusting variance plane implementing maximum SNR.
139 F = np.sum(weightArr[good]*imArr[good]**2, dtype=float)
141 SNR = 0.0
if F < Npix
else (F-Npix)/np.sqrt(F)
144 factor = (maxSNR / SNR)**2
145 weightArr[good] *= factor
148 def _computeWeightAlternative(maskedImage, maxSNR):
149 """Alternative algorithm for creating weight map.
151 This version is equivalent to that used by Piff internally. The weight map
152 it produces tends to leave a residual when removing the Poisson component
153 due to the signal. We leave it here as a reference, but without intending
156 imArr = maskedImage.image.array
157 varArr = maskedImage.variance.array
158 good = (varArr != 0) & np.isfinite(varArr) & np.isfinite(imArr)
160 fit = np.polyfit(imArr[good], varArr[good], deg=1)
163 varArr[good] -= imArr[good] / gain
164 weightArr = np.zeros_like(imArr, dtype=float)
165 weightArr[good] = 1./varArr[good]
172 """A measurePsfTask PSF estimator using Piff as the implementation.
174 ConfigClass = PiffPsfDeterminerConfig
175 _DefaultName =
"psfDeterminer.Piff"
178 self, exposure, psfCandidateList, metadata=None, flagKey=None
180 """Determine a Piff PSF model for an exposure given a list of PSF
185 exposure : `lsst.afw.image.Exposure`
186 Exposure containing the PSF candidates.
187 psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate`
188 A sequence of PSF candidates typically obtained by detecting sources
189 and then running them through a star selector.
190 metadata : `lsst.daf.base import PropertyList` or `None`, optional
191 A home for interesting tidbits of information.
192 flagKey : `str` or `None`, optional
193 Schema key used to mark sources actually used in PSF determination.
197 psf : `lsst.meas.extensions.piff.PiffPsf`
198 The measured PSF model.
200 Unused by this PsfDeterminer.
203 for candidate
in psfCandidateList:
204 cmi = candidate.getMaskedImage()
208 bds = galsim.BoundsI(
209 galsim.PositionI(*bbox.getMin()),
210 galsim.PositionI(*bbox.getMax())
212 gsImage = galsim.Image(bds, scale=1.0, dtype=float)
213 gsImage.array[:] = cmi.image.array
214 gsWeight = galsim.Image(bds, scale=1.0, dtype=float)
215 gsWeight.array[:] = weight
217 source = candidate.getSource()
218 image_pos = galsim.PositionD(source.getX(), source.getY())
220 data = piff.StarData(
225 stars.append(piff.Star(data,
None))
227 kernelSize = int(np.clip(
228 self.config.kernelSize,
229 self.config.kernelSizeMin,
230 self.config.kernelSizeMax
237 'scale': self.config.samplingSize,
241 'type':
'BasisPolynomial',
242 'order': self.config.spatialOrder
246 'nsigma': self.config.outlierNSigma,
247 'max_remove': self.config.outlierMaxRemove
251 piffResult = piff.PSF.process(piffConfig)
253 wcs = {0: galsim.PixelScale(1.0)}
256 piffResult.fit(stars, wcs, pointing, logger=self.log)
257 psf =
PiffPsf(kernelSize, kernelSize, piffResult)
259 used_image_pos = [s.image_pos
for s
in piffResult.stars]
261 for candidate
in psfCandidateList:
262 source = candidate.getSource()
263 posd = galsim.PositionD(source.getX(), source.getY())
264 if posd
in used_image_pos:
265 source.set(flagKey,
True)
267 if metadata
is not None:
268 metadata[
"spatialFitChi2"] = piffResult.chisq
269 metadata[
"numAvailStars"] = len(stars)
270 metadata[
"numGoodStars"] = len(piffResult.stars)
271 metadata[
"avgX"] = np.mean([p.x
for p
in piffResult.stars])
272 metadata[
"avgY"] = np.mean([p.y
for p
in piffResult.stars])
277 measAlg.psfDeterminerRegistry.register(
"piff", PiffPsfDeterminerTask)
def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
def computeWeight(maskedImage, maxSNR)
def applyMaxSNR(imArr, weightArr, good, maxSNR)