Coverage for python/lsst/ip/diffim/diaCatalogSourceSelector.py: 22%
93 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-29 12:09 +0000
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-29 12:09 +0000
1# This file is part of ip_diffim.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import numpy as np
24from lsst.pipe.base import Struct
25import lsst.pex.config as pexConfig
26import lsst.afw.display as afwDisplay
27import lsst.meas.algorithms as measAlg
29__all__ = ["DiaCatalogSourceSelectorConfig", "DiaCatalogSourceSelectorTask"]
32class DiaCatalogSourceSelectorConfig(measAlg.BaseStarSelectorConfig):
33 # Selection cuts on the input source catalog
34 fluxLim = pexConfig.Field( 34 ↛ exitline 34 didn't jump to the function exit
35 doc="specify the minimum psfFlux for good Kernel Candidates",
36 dtype=float,
37 default=0.0,
38 check=lambda x: x >= 0.0,
39 )
40 fluxMax = pexConfig.Field( 40 ↛ exitline 40 didn't jump to the function exit
41 doc="specify the maximum psfFlux for good Kernel Candidates (ignored if == 0)",
42 dtype=float,
43 default=0.0,
44 check=lambda x: x >= 0.0,
45 )
46 # Selection cuts on the reference catalog
47 selectStar = pexConfig.Field(
48 doc="Select objects that are flagged as stars",
49 dtype=bool,
50 default=True
51 )
52 selectGalaxy = pexConfig.Field(
53 doc="Select objects that are flagged as galaxies",
54 dtype=bool,
55 default=False
56 )
57 includeVariable = pexConfig.Field(
58 doc="Include objects that are known to be variable",
59 dtype=bool,
60 default=False
61 )
62 grMin = pexConfig.Field(
63 doc="Minimum g-r color for selection (inclusive)",
64 dtype=float,
65 default=0.0
66 )
67 grMax = pexConfig.Field(
68 doc="Maximum g-r color for selection (inclusive)",
69 dtype=float,
70 default=3.0
71 )
73 def setDefaults(self):
74 measAlg.BaseStarSelectorConfig.setDefaults(self)
75 self.badFlags = [
76 "base_PixelFlags_flag_edge",
77 "base_PixelFlags_flag_interpolatedCenter",
78 "base_PixelFlags_flag_saturatedCenter",
79 "slot_Centroid_flag",
80 ]
83class CheckSource(object):
84 """A functor to check whether a source has any flags set that should cause it to be labeled bad."""
86 def __init__(self, table, fluxLim, fluxMax, badFlags):
87 self.keys = [table.getSchema().find(name).key for name in badFlags]
88 self.fluxLim = fluxLim
89 self.fluxMax = fluxMax
91 def __call__(self, source):
92 for k in self.keys:
93 if source.get(k):
94 return False
95 if self.fluxLim is not None and source.getPsfInstFlux() < self.fluxLim: # ignore faint objects
96 return False
97 if self.fluxMax != 0.0 and source.getPsfInstFlux() > self.fluxMax: # ignore bright objects
98 return False
99 return True
102@pexConfig.registerConfigurable("diaCatalog", measAlg.sourceSelectorRegistry)
103class DiaCatalogSourceSelectorTask(measAlg.BaseSourceSelectorTask):
104 """A task that selects sources for Kernel candidates.
106 A naive star selector based on second moments. Use with caution.
108 Notes
109 -----
110 Debug Variables
112 DiaCatalogSourceSelectorTask has a debug dictionary with the following keys:
114 display : `bool`
115 if True display debug information
116 displayExposure : `bool`
117 if True display exposure
118 pauseAtEnd `bool`
119 if True wait after displaying everything and wait for user input
121 Examples
122 --------
123 For example, put something like:
125 .. code-block:: py
127 import lsstDebug
128 def DebugInfo(name):
129 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
130 if name.endswith("diaCatalogSourceSelector"):
131 di.display = True
133 return di
135 lsstDebug.Info = DebugInfo
137 into your `debug.py` file and run your task with the `--debug` flag.
138 """
139 ConfigClass = DiaCatalogSourceSelectorConfig
140 usesMatches = True # selectStars uses (requires) its matches argument
142 def selectSources(self, sourceCat, matches=None, exposure=None):
143 """Return a selection of sources for Kernel candidates.
145 Parameters
146 ----------
147 sourceCat : `lsst.afw.table.SourceCatalog`
148 Catalog of sources to select from.
149 This catalog must be contiguous in memory.
150 matches : `list` of `lsst.afw.table.ReferenceMatch`
151 A match vector as produced by meas_astrom.
152 exposure : `lsst.afw.image.Exposure` or None
153 The exposure the catalog was built from; used for debug display.
155 Returns
156 -------
157 struct : `lsst.pipe.base.Struct`
158 The struct contains the following data:
160 - selected : `array` of `bool``
161 Boolean array of sources that were selected, same length as
162 sourceCat.
163 """
164 import lsstDebug
165 display = lsstDebug.Info(__name__).display
166 displayExposure = lsstDebug.Info(__name__).displayExposure
167 pauseAtEnd = lsstDebug.Info(__name__).pauseAtEnd
169 if matches is None:
170 raise RuntimeError("DiaCatalogSourceSelector requires matches")
172 mi = exposure.getMaskedImage()
174 if display and displayExposure:
175 disp = afwDisplay.Display(frame=lsstDebug.frame)
176 disp.mtv(mi, title="Kernel candidates")
177 #
178 # Look for flags in each Source
179 #
180 isGoodSource = CheckSource(sourceCat, self.config.fluxLim, self.config.fluxMax, self.config.badFlags)
182 # Go through and find all the acceptable candidates in the catalogue
183 selected = np.zeros(len(sourceCat), dtype=bool)
185 if display and displayExposure:
186 symbs = []
187 ctypes = []
189 doColorCut = True
191 refSchema = matches[0][0].schema
192 rRefFluxField = measAlg.getRefFluxField(refSchema, "r")
193 gRefFluxField = measAlg.getRefFluxField(refSchema, "g")
194 for i, (ref, source, d) in enumerate(matches):
195 if not isGoodSource(source):
196 if display and displayExposure:
197 symbs.append("+")
198 ctypes.append(afwDisplay.RED)
199 else:
200 isStar = not ref.get("resolved")
201 isVar = not ref.get("photometric")
202 gMag = None
203 rMag = None
204 if doColorCut:
205 try:
206 gMag = -2.5 * np.log10(ref.get(gRefFluxField))
207 rMag = -2.5 * np.log10(ref.get(rRefFluxField))
208 except KeyError:
209 self.log.warning("Cannot cut on color info; fields 'g' and 'r' do not exist")
210 doColorCut = False
211 isRightColor = True
212 else:
213 isRightColor = (gMag-rMag) >= self.config.grMin and (gMag-rMag) <= self.config.grMax
215 isRightType = (self.config.selectStar and isStar) or (self.config.selectGalaxy and not isStar)
216 isRightVar = (self.config.includeVariable) or (self.config.includeVariable is isVar)
217 if isRightType and isRightVar and isRightColor:
218 selected[i] = True
219 if display and displayExposure:
220 symbs.append("+")
221 ctypes.append(afwDisplay.GREEN)
222 elif display and displayExposure:
223 symbs.append("o")
224 ctypes.append(afwDisplay.BLUE)
226 if display and displayExposure:
227 disp = afwDisplay.Display(frame=lsstDebug.frame)
228 with disp.Buffering():
229 for (ref, source, d), symb, ctype in zip(matches, symbs, ctypes):
230 disp.dot(symb, source.getX() - mi.getX0(), source.getY() - mi.getY0(),
231 size=4, ctype=ctype)
233 if display:
234 lsstDebug.frame += 1
235 if pauseAtEnd:
236 input("Continue? y[es] p[db] ")
238 return Struct(selected=selected)