213 def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
214 """Determine a PSFEX PSF model for an exposure given a list of PSF
219 exposure: `lsst.afw.image.Exposure`
220 Exposure containing the PSF candidates.
221 psfCandidateList: iterable of `lsst.meas.algorithms.PsfCandidate`
222 Sequence of PSF candidates typically obtained by detecting sources
223 and then running them through a star selector.
224 metadata: metadata, optional
225 A home for interesting tidbits of information.
226 flagKey: `lsst.afw.table.Key`, optional
227 Schema key used to mark sources actually used in PSF determination.
231 psf: `lsst.meas.extensions.psfex.PsfexPsf`
234 psfCandidateList = self.downsampleCandidates(psfCandidateList)
238 displayExposure = display
and \
240 displayPsfComponents = display
and \
242 showBadCandidates = display
and \
244 displayResiduals = display
and \
246 displayPsfMosaic = display
and \
249 afwDisplay.setDefaultMaskTransparency(75)
252 mi = exposure.getMaskedImage()
254 nCand = len(psfCandidateList)
262 bbox = mi.getBBox(afwImage.PARENT)
263 psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY)
267 sizes = np.empty(nCand)
268 for i, psfCandidate
in enumerate(psfCandidateList):
271 psfCellSet.insertCandidate(psfCandidate)
272 except Exception
as e:
273 self.log.error(
"Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
276 source = psfCandidate.getSource()
277 quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
278 rmsSize = quad.getTraceRadius()
281 pixKernelSize = self.config.stampSize
282 actualKernelSize = int(2*np.floor(0.5*pixKernelSize/self.config.samplingSize) + 1)
285 rms = np.median(sizes)
286 self.log.debug(
"Median PSF RMS size=%.2f pixels (\"FWHM\"=%.2f)",
287 rms, 2*np.sqrt(2*np.log(2))*rms)
289 self.log.trace(
"Psfex Kernel size=%.2f, Image Kernel Size=%.2f", actualKernelSize, pixKernelSize)
295 defaultsPath = ResourcePath(
296 "resource://lsst.meas.extensions.psfex/resources/config/default-lsst.psfex"
298 args_md = dafBase.PropertySet()
299 args_md.set(
"BASIS_TYPE", str(self.config.psfexBasis))
300 args_md.set(
"PSFVAR_DEGREES", str(self.config.spatialOrder))
301 args_md.set(
"PSF_SIZE", str(actualKernelSize))
302 args_md.set(
"PSF_SAMPLING", str(self.config.samplingSize))
303 args_md.set(
"PHOTFLUX_KEY", str(self.config.photometricFluxField))
304 args_md.set(
"PHOTFLUXERR_KEY", str(self.config.photometricFluxField) +
"Err")
308 with defaultsPath.as_local()
as defaultsFile:
309 prefs = psfex.Prefs(defaultsFile.ospath, args_md)
310 prefs.setCommandLine([])
311 prefs.addCatalog(
"psfexPsfDeterminer")
314 principalComponentExclusionFlag = bool(bool(psfex.Context.REMOVEHIDDEN)
315 if False else psfex.Context.KEEPHIDDEN)
316 context = psfex.Context(prefs.getContextName(), prefs.getContextGroup(),
317 prefs.getGroupDeg(), principalComponentExclusionFlag)
318 psfSet = psfex.Set(context)
319 psfSet.setVigSize(pixKernelSize, pixKernelSize)
320 psfSet.setFwhm(2*np.sqrt(2*np.log(2))*np.median(sizes))
321 psfSet.setRecentroid(self.config.recentroid)
324 backnoise2 = afwMath.makeStatistics(mi.getImage(), afwMath.VARIANCECLIP).getValue()
325 ccd = exposure.getDetector()
327 gain = np.mean(np.array([a.getGain()
for a
in ccd]))
330 self.log.warning(
"Setting gain to %g", gain)
333 for i, key
in enumerate(context.getName()):
336 contextvalp.append(exposure.getMetadata().getScalar(key[1:]))
337 except KeyError
as e:
338 raise RuntimeError(
"%s parameter not found in the header of %s" %
339 (key[1:], prefs.getContextName()))
from e
342 contextvalp.append(np.array([psfCandidateList[_].getSource().get(key)
343 for _
in range(nCand)]))
344 except KeyError
as e:
345 raise RuntimeError(
"%s parameter not found" % (key,))
from e
346 psfSet.setContextname(i, key)
351 disp = afwDisplay.Display(frame=frame)
352 disp.mtv(exposure, title=
"psf determination")
354 badBits = mi.getMask().getPlaneBitMask(self.config.badMaskBits)
355 fluxName = prefs.getPhotfluxRkey()
356 fluxFlagName = fluxName.rpartition(
"_")[0] +
"_flag"
359 for i, psfCandidate
in enumerate(psfCandidateList):
360 source = psfCandidate.getSource()
363 xc, yc = source.getX(), source.getY()
364 if not np.isfinite(xc)
or not np.isfinite(yc):
366 if self.config.rejectFlaggedCentroids:
368 if source.get(
"slot_Centroid_flag"):
371 if source.get(fluxFlagName):
374 flux = source.get(fluxName)
375 if flux < 0
or not np.isfinite(flux):
379 pstamp = psfCandidate.getMaskedImage(pixKernelSize, pixKernelSize).clone()
380 except pexExcept.LengthError:
381 self.log.warning(
"Could not get stamp image for psfCandidate: %s with kernel size: %s",
382 psfCandidate, pixKernelSize)
390 sample = psfSet.newSample()
391 sample.setCatindex(catindex)
392 sample.setExtindex(ext)
393 sample.setObjindex(i)
395 imArray = pstamp.getImage().getArray()
396 imArray[np.where(np.bitwise_and(pstamp.getMask().getArray(), badBits))] = \
398 sample.setVig(imArray)
401 sample.setBacknoise2(backnoise2)
405 sample.setFluxrad(sizes[i])
407 for j
in range(psfSet.getNcontext()):
408 sample.setContext(j, float(contextvalp[j][i]))
409 except Exception
as e:
410 self.log.error(
"Exception when processing sample at (%f,%f): %s", xc, yc, e)
413 psfSet.finiSample(sample)
419 with disp.Buffering():
420 disp.dot(
"o", xc, yc, ctype=afwDisplay.CYAN, size=4)
422 if psfSet.getNsample() == 0:
426 for i
in range(psfSet.getNcontext()):
427 cmin = contextvalp[i].min()
428 cmax = contextvalp[i].max()
429 psfSet.setContextScale(i, cmax - cmin)
430 psfSet.setContextOffset(i, (cmin + cmax)/2.0)
440 field = psfex.Field(
"Unknown")
441 field.addExt(exposure.getWcs(), exposure.getWidth(), exposure.getHeight(), psfSet.getNsample())
449 psfex.makeit(fields, sets)
450 psfs = field.getPsfs()
454 for i
in range(sets[0].getNsample()):
455 index = sets[0].getSample(i).getObjindex()
457 good_indices.append(index)
459 if flagKey
is not None:
460 for i, psfCandidate
in enumerate(psfCandidateList):
461 source = psfCandidate.getSource()
462 if i
in good_indices:
463 source.set(flagKey,
True)
465 xpos = np.array(xpos)
466 ypos = np.array(ypos)
467 numGoodStars = len(good_indices)
468 avgX, avgY = np.mean(xpos), np.mean(ypos)
476 if (ndim := psf.getNdim()) == 0:
478 num_available_stars=nCand,
479 num_good_stars=numGoodStars,
480 poly_ndim_initial=psfSet.getNcontext(),
481 poly_ndim_final=ndim,
488 assert psfCellSet
is not None
491 maUtils.showPsfSpatialCells(exposure, psfCellSet, showChi2=
True,
492 symb=
"o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
493 size=8, display=disp)
495 disp4 = afwDisplay.Display(frame=4)
496 maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
497 normalize=normalizeResiduals,
498 showBadCandidates=showBadCandidates)
499 if displayPsfComponents:
500 disp6 = afwDisplay.Display(frame=6)
501 maUtils.showPsf(psf, display=disp6)
503 disp7 = afwDisplay.Display(frame=7)
504 maUtils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=
True)
505 disp.scale(
'linear', 0, 1)
511 if metadata
is not None:
512 metadata[
"spatialFitChi2"] = np.nan
513 metadata[
"numAvailStars"] = nCand
514 metadata[
"numGoodStars"] = numGoodStars
515 metadata[
"avgX"] = avgX
516 metadata[
"avgY"] = avgY
518 return psf, psfCellSet