Coverage for python/lsst/meas/algorithms/sourceSelector.py: 40%

215 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-26 03:05 -0700

1# This file is part of meas_algorithms. 

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 

22__all__ = ["BaseSourceSelectorConfig", "BaseSourceSelectorTask", "sourceSelectorRegistry", 

23 "ColorLimit", "MagnitudeLimit", "SignalToNoiseLimit", "MagnitudeErrorLimit", 

24 "RequireFlags", "RequireUnresolved", "RequireFiniteRaDec", "RequirePrimary", 

25 "ScienceSourceSelectorConfig", "ScienceSourceSelectorTask", 

26 "ReferenceSourceSelectorConfig", "ReferenceSourceSelectorTask", 

27 "NullSourceSelectorTask" 

28 ] 

29 

30import abc 

31import numpy as np 

32import astropy.units as u 

33import pandas 

34import astropy.table 

35 

36import lsst.pex.config as pexConfig 

37import lsst.pipe.base as pipeBase 

38 

39 

40class BaseSourceSelectorConfig(pexConfig.Config): 

41 pass 

42 

43 

44class BaseSourceSelectorTask(pipeBase.Task, metaclass=abc.ABCMeta): 

45 """Base class for source selectors 

46 

47 Source selectors are classes that perform a selection on a catalog 

48 object given a set of criteria or cuts. They return the selected catalog 

49 and can optionally set a specified Flag field in the input catalog to 

50 identify if the source was selected. 

51 

52 Register all source selectors with the sourceSelectorRegistry using: 

53 sourceSelectorRegistry.register(name, class) 

54 

55 Attributes 

56 ---------- 

57 usesMatches : `bool` 

58 A boolean variable specify if the inherited source selector uses 

59 matches to an external catalog, and thus requires the ``matches`` 

60 argument to ``run()``. 

61 """ 

62 

63 ConfigClass = BaseSourceSelectorConfig 

64 _DefaultName = "sourceSelector" 

65 usesMatches = False 

66 

67 def __init__(self, **kwargs): 

68 pipeBase.Task.__init__(self, **kwargs) 

69 

70 def run(self, sourceCat, sourceSelectedField=None, matches=None, exposure=None): 

71 """Select sources and return them. 

72 

73 The input catalog must be contiguous in memory. 

74 

75 Parameters 

76 ---------- 

77 sourceCat : Various table formats 

78 Catalog of sources to select from. Can be 

79 `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` or 

80 `astropy.table.Table`, 

81 sourceSelectedField : `str` or None 

82 Name of flag field in sourceCat to set for selected sources. 

83 If set, will modify sourceCat in-place. 

84 matches : `list` of `lsst.afw.table.ReferenceMatch` or None 

85 List of matches to use for source selection. 

86 If usesMatches is set in source selector this field is required. 

87 If not, it is ignored. 

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

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

90 

91 Returns 

92 ------- 

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

94 The struct contains the following data: 

95 

96 ``sourceCat`` 

97 The catalog of sources that were selected. 

98 (may not be memory-contiguous) 

99 (`lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 

100 or `astropy.table.Table`) 

101 ``selected`` 

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

103 sourceCat. 

104 (`numpy.ndarray` of `bool`) 

105 

106 Raises 

107 ------ 

108 RuntimeError 

109 Raised if ``sourceCat`` is not contiguous. 

110 """ 

111 if hasattr(sourceCat, 'isContiguous'): 

112 # Check for continuity on afwTable catalogs 

113 if not sourceCat.isContiguous(): 

114 raise RuntimeError("Input catalogs for source selection must be contiguous.") 

115 

116 result = self.selectSources(sourceCat=sourceCat, 

117 exposure=exposure, 

118 matches=matches) 

119 

120 if sourceSelectedField is not None: 

121 sourceCat[sourceSelectedField] = result.selected 

122 

123 return pipeBase.Struct(sourceCat=sourceCat[result.selected], 

124 selected=result.selected) 

