331 """Return a selection of PSF candidates that represent likely stars.
333 A list of PSF candidates may be used by a PSF fitter to construct a PSF.
337 sourceCat : `lsst.afw.table.SourceCatalog`
338 Catalog of sources to select from.
339 This catalog must be contiguous in memory.
340 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
341 Ignored in this SourceSelector.
342 exposure : `lsst.afw.image.Exposure` or None
343 The exposure the catalog was built from; used to get the detector
344 to transform to TanPix, and for debug display.
348 struct : `lsst.pipe.base.Struct`
349 The struct contains the following data:
352 Boolean array of sources that were selected, same length as
353 sourceCat. (`numpy.ndarray` of `bool`)
355 if len(sourceCat) == 0:
356 raise RuntimeError(
"Input catalog for source selection is empty.")
367 detector = exposure.getDetector()
369 pixToTanPix = detector.getTransform(PIXELS, TAN_PIXELS)
373 flux = sourceCat[self.config.sourceFluxField]
374 fluxErr = sourceCat[self.config.sourceFluxField +
"Err"]
376 xx = numpy.empty(len(sourceCat))
377 xy = numpy.empty_like(xx)
378 yy = numpy.empty_like(xx)
379 for i, source
in enumerate(sourceCat):
380 Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
385 m.transform(linTransform)
386 Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()
388 xx[i], xy[i], yy[i] = Ixx, Ixy, Iyy
390 width = numpy.sqrt(0.5*(xx + yy))
391 with numpy.errstate(invalid=
"ignore"):
392 bad = reduce(
lambda x, y: numpy.logical_or(x, sourceCat[y]), self.config.badFlags,
False)
393 bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width)))
394 bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux)))
395 if self.config.doFluxLimit:
396 bad = numpy.logical_or(bad, flux < self.config.fluxMin)
397 if self.config.fluxMax > 0:
398 bad = numpy.logical_or(bad, flux > self.config.fluxMax)
399 if self.config.doSignalToNoiseLimit:
400 bad = numpy.logical_or(bad, flux/fluxErr < self.config.signalToNoiseMin)
401 if self.config.signalToNoiseMax > 0:
402 bad = numpy.logical_or(bad, flux/fluxErr > self.config.signalToNoiseMax)
403 bad = numpy.logical_or(bad, width < self.config.widthMin)
404 bad = numpy.logical_or(bad, width > self.config.widthMax)
405 good = numpy.logical_not(bad)
407 if not numpy.any(good):
408 raise RuntimeError(
"No objects passed our cuts for consideration as psf stars")
410 mag = -2.5*numpy.log10(flux[good])
418 import pickle
as pickle
421 pickleFile = os.path.expanduser(os.path.join(
"~",
"widths-%d.pkl" % _ii))
422 if not os.path.exists(pickleFile):
426 with open(pickleFile,
"wb")
as fd:
427 pickle.dump(mag, fd, -1)
428 pickle.dump(width, fd, -1)
430 centers, clusterId =
_kcenters(width, nCluster=4, useMedian=
True,
431 widthStdAllowed=self.config.widthStdAllowed)
433 if display
and plotMagSize:
434 fig =
plot(mag, width, centers, clusterId,
435 magType=self.config.sourceFluxField.split(
".")[-1].title(),
436 marker=
"+", markersize=3, markeredgewidth=
None, ltype=
':', clear=
True)
441 nsigma=self.config.nSigmaClip,
442 widthStdAllowed=self.config.widthStdAllowed)
444 if display
and plotMagSize:
445 plot(mag, width, centers, clusterId, marker=
"x", markersize=3, markeredgewidth=
None, clear=
False)
447 stellar = (clusterId == 0)
454 if display
and displayExposure:
455 disp = afwDisplay.Display(frame=frame)
456 disp.mtv(exposure.getMaskedImage(), title=
"PSF candidates")
459 eventHandler =
EventHandler(fig.get_axes()[0], mag, width,
460 sourceCat.getX()[good], sourceCat.getY()[good], frames=[frame])
466 reply = input(
"continue? [c h(elp) q(uit) p(db)] ").
strip()
475 We cluster the points; red are the stellar candidates and the other colours are other clusters.
476 Points labelled + are rejects from the cluster (only for cluster 0).
478 At this prompt, you can continue with almost any key; 'p' enters pdb, and 'h' prints this text
480 If displayExposure is true, you can put the cursor on a point and hit 'p' to see it in the
483 elif reply[0] ==
"p":
486 elif reply[0] ==
'q':
491 if display
and displayExposure:
492 mi = exposure.getMaskedImage()
493 with disp.Buffering():
494 for i, source
in enumerate(sourceCat):
496 ctype = afwDisplay.GREEN
498 ctype = afwDisplay.RED
500 disp.dot(
"+", source.getX() - mi.getX0(), source.getY() - mi.getY0(), ctype=ctype)
506 return Struct(selected=good)