160 def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
161 """Determine a PSFEX PSF model for an exposure given a list of PSF
166 exposure: `lsst.afw.image.Exposure`
167 Exposure containing the PSF candidates.
168 psfCandidateList: iterable of `lsst.meas.algorithms.PsfCandidate`
169 Sequence of PSF candidates typically obtained by detecting sources
170 and then running them through a star selector.
171 metadata: metadata, optional
172 A home for interesting tidbits of information.
173 flagKey: `lsst.afw.table.Key`, optional
174 Schema key used to mark sources actually used in PSF determination.
178 psf: `lsst.meas.extensions.psfex.PsfexPsf`
181 psfCandidateList = self.downsampleCandidates(psfCandidateList)
185 displayExposure = display
and \
187 displayPsfComponents = display
and \
189 showBadCandidates = display
and \
191 displayResiduals = display
and \
193 displayPsfMosaic = display
and \
196 afwDisplay.setDefaultMaskTransparency(75)
199 mi = exposure.getMaskedImage()
201 nCand = len(psfCandidateList)
203 raise RuntimeError(
"No PSF candidates supplied.")
209 bbox = mi.getBBox(afwImage.PARENT)
214 sizes = np.empty(nCand)
215 for i, psfCandidate
in enumerate(psfCandidateList):
218 psfCellSet.insertCandidate(psfCandidate)
219 except Exception
as e:
220 self.log.error(
"Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
223 source = psfCandidate.getSource()
224 quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
225 rmsSize = quad.getTraceRadius()
228 pixKernelSize = self.config.stampSize
229 actualKernelSize = int(2*np.floor(0.5*pixKernelSize/self.config.samplingSize) + 1)
232 rms = np.median(sizes)
233 self.log.debug(
"Median PSF RMS size=%.2f pixels (\"FWHM\"=%.2f)",
234 rms, 2*np.sqrt(2*np.log(2))*rms)
236 self.log.trace(
"Psfex Kernel size=%.2f, Image Kernel Size=%.2f", actualKernelSize, pixKernelSize)
242 defaultsFile = os.path.join(os.environ[
"MEAS_EXTENSIONS_PSFEX_DIR"],
"config",
"default-lsst.psfex")
244 args_md.set(
"BASIS_TYPE", str(self.config.psfexBasis))
245 args_md.set(
"PSFVAR_DEGREES", str(self.config.spatialOrder))
246 args_md.set(
"PSF_SIZE", str(actualKernelSize))
247 args_md.set(
"PSF_SAMPLING", str(self.config.samplingSize))
248 args_md.set(
"PHOTFLUX_KEY", str(self.config.photometricFluxField))
249 args_md.set(
"PHOTFLUXERR_KEY", str(self.config.photometricFluxField) +
"Err")
250 prefs = psfex.Prefs(defaultsFile, args_md)
251 prefs.setCommandLine([])
252 prefs.addCatalog(
"psfexPsfDeterminer")
255 principalComponentExclusionFlag = bool(bool(psfex.Context.REMOVEHIDDEN)
256 if False else psfex.Context.KEEPHIDDEN)
257 context = psfex.Context(prefs.getContextName(), prefs.getContextGroup(),
258 prefs.getGroupDeg(), principalComponentExclusionFlag)
259 psfSet = psfex.Set(context)
260 psfSet.setVigSize(pixKernelSize, pixKernelSize)
261 psfSet.setFwhm(2*np.sqrt(2*np.log(2))*np.median(sizes))
262 psfSet.setRecentroid(self.config.recentroid)
266 ccd = exposure.getDetector()
268 gain = np.mean(np.array([a.getGain()
for a
in ccd]))
271 self.log.warning(
"Setting gain to %g", gain)
274 for i, key
in enumerate(context.getName()):
277 contextvalp.append(exposure.getMetadata().getScalar(key[1:]))
278 except KeyError
as e:
279 raise RuntimeError(
"%s parameter not found in the header of %s" %
280 (key[1:], prefs.getContextName()))
from e
283 contextvalp.append(np.array([psfCandidateList[_].getSource().get(key)
284 for _
in range(nCand)]))
285 except KeyError
as e:
286 raise RuntimeError(
"%s parameter not found" % (key,))
from e
287 psfSet.setContextname(i, key)
292 disp = afwDisplay.Display(frame=frame)
293 disp.mtv(exposure, title=
"psf determination")
295 badBits = mi.getMask().getPlaneBitMask(self.config.badMaskBits)
296 fluxName = prefs.getPhotfluxRkey()
297 fluxFlagName =
"base_" + fluxName +
"_flag"
300 for i, psfCandidate
in enumerate(psfCandidateList):
301 source = psfCandidate.getSource()
304 xc, yc = source.getX(), source.getY()
305 if not np.isfinite(xc)
or not np.isfinite(yc):
308 if fluxFlagName
in source.schema
and source.get(fluxFlagName):
311 flux = source.get(fluxName)
312 if flux < 0
or not np.isfinite(flux):
316 pstamp = psfCandidate.getMaskedImage(pixKernelSize, pixKernelSize).clone()
318 self.log.warning(
"Could not get stamp image for psfCandidate: %s with kernel size: %s",
319 psfCandidate, pixKernelSize)
327 sample = psfSet.newSample()
328 sample.setCatindex(catindex)
329 sample.setExtindex(ext)
330 sample.setObjindex(i)
332 imArray = pstamp.getImage().getArray()
333 imArray[np.where(np.bitwise_and(pstamp.getMask().getArray(), badBits))] = \
335 sample.setVig(imArray)
338 sample.setBacknoise2(backnoise2)
342 sample.setFluxrad(sizes[i])
344 for j
in range(psfSet.getNcontext()):
345 sample.setContext(j, float(contextvalp[j][i]))
346 except Exception
as e:
347 self.log.error(
"Exception when processing sample at (%f,%f): %s", xc, yc, e)
350 psfSet.finiSample(sample)
356 with disp.Buffering():
357 disp.dot(
"o", xc, yc, ctype=afwDisplay.CYAN, size=4)
359 if psfSet.getNsample() == 0:
360 raise RuntimeError(
"No good PSF candidates to pass to PSFEx")
363 for i
in range(psfSet.getNcontext()):
364 cmin = contextvalp[i].
min()
365 cmax = contextvalp[i].
max()
366 psfSet.setContextScale(i, cmax - cmin)
367 psfSet.setContextOffset(i, (cmin + cmax)/2.0)
377 field = psfex.Field(
"Unknown")
378 field.addExt(exposure.getWcs(), exposure.getWidth(), exposure.getHeight(), psfSet.getNsample())
386 psfex.makeit(fields, sets)
387 psfs = field.getPsfs()
391 for i
in range(sets[0].getNsample()):
392 index = sets[0].getSample(i).getObjindex()
394 good_indices.append(index)
396 if flagKey
is not None:
397 for i, psfCandidate
in enumerate(psfCandidateList):
398 source = psfCandidate.getSource()
399 if i
in good_indices:
400 source.set(flagKey,
True)
402 xpos = np.array(xpos)
403 ypos = np.array(ypos)
404 numGoodStars = len(good_indices)
405 avgX, avgY = np.mean(xpos), np.mean(ypos)
413 if (ndim := psf.getNdim()) == 0:
415 num_available_stars=nCand,
416 num_good_stars=numGoodStars,
417 poly_ndim_initial=psfSet.getNcontext(),
418 poly_ndim_final=ndim,
425 assert psfCellSet
is not None
428 maUtils.showPsfSpatialCells(exposure, psfCellSet, showChi2=
True,
429 symb=
"o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
430 size=8, display=disp)
432 disp4 = afwDisplay.Display(frame=4)
433 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
434 normalize=normalizeResiduals,
435 showBadCandidates=showBadCandidates)
436 if displayPsfComponents:
437 disp6 = afwDisplay.Display(frame=6)
438 maUtils.showPsf(psf, display=disp6)
440 disp7 = afwDisplay.Display(frame=7)
441 maUtils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=
True)
442 disp.scale(
'linear', 0, 1)
448 if metadata
is not None:
449 metadata[
"spatialFitChi2"] = np.nan
450 metadata[
"numAvailStars"] = nCand
451 metadata[
"numGoodStars"] = numGoodStars
452 metadata[
"avgX"] = avgX
453 metadata[
"avgY"] = avgY
455 return psf, psfCellSet