125 

126 @abc.abstractmethod 

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

128 """Return a selection of sources selected by some criteria. 

129 

130 Parameters 

131 ---------- 

132 sourceCat : Various table formats 

133 Catalog of sources to select from. Supports 

134 `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 

135 or `astropy.table.Table` 

136 This catalog must be contiguous in memory. 

137 matches : `list` of `lsst.afw.table.ReferenceMatch` or None 

138 A list of lsst.afw.table.ReferenceMatch objects 

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

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

141 

142 Returns 

143 ------- 

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

145 The struct contains the following data: 

146 

147 ``selected`` 

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

149 sourceCat. 

150 (`numpy.ndarray` of `bool`) 

151 """ 

152 raise NotImplementedError("BaseSourceSelectorTask is abstract") 

153 

154 

155sourceSelectorRegistry = pexConfig.makeRegistry( 

156 doc="A registry of source selectors (subclasses of " 

157 "BaseSourceSelectorTask)", 

158) 

159 

160 

161class BaseLimit(pexConfig.Config): 

162 """Base class for selecting sources by applying a limit 

163 

164 This object can be used as a `lsst.pex.config.Config` for configuring 

165 the limit, and then the `apply` method can be used to identify sources 

166 in the catalog that match the configured limit. 

167 

168 This provides the `maximum` and `minimum` fields in the Config, and 

169 a method to apply the limits to an array of values calculated by the 

170 subclass. 

171 """ 

172 minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value greater than this") 

173 maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value less than this") 

174 

175 def apply(self, values): 

176 """Apply the limits to an array of values 

177 

178 Subclasses should calculate the array of values and then 

179 return the result of calling this method. 

180 

181 Parameters 

182 ---------- 

183 values : `numpy.ndarray` 

184 Array of values to which to apply limits. 

185 

186 Returns 

187 ------- 

188 selected : `numpy.ndarray` 

189 Boolean array indicating for each source whether it is selected 

190 (True means selected). 

191 """ 

192 selected = np.ones(len(values), dtype=bool) 

193 with np.errstate(invalid="ignore"): # suppress NAN warnings 

194 if self.minimum is not None: 

195 selected &= values > self.minimum 

196 if self.maximum is not None: 

197 selected &= values < self.maximum 

198 return selected 

199 

200 

201class ColorLimit(BaseLimit): 

202 """Select sources using a color limit 

203 

204 This object can be used as a `lsst.pex.config.Config` for configuring 

205 the limit, and then the `apply` method can be used to identify sources 

206 in the catalog that match the configured limit. 

207 

208 We refer to 'primary' and 'secondary' flux measurements; these are the 

209 two components of the color, which is: 

210 

211 instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary]) 

212 """ 

213 primary = pexConfig.Field(dtype=str, doc="Name of column with primary flux measurement") 

214 secondary = pexConfig.Field(dtype=str, doc="Name of column with secondary flux measurement") 

215 

216 def apply(self, catalog): 

217 """Apply the color limit to a catalog 

218 

219 Parameters 

220 ---------- 

221 catalog : Various table formats 

222 Catalog of sources to which the limit will be applied. 

223 Supports `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 

224 or `astropy.table.Table` 

225 

226 Returns 

227 ------- 

228 selected : `numpy.ndarray` 

229 Boolean array indicating for each source whether it is selected 

230 (True means selected). 

231 """ 

232 primary = _getFieldFromCatalog(catalog, self.primary) 

233 secondary = _getFieldFromCatalog(catalog, self.secondary) 

234 

235 primary = (primary*u.nJy).to_value(u.ABmag) 

236 secondary = (secondary*u.nJy).to_value(u.ABmag) 

237 color = primary - secondary 

238 return BaseLimit.apply(self, color) 

239 

240 

241class FluxLimit(BaseLimit): 

