Coverage for python/lsst/analysis/drp/dataSelectors.py: 44%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

154 statements  

1__all__ = ("FlagSelector", "PsfFlagSelector", "StarIdentifier", "GalaxyIdentifier", "UnknownIdentifier", 

2 "CoaddPlotFlagSelector", "BaseSNRSelector", "SnSelector") 

3 

4from abc import abstractmethod 

5from lsst.pex.config import ListField, Field 

6from lsst.pipe.tasks.dataFrameActions import DataFrameAction 

7import numpy as np 

8 

9 

10class FlagSelector(DataFrameAction): 

11 """The base flag selector to use to select valid sources for QA""" 

12 

13 selectWhenFalse = ListField(doc="Names of the flag columns to select on when False", 

14 dtype=str, 

15 optional=False, 

16 default=[]) 

17 

18 selectWhenTrue = ListField(doc="Names of the flag columns to select on when True", 

19 dtype=str, 

20 optional=False, 

21 default=[]) 

22 

23 @property 

24 def columns(self): 

25 allCols = list(self.selectWhenFalse) + list(self.selectWhenTrue) 

26 yield from allCols 

27 

28 def __call__(self, df, **kwargs): 

29 """Select on the given flags 

30 

31 Parameters 

32 ---------- 

33 df : `pandas.core.frame.DataFrame` 

34 

35 Returns 

36 ------- 

37 result : `numpy.ndarray` 

38 A mask of the objects that satisfy the given 

39 flag cuts. 

40 

41 Notes 

42 ----- 

43 Uses the columns in selectWhenFalse and 

44 selectWhenTrue to decide which columns to 

45 select on in each circumstance. 

46 """ 

47 result = np.ones(len(df), dtype=bool) 

48 for flag in self.selectWhenFalse: 

49 result &= (df[flag].values == 0) 

50 for flag in self.selectWhenTrue: 

51 result &= (df[flag].values == 1) 

52 return result 

53 

54 

55class PsfFlagSelector(FlagSelector): 

56 """Remove sources with bad flags set for PSF measurements """ 

57 

58 bands = ListField(doc="The bands to apply the flags in", 

59 dtype=str, 

60 default=["g", "r", "i", "z", "y"]) 

61 

62 @property 

63 def columns(self): 

64 filterColumns = ["xy_flag", "detect_isPatchInner", "detect_isDebelendedSource"] 

65 flagCols = ["psfFlux_flag", "psfFlux_flag_apCorr", "psfFlux_flag_edge", 

66 "psfFlux_flag_noGoodPixels"] 

67 filterColumns += [band + "_" + flag if len(band) > 0 else band + flag 

68 for flag in flagCols for band in self.bands] 

69 yield from filterColumns 

70 

71 def __call__(self, df, **kwargs): 

72 """Make a mask of objects with bad PSF flags 

73 

74 Parameters 

75 ---------- 

76 df : `pandas.core.frame.DataFrame` 

77 

78 Returns 

79 ------- 

80 result : `numpy.ndarray` 

81 A mask of the objects that satisfy the given 

82 flag cuts. 

83 

84 Notes 

85 ----- 

86 Uses the PSF flags and some general quality 

87 control flags to make a mask of the data that 

88 satisfies these criteria. 

89 """ 

90 

91 result = None 

92 flagCols = ["psfFlux_flag", "pixelFlags_saturatedCenter", "extendedness_flag"] 

93 filterColumns = ["xy_flag"] 

94 filterColumns += [band + "_" + flag if len(band) > 0 else band + flag 

95 for flag in flagCols for band in self.bands] 

96 for flag in filterColumns: 

97 selected = (df[flag].values == 0) 

98 if result is None: 

99 result = selected 

100 else: 

101 result &= selected 

102 result &= (df["detect_isPatchInner"] == 1) 

103 result &= (df["detect_isDeblendedSource"] == 1) 

104 return result 

105 

106 

107class BaseSNRSelector(DataFrameAction): 

108 """Selects sources that have a S/N greater than the 

109 given threshold""" 

110 

111 fluxField = Field(doc="Flux field to use in SNR calculation", dtype=str, 

112 default="psfFlux", optional=False) 

113 errField = Field(doc="Flux err field to use in SNR calculation", dtype=str, 

114 default="psfFluxErr", optional=False) 

115 threshold = Field(doc="The signal to noise threshold to select sources", 

116 dtype=float, 

117 optional=False) 

