Coverage for python/lsst/faro/utils/selectors.py: 15%

191 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-12 11:40 +0000

1# Note: analysis_drp is not yet part of the pipelines, so you need to clone it, 

2from lsst.pex.config import ListField, Field, ChoiceField 

3from lsst.pipe.tasks.dataFrameActions import DataFrameAction 

4import numpy as np 

5 

6__all__ = ("FlagSelector", "GalaxyIdentifier", "PerBandFlagSelector", "SNRSelector", 

7 "StarIdentifier", "UnknownIdentifier", "applySelectors", 

8 "brightIsolatedStarSourceTable", "brightIsolatedStarObjectTable") 

9 

10 

11class FlagSelector(DataFrameAction): 

12 """The base flag selector to use to select valid sources should not have an 

13 associated band""" 

14 

15 selectWhenFalse = ListField( 

16 doc="Names of the flag columns to select on when False", 

17 dtype=str, 

18 optional=False, 

19 default=[] 

20 ) 

21 

22 selectWhenTrue = ListField( 

23 doc="Names of the flag columns to select on when True", 

24 dtype=str, 

25 optional=False, 

26 default=[] 

27 ) 

28 

29 selectorBandType = ChoiceField( 

30 doc="Type of selection to do options are current band or static selection", 

31 dtype=str, 

32 allowed={"currentBands": "use the currentBand for selection", 

33 "staticBandSet": "use the bands listed in self.staticBandSet" 

34 }, 

35 default="currentBands", 

36 ) 

37 staticBandSet = ListField( 

38 doc="""List of bands that selection should be applied over. If changed from 

39 the default this overrides the band argument in the columns/call method.""", 

40 dtype=str, 

41 default=[""] 

42 ) 

43 

44 def columns(self, currentBands=None): 

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

46 return allCols 

47 

48 def __call__(self, df, currentBands=None): 

49 """Select on the given flags 

50 Parameters 

51 ---------- 

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

53 Returns 

54 ------- 

55 result : `numpy.ndarray` 

56 A mask of the objects that satisfy the given 

57 flag cuts. 

58 Notes 

59 ----- 

60 Uses the columns in selectWhenFalse and selectWhenTrue to decide which 

61 columns to select on in each circumstance. 

62 """ 

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

64 

65 for flag in self.selectWhenFalse: 

66 mask &= (df[flag].values == 0) 

67 

68 for flag in self.selectWhenTrue: 

69 mask &= (df[flag].values == 1) 

70 

71 return mask 

72 

73 

74class GalaxyIdentifier(DataFrameAction): 

75 """Identifies galaxies from the dataFrame""" 

76 

77 selectorBandType = ChoiceField( 

78 doc="Type of selection to do options are current band or static selection", 

79 dtype=str, 

80 allowed={"currentBands": "use the currentBand for selection", 

81 "staticBandSet": "use the bands listed in self.staticBandSet" 

82 }, 

83 default="currentBands", 

84 ) 

85 staticBandSet = ListField( 

86 doc="""List of bands that selection should be applied over. If changed from 

87 the default this overrides the band argument in the columns/call method.""", 

88 dtype=str, 

89 default=[""] 

90 ) 

91 

92 def columns(self, currentBands=None): 

93 allCols = [] 

94 if self.selectorBandType == "staticBandSet": 

95 bands = self.staticBandSet 

96 else: 

97 bands = currentBands 

98 

99 if bands is not None: 

100 for band in bands: 

101 allCols += [band+'_'+'extendedness'] 

102 else: 

103 allCols = ['extendedness'] 

104 return allCols 

105 

106 def __call__(self, df, currentBands=None): 

107 """Identifies sources classified as galaxies 

108 Parameters 

109 ---------- 

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

111 Returns 

112 ------- 

113 result : `numpy.ndarray` 

114 A mask of objects that are classified as galaxies. 

115 """ 

116 

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

118 

119 if self.selectorBandType == "staticBandSet": 

120 bands = self.staticBandSet 

121 elif self.selectorBandType == "currentBands": 

122 bands = currentBands 

123 

124 if bands is not None: 

125 for band in bands: 

126 mask &= (df[band+'_'+'extendedness'] == 1.0) 

127 else: 

128 mask &= (df['extendedness'] == 1.0) 

129 

130 return mask 

131 

132 

133class PerBandFlagSelector(DataFrameAction): 

134 """Flag selector for ObjectTable flags that are defined in each band 

135 Parameters 

136 ---------- 

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

138 Returns 

139 ------- 

140 result : `numpy.ndarray` 

141 A mask of the objects that satisfy the given 

142 flag cuts. 

143 Notes 

144 ----- 

145 """ 