242 """Select sources using a flux limit 

243 

244 This object can be used as a `lsst.pex.config.Config` for configuring 

245 the limit, and then the `apply` method can be used to identify sources 

246 in the catalog that match the configured limit. 

247 """ 

248 fluxField = pexConfig.Field(dtype=str, default="slot_CalibFlux_instFlux", 

249 doc="Name of the source flux field to use.") 

250 

251 def apply(self, catalog): 

252 """Apply the flux limits to a catalog 

253 

254 Parameters 

255 ---------- 

256 catalog : `lsst.afw.table.SourceCatalog` 

257 Catalog of sources to which the limit will be applied. 

258 

259 Returns 

260 ------- 

261 selected : `numpy.ndarray` 

262 Boolean array indicating for each source whether it is selected 

263 (True means selected). 

264 """ 

265 flagField = self.fluxField + "_flag" 

266 selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=True)) 

267 flux = _getFieldFromCatalog(catalog, self.fluxField) 

268 

269 selected &= BaseLimit.apply(self, flux) 

270 return selected 

271 

272 

273class MagnitudeLimit(BaseLimit): 

274 """Select sources using a magnitude limit 

275 

276 Note that this assumes that a zero-point has already been applied and 

277 the fluxes are in AB fluxes in Jansky. It is therefore principally 

278 intended for reference catalogs rather than catalogs extracted from 

279 science images. 

280 

281 This object can be used as a `lsst.pex.config.Config` for configuring 

282 the limit, and then the `apply` method can be used to identify sources 

283 in the catalog that match the configured limit. 

284 """ 

285 fluxField = pexConfig.Field(dtype=str, default="flux", 

286 doc="Name of the source flux field to use.") 

287 

288 def apply(self, catalog): 

289 """Apply the magnitude limits to a catalog 

290 

291 Parameters 

292 ---------- 

293 catalog : `lsst.afw.table.SourceCatalog` 

294 Catalog of sources to which the limit will be applied. 

295 

296 Returns 

297 ------- 

298 selected : `numpy.ndarray` 

299 Boolean array indicating for each source whether it is selected 

300 (True means selected). 

301 """ 

302 flagField = self.fluxField + "_flag" 

303 selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=True)) 

304 flux = _getFieldFromCatalog(catalog, self.fluxField) 

305 

306 magnitude = (flux*u.nJy).to_value(u.ABmag) 

307 selected &= BaseLimit.apply(self, magnitude) 

308 return selected 

309 

310 

311class SignalToNoiseLimit(BaseLimit): 

312 """Select sources using a flux signal-to-noise limit 

313 

314 This object can be used as a `lsst.pex.config.Config` for configuring 

315 the limit, and then the `apply` method can be used to identify sources 

316 in the catalog that match the configured limit. 

317 """ 

318 fluxField = pexConfig.Field(dtype=str, default="flux", 

319 doc="Name of the source flux field to use.") 

320 errField = pexConfig.Field(dtype=str, default="flux_err", 

321 doc="Name of the source flux error field to use.") 

322 

323 def apply(self, catalog): 

324 """Apply the signal-to-noise limits to a catalog 

325 

326 Parameters 

327 ---------- 

328 catalog : `lsst.afw.table.SourceCatalog` 

329 Catalog of sources to which the limit will be applied. 

330 

331 Returns 

332 ------- 

333 selected : `numpy.ndarray` 

334 Boolean array indicating for each source whether it is selected 

335 (True means selected). 

336 """ 

337 flagField = self.fluxField + "_flag" 

338 selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=True)) 

339 flux = _getFieldFromCatalog(catalog, self.fluxField) 

340 err = _getFieldFromCatalog(catalog, self.errField) 

341 

342 signalToNoise = flux/err 

343 selected &= BaseLimit.apply(self, signalToNoise) 

344 return selected 

345 

346 

347class MagnitudeErrorLimit(BaseLimit): 