118 band = Field(doc="The band to make the selection in.", 

119 default="i", 

120 dtype=str) 

121 

122 @property 

123 def columns(self): 

124 if len(self.band) > 0: 

125 band = self.band + "_" 

126 else: 

127 band = self.band 

128 return (band + self.fluxField, band + self.errField) 

129 

130 

131class SnSelector(DataFrameAction): 

132 """Selects points that have S/N > threshold in the given flux type""" 

133 fluxType = Field(doc="Flux type to calculate the S/N in.", 

134 dtype=str, 

135 default="psfFlux") 

136 threshold = Field(doc="The S/N threshold to remove sources with.", 

137 dtype=float, 

138 default=500.0) 

139 bands = ListField(doc="The bands to apply the signal to noise cut in.", 

140 dtype=str, 

141 default=["i"]) 

142 

143 @property 

144 def columns(self): 

145 cols = [] 

146 for band in self.bands: 

147 if len(band) > 0: 

148 band = band + "_" 

149 cols += [band + self.fluxType, band + self.fluxType + "Err"] 

150 

151 return cols 

152 

153 def __call__(self, df): 

154 """Makes a mask of objects that have S/N greater than 

155 self.threshold in self.fluxType 

156 

157 Parameters 

158 ---------- 

159 df : `pandas.core.frame.DataFrame` 

160 

161 Returns 

162 ------- 

163 result : `numpy.ndarray` 

164 A mask of the objects that satisfy the given 

165 S/N cut. 

166 """ 

167 

168 mask = np.ones(len(df), dtype=bool) 

169 for band in self.bands: 

170 if len(band) > 0: 

171 band = band + "_" 

172 mask &= ((df[band + self.fluxType] / df[band + self.fluxType + "Err"]) > self.threshold) 

173 

174 return mask 

175 

176 

177def format_extendedness(prefix): 

178 return "refExtendedness" if prefix == "ref" else ( 

179 f"{prefix}{'_' if len(prefix) > 0 else ''}extendedness") 

180 

181 

182class ExtendedIdentifier(DataFrameAction): 

183 band = Field(doc="The band the object is to be classified as a star in.", 

184 default="i", 

185 dtype=str) 

186 

187 @property 

188 def columns(self): 

189 return [self.extendedness] 

190 

191 @property 

192 def extendedness(self): 

193 return format_extendedness(self.band) 

194 

195 # TODO: Add classmethod in py3.9 

196 @property 

197 @abstractmethod 

198 def sourceType(self): 

199 raise NotImplementedError("This method should be overloaded in subclasses") 

200 

201 @abstractmethod 

202 def identified(self, df): 

203 raise NotImplementedError("This method should be overloaded in subclasses") 

204 

205 def __call__(self, df, **kwargs): 

206 """Identifies sources classified as stars 

207 

208 Parameters 

209 ---------- 

210 df : `pandas.core.frame.DataFrame` 

211 

212 Returns 

213 ------- 

214 result : `numpy.ndarray` 

215 An array with the objects that are classified as 

216 stars marked with a 1. 

217 """ 

218 sourceType = np.zeros(len(df)) 

219 sourceType[self.identified(df)] = self.sourceType 

220 return sourceType 

221 

222 

223class StarIdentifier(ExtendedIdentifier): 

224 """Identifies stars from the dataFrame and marks them as a 1 

225 in the added sourceType column""" 

226 extendedness_maximum = Field(doc="Maximum extendedness to qualify as unresolved, inclusive.", 

227 default=0.5, 

228 dtype=float) 

229 

230 @property 

231 @abstractmethod 

232 def sourceType(self): 

233 return 1 

234 

235 @abstractmethod 

236 def identified(self, df): 

237 extendedness = df[self.extendedness] 

238 return (extendedness >= 0) & (extendedness < self.extendedness_maximum) 

239 

240 

241class GalaxyIdentifier(ExtendedIdentifier): 

242 """Identifies galaxies from the dataFrame and marks them as a 2 

243 in the added sourceType column""" 

244 extendedness_minimum = Field(doc="Minimum extendedness to qualify as resolved, not inclusive.", 

245 default=0.5, 

246 dtype=float) 

247 

248 @property 

249 @abstractmethod 

250 def sourceType(self): 

251 return 2 

252 

253 @abstractmethod 

254 def identified(self, df): 

255 extendedness = df[self.extendedness] 

