LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
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.ds9 as ds9
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  )
74  maxbadflag = pexConfig.Field(
75  dtype=bool,
76  doc="Filter bad pixels? ",
77  default=True
78  )
79  maxellip = pexConfig.Field(
80  dtype=float,
81  doc="Maximum (A-B)/(A+B) ",
82  default=0.3,
83  check=lambda x: x >= 0.0,
84  )
85  minsn = pexConfig.Field(
86  dtype=float,
87  doc="Minimum S/N for candidates",
88  default=100,
89  check=lambda x: x >= 0.0,
90  )
91 
92  def validate(self):
93  pexConfig.Config.validate(self)
94 
95  if self.fluxErrName == "":
96  self.fluxErrName = self.fluxName + ".err"
97  elif self.fluxErrName != self.fluxName + ".err":
98  raise pexConfig.FieldValidationError("fluxErrName (%s) doesn't correspond to fluxName (%s)"
99  % (self.fluxErrName, self.fluxName))
100 
101  if self.minFwhm > self.maxFwhm:
102  raise pexConfig.FieldValidationError("minFwhm (%f) > maxFwhm (%f)" % (self.minFwhm, self.maxFwhm))
103 
104  def setDefaults(self):
105  self.badFlags = [
106  "base_PixelFlags_flag_edge",
107  "base_PixelFlags_flag_saturatedCenter",
108  "base_PixelFlags_flag_crCenter",
109  "base_PixelFlags_flag_bad",
110  "base_PixelFlags_flag_suspectCenter",
111  "base_PsfFlux_flag",
112  #"parent", # actually this is a test on deblend_nChild
113  ]
114 
115 
116 class EventHandler():
117  """A class to handle key strokes with matplotlib displays
118  """
119 
120  def __init__(self, axes, xs, ys, x, y, frames=[0]):
121  self.axes = axes
122  self.xs = xs
123  self.ys = ys
124  self.x = x
125  self.y = y
126  self.frames = frames
127 
128  self.cid = self.axes.figure.canvas.mpl_connect('key_press_event', self)
129 
130  def __call__(self, ev):
131  if ev.inaxes != self.axes:
132  return
133 
134  if ev.key and ev.key in ("p"):
135  dist = np.hypot(self.xs - ev.xdata, self.ys - ev.ydata)
136  dist[np.where(np.isnan(dist))] = 1e30
137 
138  which = np.where(dist == min(dist))
139 
140  x = self.x[which][0]
141  y = self.y[which][0]
142  for frame in self.frames:
143  ds9.pan(x, y, frame=frame)
144  ds9.cmdBuffer.flush()
145  else:
146  pass
147 
148 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
149 
150 
151 def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-',
152  clear=True):
153 
154  global fig
155  if not fig:
156  fig = plt.figure()
157  newFig = True
158  else:
159  newFig = False
160  if clear:
161  fig.clf()
162 
163  axes = fig.add_axes((0.1, 0.1, 0.85, 0.80))
164 
165  xmin = sorted(mag)[int(0.05*len(mag))]
166  xmax = sorted(mag)[int(0.95*len(mag))]
167 
168  axes.set_xlim(-17.5, -13)
169  axes.set_xlim(xmin - 0.1*(xmax - xmin), xmax + 0.1*(xmax - xmin))
170  axes.set_ylim(0, 10)
171 
172  colors = ["r", "g", "b", "c", "m", "k", ]
173  for k, mean in enumerate(centers):
174  if k == 0:
175  axes.plot(axes.get_xlim(), (mean, mean,), "k%s" % ltype)
176 
177  l = (clusterId == k)
178  axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
179  color=colors[k%len(colors)])
180 
181  l = (clusterId == -1)
182  axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
183  color='k')
184 
185  if newFig:
186  axes.set_xlabel("model")
187  axes.set_ylabel(r"$\sqrt{I_{xx} + I_{yy}}$")
188 
189  return fig
190 
191 
197 
198 
199 @pexConfig.registerConfigurable("psfex", sourceSelectorRegistry)
201  """A star selector whose algorithm is not yet documented.
202 
203  @anchor PsfexStarSelectorTask_
204 
205  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Contents Contents
206 
207  - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose
208  - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize
209  - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_IO
210  - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Config
211  - @ref meas_extensions_psfex_psfexStarSelectorStarSelector_Debug
212 
213  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Purpose Description
214 
215  A star selector whose algorithm is not yet documented
216 
217  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Initialize Task initialisation
218 
219  @copydoc \_\_init\_\_
220 
221  @section meas_extensions_psfex_psfexStarSelectorStarSelector_IO Invoking the Task
222 
223  Like all star selectors, the main method is `run`.
224 
225  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Config Configuration parameters
226 
227  See @ref PsfexStarSelectorConfig
228 
229  @section meas_extensions_psfex_psfexStarSelectorStarSelector_Debug Debug variables
230 
231  PsfexStarSelectorTask has a debug dictionary with the following keys:
232  <dl>
233  <dt>display
234  <dd>bool; if True display debug information
235  <dt>displayExposure
236  <dd>bool; if True display the exposure and spatial cells
237  <dt>plotFwhmHistogram
238  <dd>bool; if True plot histogram of FWHM
239  <dt>plotFlags
240  <dd>bool: if True plot the sources coloured by their flags
241  <dt>plotRejection
242  <dd>bool; if True plot why sources are rejected
243  </dl>
244 
245  For example, put something like:
246  @code{.py}
247  import lsstDebug
248  def DebugInfo(name):
249  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
250  if name.endswith("objectSizeStarSelector"):
251  di.display = True
252  di.displayExposure = True
253  di.plotFwhmHistogram = True
254 
255  return di
256 
257  lsstDebug.Info = DebugInfo
258  @endcode
259  into your `debug.py` file and run your task with the `--debug` flag.
260  """
261  ConfigClass = PsfexStarSelectorConfig
262  usesMatches = False # selectStars does not use its matches argument
263 
264  def selectSources(self, sourceCat, matches=None, exposure=None):
265  """Return a selection of psf-like objects.
266 
267  Parameters
268  ----------
269  sourceCat : `lsst.afw.table.SourceCatalog`
270  Catalog of sources to select from.
271  This catalog must be contiguous in memory.
272  matches : `list` of `lsst.afw.table.ReferenceMatch` or None
273  Ignored by this source selector.
274  exposure : `lsst.afw.image.Exposure` or None
275  The exposure the catalog was built from; used for debug display.
276 
277  Return
278  ------
279  struct : `lsst.pipe.base.Struct`
280  The struct contains the following data:
281 
282  - selected : `numpy.ndarray` of `bool``
283  Boolean array of sources that were selected, same length as
284  sourceCat.
285  """
286  import lsstDebug
287  display = lsstDebug.Info(__name__).display
288 
289  displayExposure = display and \
290  lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells
291  plotFwhmHistogram = display and plt and \
292  lsstDebug.Info(__name__).plotFwhmHistogram # Plot histogram of FWHM
293  plotFlags = display and plt and \
294  lsstDebug.Info(__name__).plotFlags # Plot the sources coloured by their flags
295  plotRejection = display and plt and \
296  lsstDebug.Info(__name__).plotRejection # Plot why sources are rejected
297 
298  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
299  #
300  fluxName = self.config.fluxName
301  fluxErrName = self.config.fluxErrName
302  minFwhm = self.config.minFwhm
303  maxFwhm = self.config.maxFwhm
304  maxFwhmVariability = self.config.maxFwhmVariability
305  maxbad = self.config.maxbad
306  maxbadflag = self.config.maxbadflag
307  maxellip = self.config.maxellip
308  minsn = self.config.minsn
309 
310  maxelong = (maxellip + 1.0)/(1.0 - maxellip) if maxellip < 1.0 else 100
311 
312  # Unpack the catalogue
313  shape = sourceCat.getShapeDefinition()
314  ixx = sourceCat.get("%s.xx" % shape)
315  iyy = sourceCat.get("%s.yy" % shape)
316 
317  fwhm = 2*np.sqrt(2*np.log(2))*np.sqrt(0.5*(ixx + iyy))
318  elong = 0.5*(ixx - iyy)/(ixx + iyy)
319 
320  flux = sourceCat.get(fluxName)
321  fluxErr = sourceCat.get(fluxErrName)
322  sn = flux/np.where(fluxErr > 0, fluxErr, 1)
323  sn[fluxErr <= 0] = -psfexLib.BIG
324 
325  flags = 0x0
326  for i, f in enumerate(self.config.badFlags):
327  flags = np.bitwise_or(flags, np.where(sourceCat.get(f), 1 << i, 0))
328  #
329  # Estimate the acceptable range of source widths
330  #
331  good = np.logical_and(sn > minsn, np.logical_not(flags))
332  good = np.logical_and(good, elong < maxelong)
333  good = np.logical_and(good, fwhm >= minFwhm)
334  good = np.logical_and(good, fwhm < maxFwhm)
335 
336  fwhmMode, fwhmMin, fwhmMax = compute_fwhmrange(fwhm[good], maxFwhmVariability, minFwhm, maxFwhm,
337  plot=dict(fwhmHistogram=plotFwhmHistogram))
338 
339  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
340  #
341  # Here's select_candidates
342  #
343  #---- Apply some selection over flags, fluxes...
344 
345  bad = (flags != 0)
346  # set.setBadFlags(int(sum(bad)))
347 
348  if plotRejection:
349  selectionVectors = []
350  selectionVectors.append((bad, "flags %d" % sum(bad)))
351 
352  dbad = sn < minsn
353  # set.setBadSN(int(sum(dbad)))
354  bad = np.logical_or(bad, dbad)
355  if plotRejection:
356  selectionVectors.append((dbad, "S/N %d" % sum(dbad)))
357 
358  dbad = fwhm < fwhmMin
359  # set.setBadFrmin(int(sum(dbad)))
360  bad = np.logical_or(bad, dbad)
361  if plotRejection:
362  selectionVectors.append((dbad, "fwhmMin %d" % sum(dbad)))
363 
364  dbad = fwhm > fwhmMax
365  # set.setBadFrmax(int(sum(dbad)))
366  bad = np.logical_or(bad, dbad)
367  if plotRejection:
368  selectionVectors.append((dbad, "fwhmMax %d" % sum(dbad)))
369 
370  dbad = elong > maxelong
371  # set.setBadElong(int(sum(dbad)))
372  bad = np.logical_or(bad, dbad)
373  if plotRejection:
374  selectionVectors.append((dbad, "elong %d" % sum(dbad)))
375 
376  #-- ... and check the integrity of the sample
377  if maxbadflag:
378  nbad = np.array([(v <= -psfexLib.BIG).sum() for v in vignet])
379  dbad = nbad > maxbad
380  # set.setBadPix(int(sum(dbad)))
381  bad = np.logical_or(bad, dbad)
382  if plotRejection:
383  selectionVectors.append((dbad, "badpix %d" % sum(dbad)))
384 
385  good = np.logical_not(bad)
386  #
387  # We know enough to plot, if so requested
388  #
389  frame = 0
390  if displayExposure:
391  mi = exposure.getMaskedImage()
392 
393  ds9.mtv(mi, frame=frame, title="PSF candidates")
394 
395  with ds9.Buffering():
396  for i, source in enumerate(sourceCat):
397  if good[i]:
398  ctype = ds9.GREEN # star candidate
399  else:
400  ctype = ds9.RED # not star
401 
402  ds9.dot("+", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
403  frame=frame, ctype=ctype)
404 
405  if plotFlags or plotRejection:
406  imag = -2.5*np.log10(flux)
407  plt.clf()
408 
409  alpha = 0.5
410  if plotFlags:
411  isSet = np.where(flags == 0x0)[0]
412  plt.plot(imag[isSet], fwhm[isSet], 'o', alpha=alpha, label="good")
413 
414  for i, f in enumerate(self.config.badFlags):
415  mask = 1 << i
416  isSet = np.where(np.bitwise_and(flags, mask))[0]
417  if isSet.any():
418  if np.isfinite(imag[isSet] + fwhm[isSet]).any():
419  label = re.sub(r"\_flag", "",
420  re.sub(r"^base\_", "",
421  re.sub(r"^.*base\_PixelFlags\_flag\_", "", f)))
422  plt.plot(imag[isSet], fwhm[isSet], 'o', alpha=alpha, label=label)
423  else:
424  for bad, label in selectionVectors:
425  plt.plot(imag[bad], fwhm[bad], 'o', alpha=alpha, label=label)
426 
427  plt.plot(imag[good], fwhm[good], 'o', color="black", label="selected")
428  [plt.axhline(_, color='red') for _ in [fwhmMin, fwhmMax]]
429  plt.xlim(np.median(imag[good]) + 5*np.array([-1, 1]))
430  plt.ylim(fwhm[np.where(np.isfinite(fwhm + imag))].min(), 2*fwhmMax)
431  plt.legend(loc=2)
432  plt.xlabel("Instrumental %s Magnitude" % fluxName.split(".")[-1].title())
433  plt.ylabel("fwhm")
434  title = "PSFEX Star Selection"
435  plt.title("%s %d selected" % (title, sum(good)))
436 
437  if displayExposure:
438  global eventHandler
439  eventHandler = EventHandler(plt.axes(), imag, fwhm, sourceCat.getX(), sourceCat.getY(),
440  frames=[frame])
441 
442  if plotFlags or plotRejection:
443  while True:
444  try:
445  reply = input("continue? [y[es] h(elp) p(db) q(uit)] ").strip()
446  except EOFError:
447  reply = "y"
448 
449  if not reply:
450  reply = "y"
451 
452  if reply[0] == "h":
453  print("""\
454 At this prompt, you can continue with almost any key; 'p' enters pdb,
455  'q' returns to the shell, and
456  'h' prints this text
457 """, end=' ')
458 
459  if displayExposure:
460  print("""
461 If you put the cursor on a point in the matplotlib scatter plot and hit 'p' you'll see it in ds9.""")
462  elif reply[0] == "p":
463  import pdb
464  pdb.set_trace()
465  elif reply[0] == 'q':
466  sys.exit(1)
467  else:
468  break
469 
470  return Struct(selected=good)
def selectSources(self, sourceCat, matches=None, exposure=None)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
int min
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
def compute_fwhmrange(fwhm, maxvar, minin, maxin, plot=dict(fwhmHistogram=False))
Definition: psfex.py:57
def __init__(self, axes, xs, ys, x, y, frames=[0])
def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-', clear=True)
bool strip
Definition: fits.cc:831