348 """Select sources using a magnitude error limit 

349 

350 Because the magnitude error is the inverse of the signal-to-noise 

351 ratio, this also works to select sources by signal-to-noise when 

352 you only have a magnitude. 

353 

354 This object can be used as a `lsst.pex.config.Config` for configuring 

355 the limit, and then the `apply` method can be used to identify sources 

356 in the catalog that match the configured limit. 

357 """ 

358 magErrField = pexConfig.Field(dtype=str, default="mag_err", 

359 doc="Name of the source flux error field to use.") 

360 

361 def apply(self, catalog): 

362 """Apply the magnitude error limits to a catalog 

363 

364 Parameters 

365 ---------- 

366 catalog : `lsst.afw.table.SourceCatalog` 

367 Catalog of sources to which the limit will be applied. 

368 

369 Returns 

370 ------- 

371 selected : `numpy.ndarray` 

372 Boolean array indicating for each source whether it is selected 

373 (True means selected). 

374 """ 

375 return BaseLimit.apply(self, catalog[self.magErrField]) 

376 

377 

378class RequireFlags(pexConfig.Config): 

379 """Select sources using flags 

380 

381 This object can be used as a `lsst.pex.config.Config` for configuring 

382 the limit, and then the `apply` method can be used to identify sources 

383 in the catalog that match the configured limit. 

384 """ 

385 good = pexConfig.ListField(dtype=str, default=[], 

386 doc="List of source flag fields that must be set for a source to be used.") 

387 bad = pexConfig.ListField(dtype=str, default=[], 

388 doc="List of source flag fields that must NOT be set for a source to be used.") 

389 

390 def apply(self, catalog): 

391 """Apply the flag requirements to a catalog 

392 

393 Returns whether the source is selected. 

394 

395 Parameters 

396 ---------- 

397 catalog : `lsst.afw.table.SourceCatalog` 

398 Catalog of sources to which the requirements will be applied. 

399 

400 Returns 

401 ------- 

402 selected : `numpy.ndarray` 

403 Boolean array indicating for each source whether it is selected 

404 (True means selected). 

405 """ 

406 selected = np.ones(len(catalog), dtype=bool) 

407 for flag in self.good: 

408 selected &= catalog[flag] 

409 for flag in self.bad: 

410 selected &= ~catalog[flag] 

411 return selected 

412 

413 

414class RequireUnresolved(BaseLimit): 

415 """Select sources using star/galaxy separation 

416 

417 This object can be used as a `lsst.pex.config.Config` for configuring 

418 the limit, and then the `apply` method can be used to identify sources 

419 in the catalog that match the configured limit. 

420 """ 

421 name = pexConfig.Field(dtype=str, default="base_ClassificationSizeExtendedness_value", 

422 doc="Name of column for star/galaxy separation") 

423 

424 def setDefaults(self): 

425 """Set default 

426 

427 Values below the threshold are unresolved. 

428 """ 

429 self.maximum = 0.1 

430 

431 def apply(self, catalog): 

432 """Apply the flag requirements to a catalog 

433 

434 Returns whether the source is selected. 

435 

436 Parameters 

437 ---------- 

438 catalog : `lsst.afw.table.SourceCatalog` 

439 Catalog of sources to which the requirements will be applied. 

440 

441 Returns 

442 ------- 

443 selected : `numpy.ndarray` 

444 Boolean array indicating for each source whether it is selected 

445 (True means selected). 

446 """ 

447 value = catalog[self.name] 

448 return BaseLimit.apply(self, value) 

449 

450 

451class RequireIsolated(pexConfig.Config): 

452 """Select sources based on whether they are isolated 

453 

454 This object can be used as a `lsst.pex.config.Config` for configuring 

455 the column names to check for "parent" and "nChild" keys. 

456 

457 Note that this should only be run on a catalog that has had the 

458 deblender already run (or else deblend_nChild does not exist). 

459 """ 

