27 import matplotlib.pyplot
as plt
36 from .
import psfexLib
37 from .psfex
import compute_fwhmrange
39 __all__ = [
"PsfexStarSelectorConfig",
"PsfexStarSelectorTask"]
43 fluxName = pexConfig.Field(
45 doc=
"Name of photometric flux key ",
46 default=
"base_PsfFlux",
48 fluxErrName = pexConfig.Field(
50 doc=
"Name of phot. flux err. key",
53 minFwhm = pexConfig.Field(
55 doc=
"Maximum allowed FWHM ",
58 maxFwhm = pexConfig.Field(
60 doc=
"Minimum allowed FWHM ",
63 maxFwhmVariability = pexConfig.Field(
65 doc=
"Allowed FWHM variability (1.0 = 100%)",
68 maxbad = pexConfig.Field(
70 doc=
"Max number of bad pixels ",
72 check=
lambda x: x >= 0,
73 deprecated=
"This field has never worked and its code is gone. Will be removed after v21."
75 maxbadflag = pexConfig.Field(
77 doc=
"Filter bad pixels? ",
79 deprecated=
"This field has never worked and its code is gone. Will be removed after v21."
81 maxellip = pexConfig.Field(
83 doc=
"Maximum (A-B)/(A+B) ",
85 check=
lambda x: x >= 0.0,
87 minsn = pexConfig.Field(
89 doc=
"Minimum S/N for candidates",
91 check=
lambda x: x >= 0.0,
95 pexConfig.Config.validate(self)
100 msg = f
"fluxErrName ({self.fluxErrName}) doesn't correspond to fluxName ({self.fluxName})"
101 raise pexConfig.FieldValidationError(PsfexStarSelectorConfig.fluxName, self, msg)
104 raise pexConfig.FieldValidationError(PsfexStarSelectorConfig.minFwhm, self,
105 f
"minFwhm ({self.minFwhm}) > maxFwhm ({self.maxFwhm})")
109 "base_PixelFlags_flag_edge",
110 "base_PixelFlags_flag_saturatedCenter",
111 "base_PixelFlags_flag_crCenter",
112 "base_PixelFlags_flag_bad",
113 "base_PixelFlags_flag_suspectCenter",
120 """A class to handle key strokes with matplotlib displays
123 def __init__(self, axes, xs, ys, x, y, frames=[0]):
131 self.
cid = self.
axes.figure.canvas.mpl_connect(
'key_press_event', self)
134 if ev.inaxes != self.
axes:
137 if ev.key
and ev.key
in (
"p"):
138 dist = np.hypot(self.
xs - ev.xdata, self.
ys - ev.ydata)
139 dist[np.where(np.isnan(dist))] = 1e30
141 which = np.where(dist ==
min(dist))
146 disp = afwDisplay.Display(frame=frame)
153 def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-',
165 axes = fig.add_axes((0.1, 0.1, 0.85, 0.80))
167 xmin = sorted(mag)[int(0.05*len(mag))]
168 xmax = sorted(mag)[int(0.95*len(mag))]
170 axes.set_xlim(-17.5, -13)
171 axes.set_xlim(xmin - 0.1*(xmax - xmin), xmax + 0.1*(xmax - xmin))
174 colors = [
"r",
"g",
"b",
"c",
"m",
"k", ]
175 for k, mean
in enumerate(centers):
177 axes.plot(axes.get_xlim(), (mean, mean,),
"k%s" % ltype)
179 ll = (clusterId == k)
180 axes.plot(mag[ll], width[ll], marker, markersize=markersize, markeredgewidth=markeredgewidth,
181 color=colors[k%len(colors)])
183 ll = (clusterId == -1)
184 axes.plot(mag[ll], width[ll], marker, markersize=markersize, markeredgewidth=markeredgewidth,
188 axes.set_xlabel(
"model")
189 axes.set_ylabel(
r"$\sqrt{I_{xx} + I_{yy}}$")
201 @pexConfig.registerConfigurable(
"psfex", sourceSelectorRegistry)
203 r"""A star selector whose algorithm is not yet documented.
205 @anchor PsfexStarSelectorTask_
207 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Contents Contents
209 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose
210 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize
211 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_IO
212 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Config
213 - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Debug
215 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose Description
217 A star selector whose algorithm is not yet documented
219 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize Task initialisation
221 @copydoc \_\_init\_\_
223 @section meas_extensions_psfex_psfexStarSelectorStarSelector_IO Invoking the Task
225 Like all star selectors, the main method is `run`.
227 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Config Configuration parameters
229 See @ref PsfexStarSelectorConfig
231 @section meas_extensions_psfex_psfexStarSelectorStarSelector_Debug Debug variables
233 PsfexStarSelectorTask has a debug dictionary with the following keys:
236 <dd>bool; if True display debug information
238 <dd>bool; if True display the exposure and spatial cells
239 <dt>plotFwhmHistogram
240 <dd>bool; if True plot histogram of FWHM
242 <dd>bool: if True plot the sources coloured by their flags
244 <dd>bool; if True plot why sources are rejected
247 For example, put something like:
251 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
252 if name.endswith("objectSizeStarSelector"):
254 di.displayExposure = True
255 di.plotFwhmHistogram = True
259 lsstDebug.Info = DebugInfo
261 into your `debug.py` file and run your task with the `--debug` flag.
263 ConfigClass = PsfexStarSelectorConfig
267 """Return a selection of psf-like objects.
271 sourceCat : `lsst.afw.table.SourceCatalog`
272 Catalog of sources to select from.
273 This catalog must be contiguous in memory.
274 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
275 Ignored by this source selector.
276 exposure : `lsst.afw.image.Exposure` or None
277 The exposure the catalog was built from; used for debug display.
281 struct : `lsst.pipe.base.Struct`
282 The struct contains the following data:
284 - selected : `numpy.ndarray` of `bool``
285 Boolean array of sources that were selected, same length as
291 displayExposure = display
and \
293 plotFwhmHistogram = display
and plt
and \
295 plotFlags = display
and plt
and \
297 plotRejection = display
and plt
and \
299 afwDisplay.setDefaultMaskTransparency(75)
301 fluxName = self.config.fluxName
302 fluxErrName = self.config.fluxErrName
303 minFwhm = self.config.minFwhm
304 maxFwhm = self.config.maxFwhm
305 maxFwhmVariability = self.config.maxFwhmVariability
306 maxellip = self.config.maxellip
307 minsn = self.config.minsn
309 maxelong = (maxellip + 1.0)/(1.0 - maxellip)
if maxellip < 1.0
else 100
312 shape = sourceCat.getShapeDefinition()
313 ixx = sourceCat.get(
"%s.xx" % shape)
314 iyy = sourceCat.get(
"%s.yy" % shape)
316 fwhm = 2*np.sqrt(2*np.log(2))*np.sqrt(0.5*(ixx + iyy))
317 elong = 0.5*(ixx - iyy)/(ixx + iyy)
319 flux = sourceCat.get(fluxName)
320 fluxErr = sourceCat.get(fluxErrName)
321 sn = flux/np.where(fluxErr > 0, fluxErr, 1)
322 sn[fluxErr <= 0] = -psfexLib.BIG
325 for i, f
in enumerate(self.config.badFlags):
326 flags = np.bitwise_or(flags, np.where(sourceCat.get(f), 1 << i, 0))
330 good = np.logical_and(sn > minsn, np.logical_not(flags))
331 good = np.logical_and(good, elong < maxelong)
332 good = np.logical_and(good, fwhm >= minFwhm)
333 good = np.logical_and(good, fwhm < maxFwhm)
335 fwhmMode, fwhmMin, fwhmMax =
compute_fwhmrange(fwhm[good], maxFwhmVariability, minFwhm, maxFwhm,
336 plot=dict(fwhmHistogram=plotFwhmHistogram))
348 selectionVectors = []
349 selectionVectors.append((bad,
"flags %d" % sum(bad)))
353 bad = np.logical_or(bad, dbad)
355 selectionVectors.append((dbad,
"S/N %d" % sum(dbad)))
357 dbad = fwhm < fwhmMin
359 bad = np.logical_or(bad, dbad)
361 selectionVectors.append((dbad,
"fwhmMin %d" % sum(dbad)))
363 dbad = fwhm > fwhmMax
365 bad = np.logical_or(bad, dbad)
367 selectionVectors.append((dbad,
"fwhmMax %d" % sum(dbad)))
369 dbad = elong > maxelong
371 bad = np.logical_or(bad, dbad)
373 selectionVectors.append((dbad,
"elong %d" % sum(dbad)))
375 good = np.logical_not(bad)
381 mi = exposure.getMaskedImage()
382 disp = afwDisplay.Display(frame=frame)
383 disp.mtv(mi, title=
"PSF candidates")
385 with disp.Buffering():
386 for i, source
in enumerate(sourceCat):
388 ctype = afwDisplay.GREEN
390 ctype = afwDisplay.RED
392 disp.dot(
"+", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
395 if plotFlags
or plotRejection:
396 imag = -2.5*np.log10(flux)
401 isSet = np.where(flags == 0x0)[0]
402 plt.plot(imag[isSet], fwhm[isSet],
'o', alpha=alpha, label=
"good")
404 for i, f
in enumerate(self.config.badFlags):
406 isSet = np.where(np.bitwise_and(flags, mask))[0]
408 if np.isfinite(imag[isSet] + fwhm[isSet]).
any():
409 label = re.sub(
r"\_flag",
"",
410 re.sub(
r"^base\_",
"",
411 re.sub(
r"^.*base\_PixelFlags\_flag\_",
"", f)))
412 plt.plot(imag[isSet], fwhm[isSet],
'o', alpha=alpha, label=label)
414 for bad, label
in selectionVectors:
415 plt.plot(imag[bad], fwhm[bad],
'o', alpha=alpha, label=label)
417 plt.plot(imag[good], fwhm[good],
'o', color=
"black", label=
"selected")
418 [plt.axhline(_, color=
'red')
for _
in [fwhmMin, fwhmMax]]
419 plt.xlim(np.median(imag[good]) + 5*np.array([-1, 1]))
420 plt.ylim(fwhm[np.where(np.isfinite(fwhm + imag))].
min(), 2*fwhmMax)
422 plt.xlabel(
"Instrumental %s Magnitude" % fluxName.split(
".")[-1].title())
424 title =
"PSFEX Star Selection"
425 plt.title(
"%s %d selected" % (title, sum(good)))
429 eventHandler =
EventHandler(plt.axes(), imag, fwhm, sourceCat.getX(), sourceCat.getY(),
432 if plotFlags
or plotRejection:
435 reply = input(
"continue? [y[es] h(elp) p(db) q(uit)] ").
strip()
444 At this prompt, you can continue with almost any key; 'p' enters pdb,
445 'q' returns to the shell, and
451 If you put the cursor on a point in the matplotlib scatter plot and hit 'p' you'll see it in ds9.""")
452 elif reply[0] ==
"p":
455 elif reply[0] ==
'q':
460 return Struct(selected=good)