Coverage for python/lsst/ip/diffim/diaCatalogSourceSelector.py: 19%

93 statements  

« prev     ^ index     » next       coverage.py v7.3.3, created at 2023-12-16 13:38 +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/>. 

21 

22import numpy as np 

23 

24from lsst.pipe.base import Struct 

25import lsst.pex.config as pexConfig 

26import lsst.afw.display as afwDisplay 

27import lsst.meas.algorithms as measAlg 

28 

29__all__ = ["DiaCatalogSourceSelectorConfig", "DiaCatalogSourceSelectorTask"] 

30 

31 

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 ) 

72 

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 ] 

81 

82 

83class CheckSource(object): 

84 """A functor to check whether a source has any flags set that should cause it to be labeled bad.""" 

85 

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 

90 

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 

100 

101 

102@pexConfig.registerConfigurable("diaCatalog", measAlg.sourceSelectorRegistry) 

103class DiaCatalogSourceSelectorTask(measAlg.BaseSourceSelectorTask): 

104 """A task that selects sources for Kernel candidates. 

105 

106 A naive star selector based on second moments. Use with caution. 

107 

108 """ 

109 ConfigClass = DiaCatalogSourceSelectorConfig 

110 usesMatches = True # selectStars uses (requires) its matches argument 

111 

112 def selectSources(self, sourceCat, matches=None, exposure=None): 

113 """Return a selection of sources for Kernel candidates. 

114 

115 Parameters 

116 ---------- 

117 sourceCat : `lsst.afw.table.SourceCatalog` 

118 Catalog of sources to select from. 

119 This catalog must be contiguous in memory. 

120 matches : `list` of `lsst.afw.table.ReferenceMatch` 

121 A match vector as produced by meas_astrom. 

122 exposure : `lsst.afw.image.Exposure` or None 

123 The exposure the catalog was built from; used for debug display. 

124 

125 Returns 

126 ------- 

127 struct : `lsst.pipe.base.Struct` 

128 The struct contains the following data: 

129 

130 - selected : `array` of `bool`` 

131 Boolean array of sources that were selected, same length as 

132 sourceCat. 

133 """ 

134 import lsstDebug 

135 display = lsstDebug.Info(__name__).display 

136 displayExposure = lsstDebug.Info(__name__).displayExposure 

137 pauseAtEnd = lsstDebug.Info(__name__).pauseAtEnd 

138 

139 if matches is None: 

140 raise RuntimeError("DiaCatalogSourceSelector requires matches") 

141 

142 mi = exposure.getMaskedImage() 

143 

144 if display and displayExposure: 

145 disp = afwDisplay.Display(frame=lsstDebug.frame) 

146 disp.mtv(mi, title="Kernel candidates") 

147 # 

148 # Look for flags in each Source 

149 # 

150 isGoodSource = CheckSource(sourceCat, self.config.fluxLim, self.config.fluxMax, self.config.badFlags) 

151 

152 # Go through and find all the acceptable candidates in the catalogue 

153 selected = np.zeros(len(sourceCat), dtype=bool) 

154 

155 if display and displayExposure: 

156 symbs = [] 

157 ctypes = [] 

158 

159 doColorCut = True 

160 

161 refSchema = matches[0][0].schema 

162 rRefFluxField = measAlg.getRefFluxField(refSchema, "r") 

163 gRefFluxField = measAlg.getRefFluxField(refSchema, "g") 

164 for i, (ref, source, d) in enumerate(matches): 

165 if not isGoodSource(source): 

166 if display and displayExposure: 

167 symbs.append("+") 

168 ctypes.append(afwDisplay.RED) 

169 else: 

170 isStar = not ref.get("resolved") 

171 isVar = not ref.get("photometric") 

172 gMag = None 

173 rMag = None 

174 if doColorCut: 

175 try: 

176 gMag = -2.5 * np.log10(ref.get(gRefFluxField)) 

177 rMag = -2.5 * np.log10(ref.get(rRefFluxField)) 

178 except KeyError: 

179 self.log.warning("Cannot cut on color info; fields 'g' and 'r' do not exist") 

180 doColorCut = False 

181 isRightColor = True 

182 else: 

183 isRightColor = (gMag-rMag) >= self.config.grMin and (gMag-rMag) <= self.config.grMax 

184 

185 isRightType = (self.config.selectStar and isStar) or (self.config.selectGalaxy and not isStar) 

186 isRightVar = (self.config.includeVariable) or (self.config.includeVariable is isVar) 

187 if isRightType and isRightVar and isRightColor: 

188 selected[i] = True 

189 if display and displayExposure: 

190 symbs.append("+") 

191 ctypes.append(afwDisplay.GREEN) 

192 elif display and displayExposure: 

193 symbs.append("o") 

194 ctypes.append(afwDisplay.BLUE) 

195 

196 if display and displayExposure: 

197 disp = afwDisplay.Display(frame=lsstDebug.frame) 

198 with disp.Buffering(): 

199 for (ref, source, d), symb, ctype in zip(matches, symbs, ctypes): 

200 disp.dot(symb, source.getX() - mi.getX0(), source.getY() - mi.getY0(), 

201 size=4, ctype=ctype) 

202 

203 if display: 

204 lsstDebug.frame += 1 

205 if pauseAtEnd: 

206 input("Continue? y[es] p[db] ") 

207 

208 return Struct(selected=selected)