460 parentName = pexConfig.Field(dtype=str, default="parent", 

461 doc="Name of column for parent") 

462 nChildName = pexConfig.Field(dtype=str, default="deblend_nChild", 

463 doc="Name of column for nChild") 

464 

465 def apply(self, catalog): 

466 """Apply the isolation requirements to a catalog 

467 

468 Returns whether the source is selected. 

469 

470 Parameters 

471 ---------- 

472 catalog : `lsst.afw.table.SourceCatalog` 

473 Catalog of sources to which the requirements will be applied. 

474 

475 Returns 

476 ------- 

477 selected : `numpy.ndarray` 

478 Boolean array indicating for each source whether it is selected 

479 (True means selected). 

480 """ 

481 selected = ((catalog[self.parentName] == 0) 

482 & (catalog[self.nChildName] == 0)) 

483 return selected 

484 

485 

486class RequireFiniteRaDec(pexConfig.Config): 

487 """Select sources that have finite RA and Dec sky coordinate values 

488 

489 This object can be used as a `lsst.pex.config.Config` for configuring 

490 the column names to check for "coord_ra" and "coord_dec" keys. 

491 

492 This will select against objects for which either the RA or Dec coordinate 

493 entries are not numpy.isfinite(). 

494 """ 

495 raColName = pexConfig.Field(dtype=str, default="coord_ra", doc="Name of column for RA coordinate") 

496 decColName = pexConfig.Field(dtype=str, default="coord_dec", doc="Name of column for Dec coordinate") 

497 

498 def apply(self, catalog): 

499 """Apply the sky coordinate requirements to a catalog 

500 

501 Returns whether the sources were selected. 

502 

503 Parameters 

504 ---------- 

505 catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 

506 or `astropy.table.Table` 

507 Catalog of sources to which the requirements will be applied. 

508 

509 Returns 

510 ------- 

511 selected : `numpy.ndarray` 

512 Boolean array indicating for each source whether it is selected 

513 (True means selected). 

514 """ 

515 selected = (np.isfinite(_getFieldFromCatalog(catalog, self.raColName)) 

516 & np.isfinite(_getFieldFromCatalog(catalog, self.decColName))) 

517 return selected 

518 

519 

520class RequirePrimary(pexConfig.Config): 

521 """Select sources that have the detect_isPrimary flag set. 

522 

523 This object can be used as a `lsst.pex.config.Config` for configuring 

524 the column names to check for "detect_isPrimary". For single frame 

525 catalogs this will be True when the source is not a sky object, and is 

526 either an isolated parent that is un-modeled or deblended from a parent 

527 with multiple children. For meas_deblender, this is equivalent to 

528 deblend_nChild=0. For coadd catalogs there is an additional constraint 

529 that the source is located on the interior of a patch and tract. 

530 """ 

531 primaryColName = pexConfig.Field( 

532 dtype=str, 

533 default="detect_isPrimary", 

534 doc="Name of primary flag column", 

535 ) 

536 

537 def apply(self, catalog): 

538 """Apply the primary requirements to a catalog. 

539 

540 Returns whether the sources were selected. 

541 

542 Parameters 

543 ---------- 

544 catalog : lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 

545 or `astropy.table.Table` 

546 Catalog of sources to which the requirement will be applied. 

547 

548 Returns 

549 ------- 

550 selected : `numpy.ndarray` 

551 Boolean array indicating for each source whether it is selected 

552 (True means selected). 

553 """ 

554 selected = (_getFieldFromCatalog(catalog, self.primaryColName)).astype(bool) 

555 

556 return selected 

557 

558 

559class ScienceSourceSelectorConfig(pexConfig.Config): 

560 """Configuration for selecting science sources""" 

561 doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?") 

562 doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?") 

563 doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?") 

564 doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?") 

565 doIsolated = pexConfig.Field(dtype=bool, default=False, doc="Apply isolated limitation?") 