146 selectWhenFalse = ListField( 

147 doc="Names of the flag columns to select on when False", 

148 dtype=str, 

149 optional=False, 

150 default=[] 

151 ) 

152 

153 selectWhenTrue = ListField( 

154 doc="Names of the flag columns to select on when True", 

155 dtype=str, 

156 optional=False, 

157 default=[] 

158 ) 

159 

160 selectorBandType = ChoiceField( 

161 doc="Type of selection to do options are current band or static selection", 

162 dtype=str, 

163 allowed={"currentBands": "use the currentBand for selection", 

164 "staticBandSet": "use the bands listed in self.staticBandSet" 

165 }, 

166 default="currentBands", 

167 ) 

168 staticBandSet = ListField( 

169 doc="""List of bands that selection should be applied over. If changed from 

170 the default this overrides the band argument in the columns/call method.""", 

171 dtype=str, 

172 default=[""] 

173 ) 

174 

175 def columns(self, currentBands=None): 

176 filterColumnsTrue = [] 

177 filterColumnsFalse = [] 

178 allCols = [] 

179 if self.selectorBandType == "staticBandSet": 

180 bands = self.staticBandSet 

181 else: 

182 bands = currentBands 

183 if bands is not None: 

184 filterColumnsTrue += [band+'_'+flag for flag in self.selectWhenTrue for band in bands] 

185 filterColumnsFalse += [band+'_'+flag for flag in self.selectWhenFalse for band in bands] 

186 else: 

187 filterColumnsTrue += [band+flag for flag in self.selectWhenTrue for band in self.bands] 

188 filterColumnsFalse += [band+flag for flag in self.selectWhenFalse for band in self.bands] 

189 allCols = list(filterColumnsFalse) + list(filterColumnsTrue) 

190 return allCols 

191 

192 def __call__(self, df, currentBands=None): 

193 """The flags to use for selecting sources from objectTables 

194 Parameters 

195 ---------- 

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

197 Returns 

198 ------- 

199 result : `numpy.ndarray` 

200 A mask of the objects that satisfy the given 

201 flag cuts. 

202 Notes 

203 ----- 

204 """ 

205 

206 filterColumnsTrue = [] 

207 filterColumnsFalse = [] 

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

209 

210 if self.selectorBandType == "staticBandSet": 

211 bands = self.staticBandSet 

212 elif self.selectorBandType == "currentBands": 

213 bands = currentBands 

214 

215 if bands is not None: 

216 for band in bands: 

217 filterColumnsTrue += [band+'_'+flag for flag in self.selectWhenTrue for band in bands] 

218 filterColumnsFalse += [band+'_'+flag for flag in self.selectWhenFalse for band in bands] 

219 else: 

220 filterColumnsTrue += [flag for flag in self.selectWhenTrue] 

221 filterColumnsFalse += [flag for flag in self.selectWhenFalse] 

222 

223 for flag in filterColumnsFalse: 

224 mask &= (df[flag].values == 0) 

225 for flag in filterColumnsTrue: 

226 mask &= (df[flag].values == 1) 

227 return mask 

228 

229 

230class SNRSelector(DataFrameAction): 

231 """SNR Selector for sources from a SourceTable or ObjectTable 

232 Selects soruces that have snrMin < S/N < snrMax in the given flux type 

233 Parameters 

234 ---------- 

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

236 Returns 

237 ------- 

238 result : `numpy.ndarray` 

239 A mask of the objects that satisfy the given 

240 flag cuts. 

241 Notes 

242 ----- 

243 """ 

244 fluxType = Field( 

245 doc="Flux type to calculate the S/N in.", 

246 dtype=str, 

247 default="psfFlux" 

248 ) 

249 snrMin = Field( 

250 doc="The minimum S/N threshold to remove sources with.", 

251 dtype=float, 

252 default=50.0 

253 ) 

254 snrMax = Field( 

255 doc="The maximum S/N threshold to remove sources with.", 

256 dtype=float, 

257 default=np.Inf 

258 ) 

259 selectorBandType = ChoiceField( 

260 doc="Type of selection to do options are current band or static selection", 

261 dtype=str, 

262 allowed={"currentBands": "use the currentBand for selection", 

263 "staticBandSet": "use the bands listed in self.staticBandSet" 

264 }, 

265 default="currentBands", 

266 ) 

267 staticBandSet = ListField( 

268 doc="""List of bands that selection should be applied over. If changed from 

269 the default this overrides the band argument in the columns/call method.""", 

270 dtype=str, 

271 default=[""] 

272 ) 

273 # want to set an error if staticBandSet, and self.staticBandSet=[""] 

274 

275 def columns(self, currentBands=None): 

