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 \
179 afwDisplay.setDefaultMaskTransparency(75)
182 mi = exposure.getMaskedImage()
184 nCand = len(psfCandidateList)
186 raise RuntimeError(
"No PSF candidates supplied.")
192 bbox = mi.getBBox(afwImage.PARENT)
197 sizes = np.empty(nCand)
198 for i, psfCandidate
in enumerate(psfCandidateList):
201 psfCellSet.insertCandidate(psfCandidate)
202 except Exception
as e:
203 self.log.
debug(
"Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
206 source = psfCandidate.getSource()
207 quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
208 rmsSize = quad.getTraceRadius()
211 if self.config.kernelSize >= 15:
212 self.log.
warn(
"NOT scaling kernelSize by stellar quadrupole moment, but using absolute value")
213 actualKernelSize =
int(self.config.kernelSize)
215 actualKernelSize = 2 *
int(self.config.kernelSize * np.sqrt(np.median(sizes)) + 0.5) + 1
216 if actualKernelSize < self.config.kernelSizeMin:
217 actualKernelSize = self.config.kernelSizeMin
218 if actualKernelSize > self.config.kernelSizeMax:
219 actualKernelSize = self.config.kernelSizeMax
221 rms = np.median(sizes)
222 print(
"Median PSF RMS size=%.2f pixels (\"FWHM\"=%.2f)" % (rms, 2*np.sqrt(2*np.log(2))*rms))
225 pixKernelSize = actualKernelSize
226 if self.config.samplingSize > 0:
227 pixKernelSize =
int(actualKernelSize*self.config.samplingSize)
228 if pixKernelSize % 2 == 0:
230 self.log.
trace(
"Psfex Kernel size=%.2f, Image Kernel Size=%.2f", actualKernelSize, pixKernelSize)
231 psfCandidateList[0].setHeight(pixKernelSize)
232 psfCandidateList[0].setWidth(pixKernelSize)
238 defaultsFile = os.path.join(os.environ[
"MEAS_EXTENSIONS_PSFEX_DIR"],
"config",
"default-lsst.psfex")
240 args_md.set(
"BASIS_TYPE",
str(self.config.psfexBasis))
241 args_md.set(
"PSFVAR_DEGREES",
str(self.config.spatialOrder))
242 args_md.set(
"PSF_SIZE",
str(actualKernelSize))
243 args_md.set(
"PSF_SAMPLING",
str(self.config.samplingSize))
244 prefs = psfex.Prefs(defaultsFile, args_md)
245 prefs.setCommandLine([])
246 prefs.addCatalog(
"psfexPsfDeterminer")
249 principalComponentExclusionFlag = bool(bool(psfex.Context.REMOVEHIDDEN)
250 if False else psfex.Context.KEEPHIDDEN)
251 context = psfex.Context(prefs.getContextName(), prefs.getContextGroup(),
252 prefs.getGroupDeg(), principalComponentExclusionFlag)
253 set = psfex.Set(context)
254 set.setVigSize(pixKernelSize, pixKernelSize)
255 set.setFwhm(2*np.sqrt(2*np.log(2))*np.median(sizes))
256 set.setRecentroid(self.config.recentroid)
260 ccd = exposure.getDetector()
262 gain = np.mean(np.array([a.getGain()
for a
in ccd]))
265 self.log.
warn(
"Setting gain to %g" % (gain,))
268 for i, key
in enumerate(context.getName()):
269 if context.getPcflag(i):
270 contextvalp.append(pcval[pc])
274 contextvalp.append(exposure.getMetadata().getScalar(key[1:]))
276 raise RuntimeError(
"*Error*: %s parameter not found in the header of %s" %
277 (key[1:], prefs.getContextName()))
280 contextvalp.append(np.array([psfCandidateList[_].getSource().get(key)
281 for _
in range(nCand)]))
283 raise RuntimeError(
"*Error*: %s parameter not found" % (key,))
284 set.setContextname(i, key)
289 disp = afwDisplay.Display(frame=frame)
290 disp.mtv(exposure, title=
"psf determination")
292 badBits = mi.getMask().getPlaneBitMask(self.config.badMaskBits)
293 fluxName = prefs.getPhotfluxRkey()
294 fluxFlagName =
"base_" + fluxName +
"_flag" 297 for i, psfCandidate
in enumerate(psfCandidateList):
298 source = psfCandidate.getSource()
299 xc, yc = source.getX(), source.getY()
306 pstamp = psfCandidate.getMaskedImage().
clone()
310 if fluxFlagName
in source.schema
and source.get(fluxFlagName):
313 flux = source.get(fluxName)
314 if flux < 0
or np.isnan(flux):
321 sample = set.newSample()
322 sample.setCatindex(catindex)
323 sample.setExtindex(ext)
324 sample.setObjindex(i)
326 imArray = pstamp.getImage().getArray()
327 imArray[np.where(np.bitwise_and(pstamp.getMask().getArray(), badBits))] = \
329 sample.setVig(imArray)
332 sample.setBacknoise2(backnoise2)
336 sample.setFluxrad(sizes[i])
338 for j
in range(set.getNcontext()):
339 sample.setContext(j,
float(contextvalp[j][i]))
340 except Exception
as e:
341 self.log.
debug(
"Exception when processing sample at (%f,%f): %s", xc, yc, e)
344 set.finiSample(sample)
350 with disp.Buffering():
351 disp.dot(
"o", xc, yc, ctype=afwDisplay.CYAN, size=4)
353 if set.getNsample() == 0:
354 raise RuntimeError(
"No good PSF candidates to pass to PSFEx")
357 for i
in range(set.getNcontext()):
358 cmin = contextvalp[i].
min()
359 cmax = contextvalp[i].
max()
360 set.setContextScale(i, cmax - cmin)
361 set.setContextOffset(i, (cmin + cmax)/2.0)
371 field = psfex.Field(
"Unknown")
372 field.addExt(exposure.getWcs(), exposure.getWidth(), exposure.getHeight(), set.getNsample())
380 psfex.makeit(fields, sets)
381 psfs = field.getPsfs()
385 for i
in range(sets[0].getNsample()):
386 index = sets[0].getSample(i).getObjindex()
388 good_indices.append(index)
390 if flagKey
is not None:
391 for i, psfCandidate
in enumerate(psfCandidateList):
392 source = psfCandidate.getSource()
393 if i
in good_indices:
394 source.set(flagKey,
True)
396 xpos = np.array(xpos)
397 ypos = np.array(ypos)
398 numGoodStars = len(good_indices)
399 avgX, avgY = np.mean(xpos), np.mean(ypos)
403 if False and (displayResiduals
or displayPsfMosaic):
408 title =
"psfexPsfDeterminer" 409 psfex.psfex.showPsf(psfs, set, ext,
410 [(exposure.getWcs(), exposure.getWidth(), exposure.getHeight())],
411 nspot=3, trim=5, frame=frame, diagnostics=diagnostics, outDir=catDir,
417 assert psfCellSet
is not None 420 maUtils.showPsfSpatialCells(exposure, psfCellSet, showChi2=
True,
421 symb=
"o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
422 size=8, display=disp)
424 disp4 = afwDisplay.Display(frame=4)
425 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
426 normalize=normalizeResiduals,
427 showBadCandidates=showBadCandidates)
428 if displayPsfComponents:
429 disp6 = afwDisplay.Display(frame=6)
430 maUtils.showPsf(psf, display=disp6)
432 disp7 = afwDisplay.Display(frame=7)
433 maUtils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=
True)
434 disp.scale(
'linear', 0, 1)
440 if metadata
is not None:
441 metadata.set(
"spatialFitChi2", np.nan)
442 metadata.set(
"numAvailStars", nCand)
443 metadata.set(
"numGoodStars", numGoodStars)
444 metadata.set(
"avgX", avgX)
445 metadata.set(
"avgY", avgY)
447 return psf, psfCellSet
449 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...