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 candidates. 150 exposure: `lsst.afw.image.Exposure` 151 Exposure containing the PSF candidates. 152 psfCandidateList: iterable of `lsst.meas.algorithms.PsfCandidate` 153 Sequence of PSF candidates typically obtained by detecting sources and then running them through a 155 metadata: metadata, optional 156 A home for interesting tidbits of information. 157 flagKey: `lsst.afw.table.Key`, optional 158 Schema key used to mark sources actually used in PSF determination. 162 psf: `lsst.meas.extensions.psfex.PsfexPsf` 168 displayExposure = display
and \
170 displayPsfComponents = display
and \
172 showBadCandidates = display
and \
174 displayResiduals = display
and \
176 displayPsfMosaic = display
and \
181 mi = exposure.getMaskedImage()
183 nCand = len(psfCandidateList)
185 raise RuntimeError(
"No PSF candidates supplied.")
191 bbox = mi.getBBox(afwImage.PARENT)
196 sizes = np.empty(nCand)
197 for i, psfCandidate
in enumerate(psfCandidateList):
200 psfCellSet.insertCandidate(psfCandidate)
201 except Exception
as e:
202 self.log.
debug(
"Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
205 source = psfCandidate.getSource()
206 quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
207 rmsSize = quad.getTraceRadius()
210 if self.config.kernelSize >= 15:
211 self.log.
warn(
"NOT scaling kernelSize by stellar quadrupole moment, but using absolute value")
212 actualKernelSize =
int(self.config.kernelSize)
214 actualKernelSize = 2 *
int(self.config.kernelSize * np.sqrt(np.median(sizes)) + 0.5) + 1
215 if actualKernelSize < self.config.kernelSizeMin:
216 actualKernelSize = self.config.kernelSizeMin
217 if actualKernelSize > self.config.kernelSizeMax:
218 actualKernelSize = self.config.kernelSizeMax
220 rms = np.median(sizes)
221 print(
"Median PSF RMS size=%.2f pixels (\"FWHM\"=%.2f)" % (rms, 2*np.sqrt(2*np.log(2))*rms))
224 pixKernelSize = actualKernelSize
225 if self.config.samplingSize > 0:
226 pixKernelSize =
int(actualKernelSize*self.config.samplingSize)
227 if pixKernelSize % 2 == 0:
229 self.log.
trace(
"Psfex Kernel size=%.2f, Image Kernel Size=%.2f", actualKernelSize, pixKernelSize)
230 psfCandidateList[0].setHeight(pixKernelSize)
231 psfCandidateList[0].setWidth(pixKernelSize)
237 defaultsFile = os.path.join(os.environ[
"MEAS_EXTENSIONS_PSFEX_DIR"],
"config",
"default-lsst.psfex")
239 args_md.set(
"BASIS_TYPE",
str(self.config.psfexBasis))
240 args_md.set(
"PSFVAR_DEGREES",
str(self.config.spatialOrder))
241 args_md.set(
"PSF_SIZE",
str(actualKernelSize))
242 args_md.set(
"PSF_SAMPLING",
str(self.config.samplingSize))
243 prefs = psfex.Prefs(defaultsFile, args_md)
244 prefs.setCommandLine([])
245 prefs.addCatalog(
"psfexPsfDeterminer")
248 principalComponentExclusionFlag = bool(bool(psfex.Context.REMOVEHIDDEN)
249 if False else psfex.Context.KEEPHIDDEN)
250 context = psfex.Context(prefs.getContextName(), prefs.getContextGroup(),
251 prefs.getGroupDeg(), principalComponentExclusionFlag)
252 set = psfex.Set(context)
253 set.setVigSize(pixKernelSize, pixKernelSize)
254 set.setFwhm(2*np.sqrt(2*np.log(2))*np.median(sizes))
255 set.setRecentroid(self.config.recentroid)
259 ccd = exposure.getDetector()
261 gain = np.mean(np.array([a.getGain()
for a
in ccd]))
264 self.log.
warn(
"Setting gain to %g" % (gain,))
267 for i, key
in enumerate(context.getName()):
268 if context.getPcflag(i):
269 contextvalp.append(pcval[pc])
273 contextvalp.append(exposure.getMetadata().getScalar(key[1:]))
275 raise RuntimeError(
"*Error*: %s parameter not found in the header of %s" %
276 (key[1:], prefs.getContextName()))
279 contextvalp.append(np.array([psfCandidateList[_].getSource().get(key)
280 for _
in range(nCand)]))
282 raise RuntimeError(
"*Error*: %s parameter not found" % (key,))
283 set.setContextname(i, key)
288 ds9.mtv(exposure, frame=frame, title=
"psf determination")
290 badBits = mi.getMask().getPlaneBitMask(self.config.badMaskBits)
291 fluxName = prefs.getPhotfluxRkey()
292 fluxFlagName =
"base_" + fluxName +
"_flag" 295 for i, psfCandidate
in enumerate(psfCandidateList):
296 source = psfCandidate.getSource()
297 xc, yc = source.getX(), source.getY()
304 pstamp = psfCandidate.getMaskedImage().
clone()
305 except Exception
as e:
308 if fluxFlagName
in source.schema
and source.get(fluxFlagName):
311 flux = source.get(fluxName)
312 if flux < 0
or np.isnan(flux):
319 sample = set.newSample()
320 sample.setCatindex(catindex)
321 sample.setExtindex(ext)
322 sample.setObjindex(i)
324 imArray = pstamp.getImage().getArray()
325 imArray[np.where(np.bitwise_and(pstamp.getMask().getArray(), badBits))] = \
327 sample.setVig(imArray)
330 sample.setBacknoise2(backnoise2)
334 sample.setFluxrad(sizes[i])
336 for j
in range(set.getNcontext()):
337 sample.setContext(j,
float(contextvalp[j][i]))
338 except Exception
as e:
339 self.log.
debug(
"Exception when processing sample at (%f,%f): %s", xc, yc, e)
342 set.finiSample(sample)
348 with ds9.Buffering():
349 ds9.dot(
"o", xc, yc, ctype=ds9.CYAN, size=4, frame=frame)
351 if set.getNsample() == 0:
352 raise RuntimeError(
"No good PSF candidates to pass to PSFEx")
355 for i
in range(set.getNcontext()):
356 cmin = contextvalp[i].
min()
357 cmax = contextvalp[i].
max()
358 set.setContextScale(i, cmax - cmin)
359 set.setContextOffset(i, (cmin + cmax)/2.0)
369 field = psfex.Field(
"Unknown")
370 field.addExt(exposure.getWcs(), exposure.getWidth(), exposure.getHeight(), set.getNsample())
378 psfex.makeit(fields, sets)
379 psfs = field.getPsfs()
383 for i
in range(sets[0].getNsample()):
384 index = sets[0].getSample(i).getObjindex()
386 good_indices.append(index)
388 if flagKey
is not None:
389 for i, psfCandidate
in enumerate(psfCandidateList):
390 source = psfCandidate.getSource()
391 if i
in good_indices:
392 source.set(flagKey,
True)
394 xpos = np.array(xpos)
395 ypos = np.array(ypos)
396 numGoodStars = len(good_indices)
397 avgX, avgY = np.mean(xpos), np.mean(ypos)
401 if False and (displayResiduals
or displayPsfMosaic):
406 title =
"psfexPsfDeterminer" 407 psfex.psfex.showPsf(psfs, set, ext,
408 [(exposure.getWcs(), exposure.getWidth(), exposure.getHeight())],
409 nspot=3, trim=5, frame=frame, diagnostics=diagnostics, outDir=catDir,
415 assert psfCellSet
is not None 418 maUtils.showPsfSpatialCells(exposure, psfCellSet, showChi2=
True,
419 symb=
"o", ctype=ds9.YELLOW, ctypeBad=ds9.RED, size=8, frame=frame)
421 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, frame=4,
422 normalize=normalizeResiduals,
423 showBadCandidates=showBadCandidates)
424 if displayPsfComponents:
425 maUtils.showPsf(psf, frame=6)
427 maUtils.showPsfMosaic(exposure, psf, frame=7, showFwhm=
True)
428 ds9.scale(
'linear', 0, 1, frame=7)
434 if metadata
is not None:
435 metadata.set(
"spatialFitChi2", np.nan)
436 metadata.set(
"numAvailStars", nCand)
437 metadata.set(
"numGoodStars", numGoodStars)
438 metadata.set(
"avgX", avgX)
439 metadata.set(
"avgY", avgY)
442 return psf, psfCellSet
446 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.