276 allCols = [] 

277 if self.selectorBandType == "staticBandSet": 

278 bands = self.staticBandSet 

279 else: 

280 bands = currentBands 

281 

282 if bands is not None: 

283 for band in bands: 

284 allCols += [band+'_'+self.fluxType, band+'_'+self.fluxType+'Err'] 

285 else: 

286 allCols = [self.fluxType, self.fluxType+'Err'] 

287 return allCols 

288 

289 def __call__(self, df, currentBands=None): 

290 """Makes a mask of objects that have S/N between self.snrMin and 

291 self.snrMax in self.fluxType 

292 Parameters 

293 ---------- 

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

295 Returns 

296 ------- 

297 result : `numpy.ndarray` 

298 A mask of the objects that satisfy the given 

299 S/N cut. 

300 """ 

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

302 if self.selectorBandType == "staticBandSet": 

303 bands = self.staticBandSet 

304 elif self.selectorBandType == "currentBands": 

305 bands = currentBands 

306 if bands is not None: 

307 for band in bands: 

308 mask &= ((df[band+'_'+self.fluxType] / df[band+'_'+self.fluxType+"Err"]) > self.snrMin) 

309 mask &= ((df[band+'_'+self.fluxType] / df[band+'_'+self.fluxType+"Err"]) < self.snrMax) 

310 else: 

311 mask &= ((df[self.fluxType] / df[self.fluxType+"Err"]) > self.snrMin) 

312 mask &= ((df[self.fluxType] / df[self.fluxType+"Err"]) < self.snrMax) 

313 return mask 

314 

315 

316class StarIdentifier(DataFrameAction): 

317 """Identifies stars from the dataFrame""" 

318 

319 selectorBandType = ChoiceField( 

320 doc="Type of selection to do options are current band or static selection", 

321 dtype=str, 

322 allowed={"currentBands": "use the currentBand for selection", 

323 "staticBandSet": "use the bands listed in self.staticBandSet" 

324 }, 

325 default="currentBands", 

326 ) 

327 staticBandSet = ListField( 

328 doc="""List of bands that selection should be applied over. If changed from 

329 the default this overrides the band argument in the columns/call method.""", 

330 dtype=str, 

331 default=[""] 

332 ) 

333 

334 def columns(self, currentBands=None): 

335 allCols = [] 

336 if self.selectorBandType == "staticBandSet": 

337 bands = self.staticBandSet 

338 else: 

339 bands = currentBands 

340 

341 if bands is not None: 

342 for band in bands: 

343 allCols += [band+'_'+'extendedness'] 

344 else: 

345 allCols = ['extendedness'] 

346 return allCols 

347 

348 def __call__(self, df, currentBands=None): 

349 """Identifies sources classified as stars 

350 Parameters 

351 ---------- 

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

353 Returns 

354 ------- 

355 result : `numpy.ndarray` 

356 A mask of objects that are classified as stars. 

357 """ 

358 

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

360 

361 if self.selectorBandType == "staticBandSet": 

362 bands = self.staticBandSet 

363 elif self.selectorBandType == "currentBands": 

364 bands = currentBands 

365 

366 if bands is not None: 

367 for band in bands: 

368 mask &= (df[band+'_'+'extendedness'] == 0.0) 

369 else: 

370 mask &= (df['extendedness'] == 0.0) 

371 

372 return mask 

373 

374 

375class UnknownIdentifier(DataFrameAction): 

376 """Identifies unclassified objects from the dataFrame""" 

377 

378 selectorBandType = ChoiceField( 

379 doc="Type of selection to do options are current band or static selection", 

380 dtype=str, 

381 allowed={"currentBands": "use the currentBand for selection", 

382 "staticBandSet": "use the bands listed in self.staticBandSet" 

383 }, 

384 default="currentBands", 

385 ) 

386 staticBandSet = ListField( 

387 doc="""List of bands that selection should be applied over. If changed from 

388 the default this overrides the band argument in the columns/call method.""", 

389 dtype=str, 

390 default=[""] 

391 ) 

392 

393 def columns(self, currentBands=None): 

394 allCols = [] 

395 if self.selectorBandType == "staticBandSet": 

396 bands = self.staticBandSet 

397 else: 

398 bands = currentBands 

399 

400 if bands is not None: 

401 for band in bands: 

402 allCols += [band+'_'+'extendedness'] 

403 else: 

404 allCols = ['extendedness'] 

405 return allCols 

406 

407 def __call__(self, df, currentBands=None): 

408 """Identifies sources with unknown classification 

409 Parameters 

410 ---------- 

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

412 Returns 

413 ------- 

414 result : `numpy.ndarray` 

415 A mask of objects that are unclassified. 

416 """ 

