LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
psfexStarSelector.py
Go to the documentation of this file.
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#
22import re
23import sys
24
25import numpy as np
26try:
27 import matplotlib.pyplot as plt
28 fig = None
29except ImportError:
30 plt = None
31
32from lsst.pipe.base import Struct
33import lsst.pex.config as pexConfig
34import lsst.afw.display as afwDisplay
35from lsst.meas.algorithms import BaseSourceSelectorTask, sourceSelectorRegistry
36from . import psfexLib
37from .psfex import compute_fwhmrange
38
39__all__ = ["PsfexStarSelectorConfig", "PsfexStarSelectorTask"]
40
41
42class 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.fluxErrNamefluxErrName == "":
98 self.fluxErrNamefluxErrName = self.fluxNamefluxName + ".err"
99 elif self.fluxErrNamefluxErrName != self.fluxNamefluxName + ".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.minFwhmminFwhm > self.maxFwhmmaxFwhm:
104 raise pexConfig.FieldValidationError(PsfexStarSelectorConfig.minFwhm, self,
105 f"minFwhm ({self.minFwhm}) > maxFwhm ({self.maxFwhm})")
106
107 def setDefaults(self):
108 self.badFlagsbadFlags = [
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
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.axesaxes = axes
125 self.xsxs = xs
126 self.ysys = ys
127 self.xx = x
128 self.yy = y
129 self.framesframes = frames
130
131 self.cidcid = self.axesaxes.figure.canvas.mpl_connect('key_press_event', self)
132
133 def __call__(self, ev):
134 if ev.inaxes != self.axesaxes:
135 return
136
137 if ev.key and ev.key in ("p"):
138 dist = np.hypot(self.xsxs - ev.xdata, self.ysys - ev.ydata)
139 dist[np.where(np.isnan(dist))] = 1e30
140
141 which = np.where(dist == min(dist))
142
143 x = self.xx[which][0]
144 y = self.yy[which][0]
145 for frame in self.framesframes:
146 disp = afwDisplay.Display(frame=frame)
147 disp.pan(x, y)
148 disp.flush()
149 else:
150 pass
151
152
153def 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("""\
444At 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("""
451If 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)
int min
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
def __init__(self, axes, xs, ys, x, y, frames=[0])
def selectSources(self, sourceCat, matches=None, exposure=None)
bool strip
Definition: fits.cc:911
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
def compute_fwhmrange(fwhm, maxvar, minin, maxin, plot=dict(fwhmHistogram=False))
Definition: psfex.py:22
def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-', clear=True)
Lightweight representation of a geometric match between two records.
Definition: Match.h:67