566 doRequireFiniteRaDec = pexConfig.Field(dtype=bool, default=False, 

567 doc="Apply finite sky coordinate check?") 

568 doRequirePrimary = pexConfig.Field(dtype=bool, default=False, 

569 doc="Apply source is primary check?") 

570 doSkySources = pexConfig.Field(dtype=bool, default=False, 

571 doc="Include sky sources, unioned with all other criteria?") 

572 fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply") 

573 flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require") 

574 unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply") 

575 signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply") 

576 isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc="Isolated criteria to apply") 

577 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec, 

578 doc="Finite sky coordinate criteria to apply") 

579 requirePrimary = pexConfig.ConfigField(dtype=RequirePrimary, 

580 doc="Primary source criteria to apply") 

581 skyFlag = pexConfig.ConfigField(dtype=RequireFlags, doc="Sky source flag to include") 

582 

583 def setDefaults(self): 

584 pexConfig.Config.setDefaults(self) 

585 self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_saturated", "base_PsfFlux_flag"] 

586 self.signalToNoise.fluxField = "base_PsfFlux_instFlux" 

587 self.signalToNoise.errField = "base_PsfFlux_instFluxErr" 

588 self.skyFlag.good = ["sky_source"] 

589 

590 

591@pexConfig.registerConfigurable("science", sourceSelectorRegistry) 

592class ScienceSourceSelectorTask(BaseSourceSelectorTask): 

593 """Science source selector 

594 

595 By "science" sources, we mean sources that are on images that we 

596 are processing, as opposed to sources from reference catalogs. 

597 

598 This selects (science) sources by (optionally) applying each of a 

599 magnitude limit, flag requirements and star/galaxy separation. 

600 """ 

601 ConfigClass = ScienceSourceSelectorConfig 

602 

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

604 """Return a selection of sources selected by specified criteria. 

605 

606 Parameters 

607 ---------- 

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

609 Catalog of sources to select from. 

610 This catalog must be contiguous in memory. 

611 matches : `list` of `lsst.afw.table.ReferenceMatch` or None 

612 Ignored in this SourceSelector. 

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

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

615 

616 Returns 

617 ------- 

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

619 The struct contains the following data: 

620 

621 ``selected`` 

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

623 sourceCat. 

624 (`numpy.ndarray` of `bool`) 

625 """ 

626 selected = np.ones(len(sourceCat), dtype=bool) 

627 if self.config.doFluxLimit: 

628 selected &= self.config.fluxLimit.apply(sourceCat) 

629 if self.config.doFlags: 

630 selected &= self.config.flags.apply(sourceCat) 

631 if self.config.doUnresolved: 

632 selected &= self.config.unresolved.apply(sourceCat) 

633 if self.config.doSignalToNoise: 

634 selected &= self.config.signalToNoise.apply(sourceCat) 

635 if self.config.doIsolated: 

636 selected &= self.config.isolated.apply(sourceCat) 

637 if self.config.doRequireFiniteRaDec: 

638 selected &= self.config.requireFiniteRaDec.apply(sourceCat) 

639 if self.config.doRequirePrimary: 

640 selected &= self.config.requirePrimary.apply(sourceCat) 

641 if self.config.doSkySources: 

642 selected |= self.config.skyFlag.apply(sourceCat) 

643 

644 self.log.info("Selected %d/%d sources", selected.sum(), len(sourceCat)) 

645 

646 return pipeBase.Struct(selected=selected) 

647 

648 

649class ReferenceSourceSelectorConfig(pexConfig.Config): 

650 doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?") 

651 doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?") 

652 doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?") 

653 doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?") 

654 doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?") 

655 doRequireFiniteRaDec = pexConfig.Field(dtype=bool, default=True, 

656 doc="Apply finite sky coordinate check?") 

657 magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply") 

658 flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require") 

659 unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply") 

660 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec, 

661 doc="Finite sky coordinate criteria to apply") 

