LSSTApplications  19.0.0-14-gb0260a2+72efe9b372,20.0.0+7927753e06,20.0.0+8829bf0056,20.0.0+995114c5d2,20.0.0+b6f4b2abd1,20.0.0+bddc4f4cbe,20.0.0-1-g253301a+8829bf0056,20.0.0-1-g2b7511a+0d71a2d77f,20.0.0-1-g5b95a8c+7461dd0434,20.0.0-12-g321c96ea+23efe4bbff,20.0.0-16-gfab17e72e+fdf35455f6,20.0.0-2-g0070d88+ba3ffc8f0b,20.0.0-2-g4dae9ad+ee58a624b3,20.0.0-2-g61b8584+5d3db074ba,20.0.0-2-gb780d76+d529cf1a41,20.0.0-2-ged6426c+226a441f5f,20.0.0-2-gf072044+8829bf0056,20.0.0-2-gf1f7952+ee58a624b3,20.0.0-20-geae50cf+e37fec0aee,20.0.0-25-g3dcad98+544a109665,20.0.0-25-g5eafb0f+ee58a624b3,20.0.0-27-g64178ef+f1f297b00a,20.0.0-3-g4cc78c6+e0676b0dc8,20.0.0-3-g8f21e14+4fd2c12c9a,20.0.0-3-gbd60e8c+187b78b4b8,20.0.0-3-gbecbe05+48431fa087,20.0.0-38-ge4adf513+a12e1f8e37,20.0.0-4-g97dc21a+544a109665,20.0.0-4-gb4befbc+087873070b,20.0.0-4-gf910f65+5d3db074ba,20.0.0-5-gdfe0fee+199202a608,20.0.0-5-gfbfe500+d529cf1a41,20.0.0-6-g64f541c+d529cf1a41,20.0.0-6-g9a5b7a1+a1cd37312e,20.0.0-68-ga3f3dda+5fca18c6a4,20.0.0-9-g4aef684+e18322736b,w.2020.45
LSSTDataManagementBasePackage
psfexStarSelector.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import re
23 import sys
24 
25 import numpy as np
26 try:
27  import matplotlib.pyplot as plt
28  fig = None
29 except ImportError:
30  plt = None
31 
32 from lsst.pipe.base import Struct
33 import lsst.pex.config as pexConfig
34 import lsst.afw.display as afwDisplay
35 from lsst.meas.algorithms import BaseSourceSelectorTask, sourceSelectorRegistry
36 from . import psfexLib
37 from .psfex import compute_fwhmrange
38 
39 __all__ = ["PsfexStarSelectorConfig", "PsfexStarSelectorTask"]
40 
41 
42 class PsfexStarSelectorConfig(BaseSourceSelectorTask.ConfigClass):
43  fluxName = pexConfig.Field(
44  dtype=str,
45  doc="Name of photometric flux key ",
46  default="base_PsfFlux",
47  )
48  fluxErrName = pexConfig.Field(
49  dtype=str,
50  doc="Name of phot. flux err. key",
51  default="",
52  )
53  minFwhm = pexConfig.Field(
54  dtype=float,
55  doc="Maximum allowed FWHM ",
56  default=2,
57  )
58  maxFwhm = pexConfig.Field(
59  dtype=float,
60  doc="Minimum allowed FWHM ",
61  default=10,
62  )
63  maxFwhmVariability = pexConfig.Field(
64  dtype=float,
65  doc="Allowed FWHM variability (1.0 = 100%)",
66  default=0.2,
67  )
68  maxbad = pexConfig.Field(
69  dtype=int,
70  doc="Max number of bad pixels ",
71  default=0,
72  check=lambda x: x >= 0,
73  deprecated="This field has never worked and its code is gone. Will be removed after v21."
74  )
75  maxbadflag = pexConfig.Field(
76  dtype=bool,
77  doc="Filter bad pixels? ",
78  default=True,
79  deprecated="This field has never worked and its code is gone. Will be removed after v21."
80  )
81  maxellip = pexConfig.Field(
82  dtype=float,
83  doc="Maximum (A-B)/(A+B) ",
84  default=0.3,
85  check=lambda x: x >= 0.0,
86  )
87  minsn = pexConfig.Field(
88  dtype=float,
89  doc="Minimum S/N for candidates",
90  default=100,
91  check=lambda x: x >= 0.0,
92  )
93 
94  def validate(self):
95  pexConfig.Config.validate(self)
96 
97  if self.fluxErrName == "":
98  self.fluxErrName = self.fluxName + ".err"
99  elif self.fluxErrName != self.fluxName + ".err":
100  msg = f"fluxErrName ({self.fluxErrName}) doesn't correspond to fluxName ({self.fluxName})"
101  raise pexConfig.FieldValidationError(PsfexStarSelectorConfig.fluxName, self, msg)
102 
103  if self.minFwhm > self.maxFwhm:
104  raise pexConfig.FieldValidationError(PsfexStarSelectorConfig.minFwhm, self,
105  f"minFwhm ({self.minFwhm}) > maxFwhm ({self.maxFwhm})")
106 
107  def setDefaults(self):
108  self.badFlags = [
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",
114  "base_PsfFlux_flag",
115  # "parent", # actually this is a test on deblend_nChild
116  ]
117 
118 
119 class EventHandler():
120  """A class to handle key strokes with matplotlib displays
121  """
122 
123  def __init__(self, axes, xs, ys, x, y, frames=[0]):
124  self.axes = axes
125  self.xs = xs
126  self.ys = ys
127  self.x = x
128  self.y = y
129  self.frames = frames
130 
131  self.cid = self.axes.figure.canvas.mpl_connect('key_press_event', self)
132 
133  def __call__(self, ev):
134  if ev.inaxes != self.axes:
135  return
136 
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
140 
141  which = np.where(dist == min(dist))
142 
143  x = self.x[which][0]
144  y = self.y[which][0]
145  for frame in self.frames:
146  disp = afwDisplay.Display(frame=frame)
147  disp.pan(x, y)
148  disp.flush()
149  else:
150  pass
151 
152 
153 def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-',
154  clear=True):
155 
156  global fig
157  if not fig:
158  fig = plt.figure()
159  newFig = True
160  else:
161  newFig = False
162  if clear:
163  fig.clf()
164 
165  axes = fig.add_axes((0.1, 0.1, 0.85, 0.80))
166 
167  xmin = sorted(mag)[int(0.05*len(mag))]
168  xmax = sorted(mag)[int(0.95*len(mag))]
169 
170  axes.set_xlim(-17.5, -13)
171  axes.set_xlim(xmin - 0.1*(xmax - xmin), xmax + 0.1*(xmax - xmin))
172  axes.set_ylim(0, 10)
173 
174  colors = ["r", "g", "b", "c", "m", "k", ]
175  for k, mean in enumerate(centers):
176  if k == 0:
177  axes.plot(axes.get_xlim(), (mean, mean,), "k%s" % ltype)
178 
179  ll = (clusterId == k)
180  axes.plot(mag[ll], width[ll], marker, markersize=markersize, markeredgewidth=markeredgewidth,
181  color=colors[k%len(colors)])
182 
183  ll = (clusterId == -1)
184  axes.plot(mag[ll], width[ll], marker, markersize=markersize, markeredgewidth=markeredgewidth,
185  color='k')
186 
187  if newFig:
188  axes.set_xlabel("model")
189  axes.set_ylabel(r"$\sqrt{I_{xx} + I_{yy}}$")
190 
191  return fig
192 
193 
199 
200 
201 @pexConfig.registerConfigurable("psfex", sourceSelectorRegistry)
203  r"""A star selector whose algorithm is not yet documented.
204 
205  @anchor PsfexStarSelectorTask_
206 
207  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Contents Contents
208 
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
214 
215  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose Description
216 
217  A star selector whose algorithm is not yet documented
218 
219  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize Task initialisation
220 
221  @copydoc \_\_init\_\_
222 
223  @section meas_extensions_psfex_psfexStarSelectorStarSelector_IO Invoking the Task
224 
225  Like all star selectors, the main method is `run`.
226 
227  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Config Configuration parameters
228 
229  See @ref PsfexStarSelectorConfig
230 
231  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Debug Debug variables
232 
233  PsfexStarSelectorTask has a debug dictionary with the following keys:
234  <dl>
235  <dt>display
236  <dd>bool; if True display debug information
237  <dt>displayExposure
238  <dd>bool; if True display the exposure and spatial cells
239  <dt>plotFwhmHistogram
240  <dd>bool; if True plot histogram of FWHM
241  <dt>plotFlags
242  <dd>bool: if True plot the sources coloured by their flags
243  <dt>plotRejection
244  <dd>bool; if True plot why sources are rejected
245  </dl>
246 
247  For example, put something like:
248  @code{.py}
249  import lsstDebug
250  def DebugInfo(name):
251  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
252  if name.endswith("objectSizeStarSelector"):
253  di.display = True
254  di.displayExposure = True
255  di.plotFwhmHistogram = True
256 
257  return di
258 
259  lsstDebug.Info = DebugInfo
260  @endcode
261  into your `debug.py` file and run your task with the `--debug` flag.
262  """ # noqa: W505
263  ConfigClass = PsfexStarSelectorConfig
264  usesMatches = False # selectStars does not use its matches argument
265 
266  def selectSources(self, sourceCat, matches=None, exposure=None):
267  """Return a selection of psf-like objects.
268 
269  Parameters
270  ----------
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.
278 
279  Return
280  ------
281  struct : `lsst.pipe.base.Struct`
282  The struct contains the following data:
283 
284  - selected : `numpy.ndarray` of `bool``
285  Boolean array of sources that were selected, same length as
286  sourceCat.
287  """
288  import lsstDebug
289  display = lsstDebug.Info(__name__).display
290 
291  displayExposure = display and \
292  lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells
293  plotFwhmHistogram = display and plt and \
294  lsstDebug.Info(__name__).plotFwhmHistogram # Plot histogram of FWHM
295  plotFlags = display and plt and \
296  lsstDebug.Info(__name__).plotFlags # Plot the sources coloured by their flags
297  plotRejection = display and plt and \
298  lsstDebug.Info(__name__).plotRejection # Plot why sources are rejected
299  afwDisplay.setDefaultMaskTransparency(75)
300 
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
308 
309  maxelong = (maxellip + 1.0)/(1.0 - maxellip) if maxellip < 1.0 else 100
310 
311  # Unpack the catalogue
312  shape = sourceCat.getShapeDefinition()
313  ixx = sourceCat.get("%s.xx" % shape)
314  iyy = sourceCat.get("%s.yy" % shape)
315 
316  fwhm = 2*np.sqrt(2*np.log(2))*np.sqrt(0.5*(ixx + iyy))
317  elong = 0.5*(ixx - iyy)/(ixx + iyy)
318 
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
323 
324  flags = 0x0
325  for i, f in enumerate(self.config.badFlags):
326  flags = np.bitwise_or(flags, np.where(sourceCat.get(f), 1 << i, 0))
327  #
328  # Estimate the acceptable range of source widths
329  #
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)
334 
335  fwhmMode, fwhmMin, fwhmMax = compute_fwhmrange(fwhm[good], maxFwhmVariability, minFwhm, maxFwhm,
336  plot=dict(fwhmHistogram=plotFwhmHistogram))
337 
338  # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
339  #
340  # Here's select_candidates
341  #
342  # ---- Apply some selection over flags, fluxes...
343 
344  bad = (flags != 0)
345  # set.setBadFlags(int(sum(bad)))
346 
347  if plotRejection:
348  selectionVectors = []
349  selectionVectors.append((bad, "flags %d" % sum(bad)))
350 
351  dbad = sn < minsn
352  # set.setBadSN(int(sum(dbad)))
353  bad = np.logical_or(bad, dbad)
354  if plotRejection:
355  selectionVectors.append((dbad, "S/N %d" % sum(dbad)))
356 
357  dbad = fwhm < fwhmMin
358  # set.setBadFrmin(int(sum(dbad)))
359  bad = np.logical_or(bad, dbad)
360  if plotRejection:
361  selectionVectors.append((dbad, "fwhmMin %d" % sum(dbad)))
362 
363  dbad = fwhm > fwhmMax
364  # set.setBadFrmax(int(sum(dbad)))
365  bad = np.logical_or(bad, dbad)
366  if plotRejection:
367  selectionVectors.append((dbad, "fwhmMax %d" % sum(dbad)))
368 
369  dbad = elong > maxelong
370  # set.setBadElong(int(sum(dbad)))
371  bad = np.logical_or(bad, dbad)
372  if plotRejection:
373  selectionVectors.append((dbad, "elong %d" % sum(dbad)))
374 
375  good = np.logical_not(bad)
376  #
377  # We know enough to plot, if so requested
378  #
379  frame = 0
380  if displayExposure:
381  mi = exposure.getMaskedImage()
382  disp = afwDisplay.Display(frame=frame)
383  disp.mtv(mi, title="PSF candidates")
384 
385  with disp.Buffering():
386  for i, source in enumerate(sourceCat):
387  if good[i]:
388  ctype = afwDisplay.GREEN # star candidate
389  else:
390  ctype = afwDisplay.RED # not star
391 
392  disp.dot("+", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
393  ctype=ctype)
394 
395  if plotFlags or plotRejection:
396  imag = -2.5*np.log10(flux)
397  plt.clf()
398 
399  alpha = 0.5
400  if plotFlags:
401  isSet = np.where(flags == 0x0)[0]
402  plt.plot(imag[isSet], fwhm[isSet], 'o', alpha=alpha, label="good")
403 
404  for i, f in enumerate(self.config.badFlags):
405  mask = 1 << i
406  isSet = np.where(np.bitwise_and(flags, mask))[0]
407  if isSet.any():
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)
413  else:
414  for bad, label in selectionVectors:
415  plt.plot(imag[bad], fwhm[bad], 'o', alpha=alpha, label=label)
416 
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)
421  plt.legend(loc=2)
422  plt.xlabel("Instrumental %s Magnitude" % fluxName.split(".")[-1].title())
423  plt.ylabel("fwhm")
424  title = "PSFEX Star Selection"
425  plt.title("%s %d selected" % (title, sum(good)))
426 
427  if displayExposure:
428  global eventHandler
429  eventHandler = EventHandler(plt.axes(), imag, fwhm, sourceCat.getX(), sourceCat.getY(),
430  frames=[frame])
431 
432  if plotFlags or plotRejection:
433  while True:
434  try:
435  reply = input("continue? [y[es] h(elp) p(db) q(uit)] ").strip()
436  except EOFError:
437  reply = "y"
438 
439  if not reply:
440  reply = "y"
441 
442  if reply[0] == "h":
443  print("""\
444 At this prompt, you can continue with almost any key; 'p' enters pdb,
445  'q' returns to the shell, and
446  'h' prints this text
447 """, end=' ')
448 
449  if displayExposure:
450  print("""
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":
453  import pdb
454  pdb.set_trace()
455  elif reply[0] == 'q':
456  sys.exit(1)
457  else:
458  break
459 
460  return Struct(selected=good)
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.cid
cid
Definition: psfexStarSelector.py:131
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorTask
Definition: psfexStarSelector.py:202
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.maxFwhm
maxFwhm
Definition: psfexStarSelector.py:58
lsst::afw.display
Definition: __init__.py:1
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.xs
xs
Definition: psfexStarSelector.py:125
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.x
x
Definition: psfexStarSelector.py:127
strip
bool strip
Definition: fits.cc:911
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.minFwhm
minFwhm
Definition: psfexStarSelector.py:53
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.badFlags
badFlags
Definition: psfexStarSelector.py:108
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorTask.selectSources
def selectSources(self, sourceCat, matches=None, exposure=None)
Definition: psfexStarSelector.py:266
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.axes
axes
Definition: psfexStarSelector.py:124
lsst.pipe.base.struct.Struct
Definition: struct.py:26
lsstDebug.Info
Definition: lsstDebug.py:28
lsst.pex.config
Definition: __init__.py:1
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.frames
frames
Definition: psfexStarSelector.py:129
lsst::meas::algorithms.sourceSelector.BaseSourceSelectorTask
Definition: sourceSelector.py:45
lsst::geom::any
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
Definition: CoordinateExpr.h:89
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.ys
ys
Definition: psfexStarSelector.py:126
lsst::meas::extensions::psfex.psfex.compute_fwhmrange
def compute_fwhmrange(fwhm, maxvar, minin, maxin, plot=dict(fwhmHistogram=False))
Definition: psfex.py:22
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler
Definition: psfexStarSelector.py:119
lsst::meas::extensions::psfex.psfexStarSelector.plot
def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-', clear=True)
Definition: psfexStarSelector.py:153
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.y
y
Definition: psfexStarSelector.py:128
min
int min
Definition: BoundedField.cc:103
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.fluxName
fluxName
Definition: psfexStarSelector.py:43
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.validate
def validate(self)
Definition: psfexStarSelector.py:94
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.setDefaults
def setDefaults(self)
Definition: psfexStarSelector.py:107
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig.fluxErrName
fluxErrName
Definition: psfexStarSelector.py:48
lsst.pipe.base
Definition: __init__.py:1
lsst::meas::algorithms
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
Definition: CoaddBoundedField.h:34
lsst::meas::extensions::psfex.psfexStarSelector.PsfexStarSelectorConfig
Definition: psfexStarSelector.py:42
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.__init__
def __init__(self, axes, xs, ys, x, y, frames=[0])
Definition: psfexStarSelector.py:123
lsst::meas::extensions::psfex.psfexStarSelector.EventHandler.__call__
def __call__(self, ev)
Definition: psfexStarSelector.py:133