1 from __future__
import absolute_import, division, print_function
2 from builtins
import zip
3 from builtins
import input
4 from builtins
import object
31 import matplotlib.pyplot
as plt
36 from lsst.afw.table
import SourceCatalog
37 from lsst.pipe.base
import Struct
38 import lsst.pex.config
as pexConfig
39 import lsst.afw.display.ds9
as ds9
40 from lsst.meas.algorithms
import BaseStarSelectorTask, starSelectorRegistry
41 from .
import psfexLib
42 from .psfex
import compute_fwhmrange
44 __all__ = [
"PsfexStarSelectorConfig",
"PsfexStarSelectorTask"]
48 fluxName = pexConfig.Field(
50 doc=
"Name of photometric flux key ",
51 default=
"base_PsfFlux",
53 fluxErrName = pexConfig.Field(
55 doc=
"Name of phot. flux err. key",
58 minFwhm = pexConfig.Field(
60 doc=
"Maximum allowed FWHM ",
63 maxFwhm = pexConfig.Field(
65 doc=
"Minimum allowed FWHM ",
68 maxFwhmVariability = pexConfig.Field(
70 doc=
"Allowed FWHM variability (1.0 = 100%)",
73 maxbad = pexConfig.Field(
75 doc=
"Max number of bad pixels ",
77 check=
lambda x: x >= 0,
79 maxbadflag = pexConfig.Field(
81 doc=
"Filter bad pixels? ",
84 maxellip = pexConfig.Field(
86 doc=
"Maximum (A-B)/(A+B) ",
88 check=
lambda x: x >= 0.0,
90 minsn = pexConfig.Field(
92 doc=
"Minimum S/N for candidates",
94 check=
lambda x: x >= 0.0,
98 pexConfig.Config.validate(self)
103 raise pexConfig.FieldValidationError(
"fluxErrName (%s) doesn't correspond to fluxName (%s)"
107 raise pexConfig.FieldValidationError(
"minFwhm (%f) > maxFwhm (%f)" % (self.
minFwhm, self.
maxFwhm))
111 "base_PixelFlags_flag_edge",
112 "base_PixelFlags_flag_saturatedCenter",
113 "base_PixelFlags_flag_crCenter",
114 "base_PixelFlags_flag_bad",
115 "base_PixelFlags_flag_suspectCenter",
122 """A class to handle key strokes with matplotlib displays"""
124 def __init__(self, axes, xs, ys, x, y, frames=[0]):
132 self.
cid = self.axes.figure.canvas.mpl_connect(
'key_press_event', self)
135 if ev.inaxes != self.
axes:
138 if ev.key
and ev.key
in (
"p"):
139 dist = np.hypot(self.
xs - ev.xdata, self.
ys - ev.ydata)
140 dist[np.where(np.isnan(dist))] = 1e30
142 which = np.where(dist == min(dist))
147 ds9.pan(x, y, frame=frame)
148 ds9.cmdBuffer.flush()
155 def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-',
167 axes = fig.add_axes((0.1, 0.1, 0.85, 0.80))
169 xmin = sorted(mag)[int(0.05*len(mag))]
170 xmax = sorted(mag)[int(0.95*len(mag))]
172 axes.set_xlim(-17.5, -13)
173 axes.set_xlim(xmin - 0.1*(xmax - xmin), xmax + 0.1*(xmax - xmin))
176 colors = [
"r", "g", "b", "c", "m", "k", ]
177 for k, mean
in enumerate(centers):
179 axes.plot(axes.get_xlim(), (mean, mean,),
"k%s" % ltype)
182 axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
183 color=colors[k%len(colors)])
185 l = (clusterId == -1)
186 axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
190 axes.set_xlabel(
"model")
191 axes.set_ylabel(
r"$\sqrt{I_{xx} + I_{yy}}$")
204 """!A star selector whose algorithm is not yet documented
206 @anchor PsfexStarSelectorTask_
208 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Contents Contents
210 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose
211 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize
212 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_IO
213 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Config
214 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Debug
216 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose Description
218 A star selector whose algorithm is not yet documented
220 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize Task initialisation
222 @copydoc \_\_init\_\_
224 @section meas_extensions_psfex_psfexStarSelectorStarSelector_IO Invoking the Task
226 Like all star selectors, the main method is `run`.
228 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Config Configuration parameters
230 See @ref PsfexStarSelectorConfig
232 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Debug Debug variables
234 PsfexStarSelectorTask has a debug dictionary with the following keys:
237 <dd>bool; if True display debug information
239 <dd>bool; if True display the exposure and spatial cells
240 <dt>plotFwhmHistogram
241 <dd>bool; if True plot histogram of FWHM
243 <dd>bool: if True plot the sources coloured by their flags
245 <dd>bool; if True plot why sources are rejected
248 For example, put something like:
252 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
253 if name.endswith("objectSizeStarSelector"):
255 di.displayExposure = True
256 di.plotFwhmHistogram = True
260 lsstDebug.Info = DebugInfo
262 into your `debug.py` file and run your task with the `--debug` flag.
264 ConfigClass = PsfexStarSelectorConfig
268 """!Select stars from source catalog
270 @param[in] exposure the exposure containing the sources
271 @param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog)
272 @param[in] matches astrometric matches; ignored by this star selector
274 @return a Struct containing:
275 - starCat a subset of sourceCat containing the selected stars
278 display = lsstDebug.Info(__name__).display
280 displayExposure = display
and \
281 lsstDebug.Info(__name__).displayExposure
282 plotFwhmHistogram = display
and plt
and \
283 lsstDebug.Info(__name__).plotFwhmHistogram
284 plotFlags = display
and plt
and \
285 lsstDebug.Info(__name__).plotFlags
286 plotRejection = display
and plt
and \
287 lsstDebug.Info(__name__).plotRejection
291 fluxName = self.config.fluxName
292 fluxErrName = self.config.fluxErrName
293 minFwhm = self.config.minFwhm
294 maxFwhm = self.config.maxFwhm
295 maxFwhmVariability = self.config.maxFwhmVariability
296 maxbad = self.config.maxbad
297 maxbadflag = self.config.maxbadflag
298 maxellip = self.config.maxellip
299 minsn = self.config.minsn
301 maxelong = (maxellip + 1.0)/(1.0 - maxellip)
if maxellip < 1.0
else 100
304 shape = sourceCat.getShapeDefinition()
305 ixx = sourceCat.get(
"%s.xx" % shape)
306 iyy = sourceCat.get(
"%s.yy" % shape)
308 fwhm = 2*np.sqrt(2*np.log(2))*np.sqrt(0.5*(ixx + iyy))
309 elong = 0.5*(ixx - iyy)/(ixx + iyy)
311 flux = sourceCat.get(fluxName)
312 fluxErr = sourceCat.get(fluxErrName)
313 sn = flux/np.where(fluxErr > 0, fluxErr, 1)
314 sn[fluxErr <= 0] = -psfexLib.BIG
317 for i, f
in enumerate(self.config.badFlags):
318 flags = np.bitwise_or(flags, np.where(sourceCat.get(f), 1 << i, 0))
322 good = np.logical_and(sn > minsn, np.logical_not(flags))
323 good = np.logical_and(good, elong < maxelong)
324 good = np.logical_and(good, fwhm >= minFwhm)
325 good = np.logical_and(good, fwhm < maxFwhm)
327 fwhmMode, fwhmMin, fwhmMax =
compute_fwhmrange(fwhm[good], maxFwhmVariability, minFwhm, maxFwhm,
328 plot=dict(fwhmHistogram=plotFwhmHistogram))
340 selectionVectors = []
341 selectionVectors.append((bad,
"flags %d" % sum(bad)))
345 bad = np.logical_or(bad, dbad)
347 selectionVectors.append((dbad,
"S/N %d" % sum(dbad)))
349 dbad = fwhm < fwhmMin
351 bad = np.logical_or(bad, dbad)
353 selectionVectors.append((dbad,
"fwhmMin %d" % sum(dbad)))
355 dbad = fwhm > fwhmMax
357 bad = np.logical_or(bad, dbad)
359 selectionVectors.append((dbad,
"fwhmMax %d" % sum(dbad)))
361 dbad = elong > maxelong
363 bad = np.logical_or(bad, dbad)
365 selectionVectors.append((dbad,
"elong %d" % sum(dbad)))
369 nbad = np.array([(v <= -psfexLib.BIG).sum()
for v
in vignet])
372 bad = np.logical_or(bad, dbad)
374 selectionVectors.append((dbad,
"badpix %d" % sum(dbad)))
376 good = np.logical_not(bad)
382 mi = exposure.getMaskedImage()
384 ds9.mtv(mi, frame=frame, title=
"PSF candidates")
386 with ds9.Buffering():
387 for i, source
in enumerate(sourceCat):
393 ds9.dot(
"+", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
394 frame=frame, ctype=ctype)
396 if plotFlags
or plotRejection:
397 imag = -2.5*np.log10(flux)
402 isSet = np.where(flags == 0x0)[0]
403 plt.plot(imag[isSet], fwhm[isSet],
'o', alpha=alpha, label=
"good")
405 for i, f
in enumerate(self.config.badFlags):
407 isSet = np.where(np.bitwise_and(flags, mask))[0]
409 if np.isfinite(imag[isSet] + fwhm[isSet]).any():
410 label = re.sub(
r"\_flag",
"",
411 re.sub(
r"^base\_",
"",
412 re.sub(
r"^.*base\_PixelFlags\_flag\_",
"", f)))
413 plt.plot(imag[isSet], fwhm[isSet],
'o', alpha=alpha, label=label)
415 for bad, label
in selectionVectors:
416 plt.plot(imag[bad], fwhm[bad],
'o', alpha=alpha, label=label)
418 plt.plot(imag[good], fwhm[good],
'o', color=
"black", label=
"selected")
419 [plt.axhline(_, color=
'red')
for _
in [fwhmMin, fwhmMax]]
420 plt.xlim(np.median(imag[good]) + 5*np.array([-1, 1]))
421 plt.ylim(fwhm[np.where(np.isfinite(fwhm + imag))].min(), 2*fwhmMax)
423 plt.xlabel(
"Instrumental %s Magnitude" % fluxName.split(
".")[-1].title())
425 title =
"PSFEX Star Selection"
426 plt.title(
"%s %d selected" % (title, sum(good)))
430 eventHandler =
EventHandler(plt.axes(), imag, fwhm, sourceCat.getX(), sourceCat.getY(),
433 if plotFlags
or plotRejection:
436 reply = input(
"continue? [y[es] h(elp) p(db) q(uit)] ").strip()
445 At this prompt, you can continue with almost any key; 'p' enters pdb,
446 'q' returns to the shell, and
452 If you put the cursor on a point in the matplotlib scatter plot and hit 'p' you'll see it in ds9.""")
453 elif reply[0] ==
"p":
456 elif reply[0] ==
'q':
461 starCat = SourceCatalog(sourceCat.schema)
462 for source, isGood
in zip(sourceCat, good):
464 starCat.append(source)
470 starSelectorRegistry.register(
"psfex", PsfexStarSelectorTask)
def selectStars
Select stars from source catalog.
A star selector whose algorithm is not yet documented.