417 

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

419 

420 if self.selectorBandType == "staticBandSet": 

421 bands = self.staticBandSet 

422 elif self.selectorBandType == "currentBands": 

423 bands = currentBands 

424 

425 if bands is not None: 

426 for band in bands: 

427 mask &= (df[band+'_'+'extendedness'] == 9.0) 

428 else: 

429 mask &= (df['extendedness'] == 9.0) 

430 

431 return mask 

432 

433 

434def applySelectors(catalog, selectorList, currentBands=None, returnMask=False): 

435 """ Apply the selectors to narrow down the sources to use 

436 Parameters 

437 ---------- 

438 catalog : `pandas.core.frame.DataFrame` 

439 selectorList: list of selector DataFrameActions 

440 currentBands: the bands associated with the current measurement quanta 

441 returnMask: boolean to return the mask without applying it to the catalog 

442 

443 Returns 

444 ------- 

445 result : `numpy.ndarray` 

446 if returnMask==False a dataframe with only sources that pass the selector 

447 actions is returned. 

448 otherwise the original dataframe and a boolean mask indicating the sources 

449 that pass the selector actions is returned. 

450 """ 

451 mask = np.ones(len(catalog), dtype=bool) 

452 for selector in selectorList: 

453 mask &= selector(catalog, currentBands=currentBands) 

454 if returnMask: 

455 return catalog, mask 

456 else: 

457 return catalog[mask] 

458 

459 

460def brightIsolatedStarSourceTable(config): 

461 """ 

462 To be called in a measurement yaml sets up a 

463 standard set of selectorActions for a SourceTable metric 

464 

465 Parameters 

466 ---------- 

467 measurement config dict 

468 

469 Returns 

470 ------- 

471 result : 

472 mesurement config dict confugured to create a mask used to select 

473 bright isolated stars from a SourceTable catalog 

474 """ 

475 # will want to put more thought into this 

476 # setup SNRSelector 

477 config.selectorActions.SNRSelector = SNRSelector 

478 config.selectorActions.SNRSelector.fluxType = "psfFlux" 

479 config.selectorActions.SNRSelector.snrMin = 50 

480 config.selectorActions.SNRSelector.selectorBandType = "currentBands" 

481 # setup stellarSelector 

482 config.selectorActions.StarIdentifier = StarIdentifier 

483 config.selectorActions.StarIdentifier.selectorBandType = "currentBands" 

484 # setup flag slectors 

485 config.selectorActions.FlagSelector = FlagSelector 

486 config.selectorActions.FlagSelector.selectWhenTrue = ["detect_isPrimary"] 

487 config.selectorActions.FlagSelector.selectWhenFalse = ["pixelFlags_saturated", "pixelFlags_cr", 

488 "pixelFlags_bad", "pixelFlags_edge", 

489 "deblend_nChild"] 

490 config.selectorActions.FlagSelector.selectorBandType = "currentBands" 

491 return config 

492 

493 

494def brightIsolatedStarObjectTable(config): 

495 """ 

496 To be called in a measurement yaml sets up a 

497 standard set of selectorActions for a ObjectTable metric 

498 

499 Parameters 

500 ---------- 

501 measurement config dict 

502 

503 Returns 

504 ------- 

505 result : 

506 mesurement config dict confugured to create a mask used to select 

507 bright isolated stars from an ObjectTable catalog 

508 """ 

509 # will want to put more thought into this 

510 # setup SNRSelector 

511 config.selectorActions.SNRSelector = SNRSelector 

512 config.selectorActions.SNRSelector.fluxType = "psfFlux" 

513 config.selectorActions.SNRSelector.snrMin = 50 

514 config.selectorActions.SNRSelector.selectorBandType = "currentBands" 

515 # setup stellarSelector 

516 config.selectorActions.StarIdentifier = StarIdentifier 

517 config.selectorActions.StarIdentifier.selectorBandType = "currentBands" 

518 # setup non band flag slectors 

519 config.selectorActions.FlagSelector = FlagSelector 

520 config.selectorActions.FlagSelector.selectWhenTrue = ["detect_isPrimary"] 

521 config.selectorActions.FlagSelector.selectorBandType = "currentBands" 

522 # setup per band flag selectors 

523 config.selectorActions.PerBandFlagSelector = PerBandFlagSelector 

524 config.selectorActions.PerBandFlagSelector.selectWhenFalse = ["pixelFlags_saturated", "pixelFlags_cr", 

525 "pixelFlags_bad", "pixelFlags_edge"] 

526 config.selectorActions.PerBandFlagSelector.selectorBandType = "currentBands" 

527 return config