256 return (extendedness > self.extendedness_minimum) & (extendedness <= 1) 

257 

258 

259class UnknownIdentifier(ExtendedIdentifier): 

260 """Identifies un classified objects from the dataFrame and marks them as a 

261 9 in the added sourceType column""" 

262 

263 @property 

264 @abstractmethod 

265 def sourceType(self): 

266 return 9 

267 

268 @abstractmethod 

269 def identified(self, df): 

270 return df[self.extendedness] == 9 

271 

272 

273class VisitPlotFlagSelector(DataFrameAction): 

274 

275 @property 

276 def columns(self): 

277 flagCols = ["psfFlux_flag", "pixelFlags_saturatedCenter", "extendedness_flag", "centroid_flag"] 

278 yield from flagCols 

279 

280 def __call__(self, df, **kwargs): 

281 """The flags to use for selecting sources for visit QA 

282 

283 Parameters 

284 ---------- 

285 df : `pandas.core.frame.DataFrame` 

286 

287 Returns 

288 ------- 

289 result : `numpy.ndarray` 

290 A mask of the objects that satisfy the given 

291 flag cuts. 

292 

293 Notes 

294 ----- 

295 These flags are taken from pipe_analysis and are considered to 

296 be the standard flags for general QA plots. Some of the plots 

297 will require a different set of flags, or additional ones on 

298 top of the ones specified here. These should be specifed in 

299 an additional selector rather than adding to this one. 

300 """ 

301 

302 result = None 

303 flagCols = ["psfFlux_flag", "pixelFlags_saturatedCenter", "extendedness_flag", "centroid_flag"] 

304 for flag in flagCols: 

305 selected = (df[flag].values == 0) 

306 if result is None: 

307 result = selected 

308 else: 

309 result &= selected 

310 return result 

311 

312 

313class CoaddPlotFlagSelector(DataFrameAction): 

314 """The flags to use for selecting sources for coadd QA 

315 

316 Parameters 

317 ---------- 

318 df : `pandas.core.frame.DataFrame` 

319 

320 Returns 

321 ------- 

322 result : `numpy.ndarray` 

323 A mask of the objects that satisfy the given 

324 flag cuts. 

325 

326 Notes 

327 ----- 

328 These flags are taken from pipe_analysis and are considered to 

329 be the standard flags for general QA plots. Some of the plots 

330 will require a different set of flags, or additional ones on 

331 top of the ones specified here. These should be specifed in 

332 an additional selector rather than adding to this one. 

333 """ 

334 

335 bands = ListField(doc="The bands to apply the flags in", 

336 dtype=str, 

337 default=["g", "r", "i", "z", "y"]) 

338 

339 @property 

340 def columns(self): 

341 flagCols = ["psfFlux_flag", "pixelFlags_saturatedCenter", "extendedness_flag"] 

342 filterColumns = ["xy_flag", "detect_isPatchInner", "detect_isDeblendedSource"] 

343 filterColumns += [band + "_" + flag if len(band) > 0 else band + flag 

344 for flag in flagCols for band in self.bands] 

345 yield from filterColumns 

346 

347 def __call__(self, df, **kwargs): 

348 """The flags to use for selecting sources for coadd QA 

349 

350 Parameters 

351 ---------- 

352 df : `pandas.core.frame.DataFrame` 

353 

354 Returns 

355 ------- 

356 result : `numpy.ndarray` 

357 A mask of the objects that satisfy the given 

358 flag cuts. 

359 

360 Notes 

361 ----- 

362 These flags are taken from pipe_analysis and are considered to 

363 be the standard flags for general QA plots. Some of the plots 

364 will require a different set of flags, or additional ones on 

365 top of the ones specified here. These should be specifed in 

366 an additional selector rather than adding to this one. 

367 """ 

368 

369 result = None 

370 flagCols = ["psfFlux_flag", "pixelFlags_saturatedCenter", "extendedness_flag"] 

371 filterColumns = ["xy_flag"] 

372 filterColumns += [band + "_" + flag if len(band) > 0 else band + flag 

373 for flag in flagCols for band in self.bands] 

374 for flag in filterColumns: 

375 selected = (df[flag].values == 0) 

376 if result is None: 

377 result = selected 

378 else: 

379 result &= selected 

380 result &= (df["detect_isPatchInner"] == 1) 

381 result &= (df["detect_isDeblendedSource"] == 1) 

382 return result