662 signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply") 

663 magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply") 

664 colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={}, 

665 doc="Color limits to apply; key is used as a label only") 

666 

667 

668@pexConfig.registerConfigurable("references", sourceSelectorRegistry) 

669class ReferenceSourceSelectorTask(BaseSourceSelectorTask): 

670 """Reference source selector 

671 

672 This selects reference sources by (optionally) applying each of a 

673 magnitude limit, flag requirements and color limits. 

674 """ 

675 ConfigClass = ReferenceSourceSelectorConfig 

676 

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

678 """Return a selection of reference sources selected by some criteria. 

679 

680 Parameters 

681 ---------- 

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

683 Catalog of sources to select from. 

684 This catalog must be contiguous in memory. 

685 matches : `list` of `lsst.afw.table.ReferenceMatch` or None 

686 Ignored in this SourceSelector. 

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

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

689 

690 Returns 

691 ------- 

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

693 The struct contains the following data: 

694 

695 ``selected`` 

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

697 sourceCat. 

698 (`numpy.ndarray` of `bool`) 

699 """ 

700 selected = np.ones(len(sourceCat), dtype=bool) 

701 if self.config.doMagLimit: 

702 selected &= self.config.magLimit.apply(sourceCat) 

703 if self.config.doFlags: 

704 selected &= self.config.flags.apply(sourceCat) 

705 if self.config.doUnresolved: 

706 selected &= self.config.unresolved.apply(sourceCat) 

707 if self.config.doSignalToNoise: 

708 selected &= self.config.signalToNoise.apply(sourceCat) 

709 if self.config.doMagError: 

710 selected &= self.config.magError.apply(sourceCat) 

711 if self.config.doRequireFiniteRaDec: 

712 selected &= self.config.requireFiniteRaDec.apply(sourceCat) 

713 for limit in self.config.colorLimits.values(): 

714 selected &= limit.apply(sourceCat) 

715 

716 self.log.info("Selected %d/%d references", selected.sum(), len(sourceCat)) 

717 

718 return pipeBase.Struct(selected=selected) 

719 

720 

721@pexConfig.registerConfigurable("null", sourceSelectorRegistry) 

722class NullSourceSelectorTask(BaseSourceSelectorTask): 

723 """Source selector that returns true for all sources. 

724 

725 Use this when you do not want any sub-selection on your inputs. 

726 """ 

727 ConfigClass = BaseSourceSelectorConfig 

728 

729 def selectSources(self, sourceCat, **kwargs): 

730 # docstring inherited 

731 return pipeBase.Struct(selected=np.ones(len(sourceCat), dtype=bool)) 

732 

733 

734def _getFieldFromCatalog(catalog, field, isFlag=False): 

735 """ 

736 Get a field from a catalog, for `lsst.afw.table` catalogs or 

737 `pandas.DataFrame` or `astropy.table.Table` catalogs. 

738 

739 Parameters 

740 ---------- 

741 catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 

742 or `astropy.table.Table` 

743 Catalog of sources to extract field array 

744 field : `str` 

745 Name of field 

746 isFlag : `bool`, optional 

747 Is this a flag column? If it does not exist, return array 

748 of False. 

749 

750 Returns 

751 ------- 

752 array : `np.ndarray` 

753 Array of field values from the catalog. 

754 """ 

755 found = False 

756 if isinstance(catalog, (pandas.DataFrame, astropy.table.Table)): 

757 if field in catalog.columns: 

758 found = True 

759 # Sequences must be converted to numpy arrays 

760 arr = np.array(catalog[field]) 

761 else: 

762 if field in catalog.schema: 

763 found = True 

764 arr = catalog[field] 

765 

766 if isFlag and not found: 

767 arr = np.zeros(len(catalog), dtype=bool) 

768 elif not found: 

769 raise KeyError(f"Could not find field {field} in catalog.") 

770 

771 return arr