26 import lsst.pex.config
as pexConfig
38 __nEigenComponents = pexConfig.Field(
39 doc=
"number of eigen components for PSF kernel creation",
43 spatialOrder = pexConfig.Field(
44 doc=
"specify spatial order for PSF kernel creation",
47 check=
lambda x: x >= 0,
49 sizeCellX = pexConfig.Field(
50 doc=
"size of cell used to determine PSF (pixels, column direction)",
54 check=
lambda x: x >= 10,
56 sizeCellY = pexConfig.Field(
57 doc=
"size of cell used to determine PSF (pixels, row direction)",
59 default=sizeCellX.default,
61 check=
lambda x: x >= 10,
63 __nStarPerCell = pexConfig.Field(
64 doc=
"number of stars per psf cell for PSF kernel creation",
68 samplingSize = pexConfig.Field(
69 doc=
"Resolution of the internal PSF model relative to the pixel size; " 70 "e.g. 0.5 is equal to 2x oversampling",
74 badMaskBits = pexConfig.ListField(
75 doc=
"List of mask bits which cause a source to be rejected as bad " 76 "N.b. INTRP is used specially in PsfCandidateSet; it means \"Contaminated by neighbour\"",
78 default=[
"INTRP",
"SAT"],
80 psfexBasis = pexConfig.ChoiceField(
81 doc=
"BASIS value given to psfex. PIXEL_AUTO will use the requested samplingSize only if " 82 "the FWHM < 3 pixels. Otherwise, it will use samplingSize=1. PIXEL will always use the " 83 "requested samplingSize",
86 "PIXEL":
"Always use requested samplingSize",
87 "PIXEL_AUTO":
"Only use requested samplingSize when FWHM < 3",
92 __borderWidth = pexConfig.Field(
93 doc=
"Number of pixels to ignore around the edge of PSF candidate postage stamps",
97 __nStarPerCellSpatialFit = pexConfig.Field(
98 doc=
"number of stars per psf Cell for spatial fitting",
102 __constantWeight = pexConfig.Field(
103 doc=
"Should each PSF candidate be given the same weight, independent of magnitude?",
107 __nIterForPsf = pexConfig.Field(
108 doc=
"number of iterations of PSF candidate star list",
112 tolerance = pexConfig.Field(
113 doc=
"tolerance of spatial fitting",
117 lam = pexConfig.Field(
118 doc=
"floor for variance is lam*data",
122 reducedChi2ForPsfCandidates = pexConfig.Field(
123 doc=
"for psf candidate evaluation",
127 spatialReject = pexConfig.Field(
128 doc=
"Rejection threshold (stdev) for candidates based on spatial fit",
132 recentroid = pexConfig.Field(
133 doc=
"Should PSFEX be permitted to recentroid PSF candidates?",
143 ConfigClass = PsfexPsfDeterminerConfig
145 def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
146 """Determine a PSFEX PSF model for an exposure given a list of PSF 151 exposure: `lsst.afw.image.Exposure` 152 Exposure containing the PSF candidates. 153 psfCandidateList: iterable of `lsst.meas.algorithms.PsfCandidate` 154 Sequence of PSF candidates typically obtained by detecting sources 155 and then running them through a star selector. 156 metadata: metadata, optional 157 A home for interesting tidbits of information. 158 flagKey: `lsst.afw.table.Key`, optional 159 Schema key used to mark sources actually used in PSF determination. 163 psf: `lsst.meas.extensions.psfex.PsfexPsf` 169 displayExposure = display
and \
171 displayPsfComponents = display
and \
173 showBadCandidates = display
and \
175 displayResiduals = display
and \
177 displayPsfMosaic = display
and \
180 afwDisplay.setDefaultMaskTransparency(75)
183 mi = exposure.getMaskedImage()
185 nCand = len(psfCandidateList)
187 raise RuntimeError(
"No PSF candidates supplied.")
193 bbox = mi.getBBox(afwImage.PARENT)
198 sizes = np.empty(nCand)
199 for i, psfCandidate
in enumerate(psfCandidateList):
202 psfCellSet.insertCandidate(psfCandidate)
203 except Exception
as e:
204 self.log.
debug(
"Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
207 source = psfCandidate.getSource()
208 quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
209 rmsSize = quad.getTraceRadius()
212 if self.config.kernelSize >= 15:
213 self.log.
warn(
"NOT scaling kernelSize by stellar quadrupole moment, but using absolute value")
214 actualKernelSize = int(self.config.kernelSize)
216 actualKernelSize = 2 * int(self.config.kernelSize * np.sqrt(np.median(sizes)) + 0.5) + 1
217 if actualKernelSize < self.config.kernelSizeMin:
218 actualKernelSize = self.config.kernelSizeMin
219 if actualKernelSize > self.config.kernelSizeMax:
220 actualKernelSize = self.config.kernelSizeMax
222 rms = np.median(sizes)
223 print(
"Median PSF RMS size=%.2f pixels (\"FWHM\"=%.2f)" % (rms, 2*np.sqrt(2*np.log(2))*rms))
227 pixKernelSize = actualKernelSize
228 if self.config.samplingSize > 0:
229 pixKernelSize = int(actualKernelSize*self.config.samplingSize)
230 if pixKernelSize % 2 == 0:
232 self.log.
trace(
"Psfex Kernel size=%.2f, Image Kernel Size=%.2f", actualKernelSize, pixKernelSize)
233 psfCandidateList[0].setHeight(pixKernelSize)
234 psfCandidateList[0].setWidth(pixKernelSize)
240 defaultsFile = os.path.join(os.environ[
"MEAS_EXTENSIONS_PSFEX_DIR"],
"config",
"default-lsst.psfex")
242 args_md.set(
"BASIS_TYPE", str(self.config.psfexBasis))
243 args_md.set(
"PSFVAR_DEGREES", str(self.config.spatialOrder))
244 args_md.set(
"PSF_SIZE", str(actualKernelSize))
245 args_md.set(
"PSF_SAMPLING", str(self.config.samplingSize))
246 prefs = psfex.Prefs(defaultsFile, args_md)
247 prefs.setCommandLine([])
248 prefs.addCatalog(
"psfexPsfDeterminer")
251 principalComponentExclusionFlag = bool(bool(psfex.Context.REMOVEHIDDEN)
252 if False else psfex.Context.KEEPHIDDEN)
253 context = psfex.Context(prefs.getContextName(), prefs.getContextGroup(),
254 prefs.getGroupDeg(), principalComponentExclusionFlag)
255 set = psfex.Set(context)
256 set.setVigSize(pixKernelSize, pixKernelSize)
257 set.setFwhm(2*np.sqrt(2*np.log(2))*np.median(sizes))
258 set.setRecentroid(self.config.recentroid)
262 ccd = exposure.getDetector()
264 gain = np.mean(np.array([a.getGain()
for a
in ccd]))
267 self.log.
warn(
"Setting gain to %g" % (gain,))
271 for i, key
in enumerate(context.getName()):
272 if context.getPcflag(i):
273 raise RuntimeError(
"Principal Components can not be accessed")
274 contextvalp.append(pcval[pc])
278 contextvalp.append(exposure.getMetadata().getScalar(key[1:]))
280 raise RuntimeError(
"*Error*: %s parameter not found in the header of %s" %
281 (key[1:], prefs.getContextName()))
284 contextvalp.append(np.array([psfCandidateList[_].getSource().get(key)
285 for _
in range(nCand)]))
287 raise RuntimeError(
"*Error*: %s parameter not found" % (key,))
288 set.setContextname(i, key)
293 disp = afwDisplay.Display(frame=frame)
294 disp.mtv(exposure, title=
"psf determination")
296 badBits = mi.getMask().getPlaneBitMask(self.config.badMaskBits)
297 fluxName = prefs.getPhotfluxRkey()
298 fluxFlagName =
"base_" + fluxName +
"_flag" 301 for i, psfCandidate
in enumerate(psfCandidateList):
302 source = psfCandidate.getSource()
303 xc, yc = source.getX(), source.getY()
310 pstamp = psfCandidate.getMaskedImage().
clone()
314 if fluxFlagName
in source.schema
and source.get(fluxFlagName):
317 flux = source.get(fluxName)
318 if flux < 0
or np.isnan(flux):
326 sample = set.newSample()
327 sample.setCatindex(catindex)
328 sample.setExtindex(ext)
329 sample.setObjindex(i)
331 imArray = pstamp.getImage().getArray()
332 imArray[np.where(np.bitwise_and(pstamp.getMask().getArray(), badBits))] = \
334 sample.setVig(imArray)
337 sample.setBacknoise2(backnoise2)
341 sample.setFluxrad(sizes[i])
343 for j
in range(set.getNcontext()):
344 sample.setContext(j, float(contextvalp[j][i]))
345 except Exception
as e:
346 self.log.
debug(
"Exception when processing sample at (%f,%f): %s", xc, yc, e)
349 set.finiSample(sample)
355 with disp.Buffering():
356 disp.dot(
"o", xc, yc, ctype=afwDisplay.CYAN, size=4)
358 if set.getNsample() == 0:
359 raise RuntimeError(
"No good PSF candidates to pass to PSFEx")
362 for i
in range(set.getNcontext()):
363 cmin = contextvalp[i].
min()
364 cmax = contextvalp[i].
max()
365 set.setContextScale(i, cmax - cmin)
366 set.setContextOffset(i, (cmin + cmax)/2.0)
376 field = psfex.Field(
"Unknown")
377 field.addExt(exposure.getWcs(), exposure.getWidth(), exposure.getHeight(), set.getNsample())
385 psfex.makeit(fields, sets)
386 psfs = field.getPsfs()
390 for i
in range(sets[0].getNsample()):
391 index = sets[0].getSample(i).getObjindex()
393 good_indices.append(index)
395 if flagKey
is not None:
396 for i, psfCandidate
in enumerate(psfCandidateList):
397 source = psfCandidate.getSource()
398 if i
in good_indices:
399 source.set(flagKey,
True)
401 xpos = np.array(xpos)
402 ypos = np.array(ypos)
403 numGoodStars = len(good_indices)
404 avgX, avgY = np.mean(xpos), np.mean(ypos)
408 if False and (displayResiduals
or displayPsfMosaic):
413 title =
"psfexPsfDeterminer" 414 psfex.psfex.showPsf(psfs, set, ext,
415 [(exposure.getWcs(), exposure.getWidth(), exposure.getHeight())],
416 nspot=3, trim=5, frame=frame, diagnostics=diagnostics, outDir=catDir,
422 assert psfCellSet
is not None 425 maUtils.showPsfSpatialCells(exposure, psfCellSet, showChi2=
True,
426 symb=
"o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
427 size=8, display=disp)
429 disp4 = afwDisplay.Display(frame=4)
430 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
431 normalize=normalizeResiduals,
432 showBadCandidates=showBadCandidates)
433 if displayPsfComponents:
434 disp6 = afwDisplay.Display(frame=6)
435 maUtils.showPsf(psf, display=disp6)
437 disp7 = afwDisplay.Display(frame=7)
438 maUtils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=
True)
439 disp.scale(
'linear', 0, 1)
445 if metadata
is not None:
446 metadata.set(
"spatialFitChi2", np.nan)
447 metadata.set(
"numAvailStars", nCand)
448 metadata.set(
"numGoodStars", numGoodStars)
449 metadata.set(
"avgX", avgX)
450 metadata.set(
"avgY", avgY)
452 return psf, psfCellSet
455 measAlg.psfDeterminerRegistry.register(
"psfex", PsfexPsfDeterminerTask)
def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
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<>
A collection of SpatialCells covering an entire image.
Represent a PSF as a linear combination of PSFEX (== Karhunen-Loeve) basis functions.
Class for storing generic metadata.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...