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

196 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-11 10:33 +0000

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2017 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23 

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

25 "ColorLimit", "MagnitudeLimit", "SignalToNoiseLimit", "MagnitudeErrorLimit", 

26 "RequireFlags", "RequireUnresolved", "RequireFiniteRaDec", 

27 "ScienceSourceSelectorConfig", "ScienceSourceSelectorTask", 

28 "ReferenceSourceSelectorConfig", "ReferenceSourceSelectorTask", 

29 ] 

30 

31import abc 

32import numpy as np 

33import astropy.units as u 

34import pandas 

35import astropy.table 

36 

37import lsst.pex.config as pexConfig 

38import lsst.pipe.base as pipeBase 

39 

40 

41class BaseSourceSelectorConfig(pexConfig.Config): 

42 pass 

43 

44 

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

46 """Base class for source selectors 

47 

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

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

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

51 identify if the source was selected. 

52 

53 Register all source selectors with the sourceSelectorRegistry using: 

54 sourceSelectorRegistry.register(name, class) 

55 

56 Attributes 

57 ---------- 

58 usesMatches : `bool` 

59 A boolean variable specify if the inherited source selector uses 

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

61 argument to ``run()``. 

62 """ 

63 

64 ConfigClass = BaseSourceSelectorConfig 

65 _DefaultName = "sourceSelector" 

66 usesMatches = False 

67 

68 def __init__(self, **kwargs): 

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

70 

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

72 """Select sources and return them. 

73 

74 The input catalog must be contiguous in memory. 

75 

76 Parameters 

77 ---------- 

78 sourceCat : Various table formats 

79 Catalog of sources to select from. Can be 

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

81 `astropy.table.Table`, 

82 sourceSelectedField : `str` or None 

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

84 If set, will modify sourceCat in-place. 

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

86 List of matches to use for source selection. 

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

88 If not, it is ignored. 

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

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

91 

92 Returns 

93 ------- 

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

95 The struct contains the following data: 

96 

97 ``sourceCat`` 

98 The catalog of sources that were selected. 

99 (may not be memory-contiguous) 

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

101 or `astropy.table.Table`) 

102 ``selected`` 

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

104 sourceCat. 

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

106 

107 Raises 

108 ------ 

109 RuntimeError 

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

111 """ 

112 if hasattr(sourceCat, 'isContiguous'): 

113 # Check for continuity on afwTable catalogs 

114 if not sourceCat.isContiguous(): 

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

116 

117 result = self.selectSources(sourceCat=sourceCat, 

118 exposure=exposure, 

119 matches=matches) 

120 

121 if sourceSelectedField is not None: 

122 if isinstance(sourceCat, (pandas.DataFrame, astropy.table.Table)): 

123 sourceCat[sourceSelectedField] = result.selected 

124 else: 

125 source_selected_key = \ 

126 sourceCat.getSchema()[sourceSelectedField].asKey() 

127 # TODO: Remove for loop when DM-6981 is completed. 

128 for source, flag in zip(sourceCat, result.selected): 

129 source.set(source_selected_key, bool(flag)) 

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

131 selected=result.selected) 

132 

133 @abc.abstractmethod 

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

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

136 

137 Parameters 

138 ---------- 

139 sourceCat : Various table formats 

140 Catalog of sources to select from. Supports 

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

142 or `astropy.table.Table` 

143 This catalog must be contiguous in memory. 

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

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

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

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

148 

149 Returns 

150 ------- 

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

152 The struct contains the following data: 

153 

154 ``selected`` 

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

156 sourceCat. 

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

158 """ 

159 raise NotImplementedError("BaseSourceSelectorTask is abstract") 

160 

161 

162sourceSelectorRegistry = pexConfig.makeRegistry( 

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

164 "BaseSourceSelectorTask)", 

165) 

166 

167 

168class BaseLimit(pexConfig.Config): 

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

170 

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

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

173 in the catalog that match the configured limit. 

174 

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

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

177 subclass. 

178 """ 

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

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

181 

182 def apply(self, values): 

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

184 

185 Subclasses should calculate the array of values and then 

186 return the result of calling this method. 

187 

188 Parameters 

189 ---------- 

190 values : `numpy.ndarray` 

191 Array of values to which to apply limits. 

192 

193 Returns 

194 ------- 

195 selected : `numpy.ndarray` 

196 Boolean array indicating for each source whether it is selected 

197 (True means selected). 

198 """ 

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

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

201 if self.minimum is not None: 

202 selected &= values > self.minimum 

203 if self.maximum is not None: 

204 selected &= values < self.maximum 

205 return selected 

206 

207 

208class ColorLimit(BaseLimit): 

209 """Select sources using a color limit 

210 

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

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

213 in the catalog that match the configured limit. 

214 

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

216 two components of the color, which is: 

217 

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

219 """ 

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

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

222 

223 def apply(self, catalog): 

224 """Apply the color limit to a catalog 

225 

226 Parameters 

227 ---------- 

228 catalog : Various table formats 

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

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

231 or `astropy.table.Table` 

232 

233 Returns 

234 ------- 

235 selected : `numpy.ndarray` 

236 Boolean array indicating for each source whether it is selected 

237 (True means selected). 

238 """ 

239 primary = _getFieldFromCatalog(catalog, self.primary) 

240 secondary = _getFieldFromCatalog(catalog, self.secondary) 

241 

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

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

244 color = primary - secondary 

245 return BaseLimit.apply(self, color) 

246 

247 

248class FluxLimit(BaseLimit): 

249 """Select sources using a flux limit 

250 

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

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

253 in the catalog that match the configured limit. 

254 """ 

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

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

257 

258 def apply(self, catalog): 

259 """Apply the flux limits to a catalog 

260 

261 Parameters 

262 ---------- 

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

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

265 

266 Returns 

267 ------- 

268 selected : `numpy.ndarray` 

269 Boolean array indicating for each source whether it is selected 

270 (True means selected). 

271 """ 

272 flagField = self.fluxField + "_flag" 

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

274 flux = _getFieldFromCatalog(catalog, self.fluxField) 

275 

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

277 return selected 

278 

279 

280class MagnitudeLimit(BaseLimit): 

281 """Select sources using a magnitude limit 

282 

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

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

285 intended for reference catalogs rather than catalogs extracted from 

286 science images. 

287 

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

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

290 in the catalog that match the configured limit. 

291 """ 

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

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

294 

295 def apply(self, catalog): 

296 """Apply the magnitude limits to a catalog 

297 

298 Parameters 

299 ---------- 

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

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

302 

303 Returns 

304 ------- 

305 selected : `numpy.ndarray` 

306 Boolean array indicating for each source whether it is selected 

307 (True means selected). 

308 """ 

309 flagField = self.fluxField + "_flag" 

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

311 flux = _getFieldFromCatalog(catalog, self.fluxField) 

312 

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

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

315 return selected 

316 

317 

318class SignalToNoiseLimit(BaseLimit): 

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

320 

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

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

323 in the catalog that match the configured limit. 

324 """ 

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

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

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

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

329 

330 def apply(self, catalog): 

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

332 

333 Parameters 

334 ---------- 

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

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

337 

338 Returns 

339 ------- 

340 selected : `numpy.ndarray` 

341 Boolean array indicating for each source whether it is selected 

342 (True means selected). 

343 """ 

344 flagField = self.fluxField + "_flag" 

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

346 flux = _getFieldFromCatalog(catalog, self.fluxField) 

347 err = _getFieldFromCatalog(catalog, self.errField) 

348 

349 signalToNoise = flux/err 

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

351 return selected 

352 

353 

354class MagnitudeErrorLimit(BaseLimit): 

355 """Select sources using a magnitude error limit 

356 

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

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

359 you only have a magnitude. 

360 

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

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

363 in the catalog that match the configured limit. 

364 """ 

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

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

367 

368 def apply(self, catalog): 

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

370 

371 Parameters 

372 ---------- 

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

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

375 

376 Returns 

377 ------- 

378 selected : `numpy.ndarray` 

379 Boolean array indicating for each source whether it is selected 

380 (True means selected). 

381 """ 

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

383 

384 

385class RequireFlags(pexConfig.Config): 

386 """Select sources using flags 

387 

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

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

390 in the catalog that match the configured limit. 

391 """ 

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

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

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

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

396 

397 def apply(self, catalog): 

398 """Apply the flag requirements to a catalog 

399 

400 Returns whether the source is selected. 

401 

402 Parameters 

403 ---------- 

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

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

406 

407 Returns 

408 ------- 

409 selected : `numpy.ndarray` 

410 Boolean array indicating for each source whether it is selected 

411 (True means selected). 

412 """ 

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

414 for flag in self.good: 

415 selected &= catalog[flag] 

416 for flag in self.bad: 

417 selected &= ~catalog[flag] 

418 return selected 

419 

420 

421class RequireUnresolved(BaseLimit): 

422 """Select sources using star/galaxy separation 

423 

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

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

426 in the catalog that match the configured limit. 

427 """ 

428 name = pexConfig.Field(dtype=str, default="base_ClassificationExtendedness_value", 

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

430 

431 def setDefaults(self): 

432 """Set default 

433 

434 ``base_ClassificationExtendedness_value < 0.5`` means unresolved. 

435 """ 

436 self.maximum = 0.5 

437 

438 def apply(self, catalog): 

439 """Apply the flag requirements to a catalog 

440 

441 Returns whether the source is selected. 

442 

443 Parameters 

444 ---------- 

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

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

447 

448 Returns 

449 ------- 

450 selected : `numpy.ndarray` 

451 Boolean array indicating for each source whether it is selected 

452 (True means selected). 

453 """ 

454 value = catalog[self.name] 

455 return BaseLimit.apply(self, value) 

456 

457 

458class RequireIsolated(pexConfig.Config): 

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

460 

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

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

463 

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

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

466 """ 

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

468 doc="Name of column for parent") 

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

470 doc="Name of column for nChild") 

471 

472 def apply(self, catalog): 

473 """Apply the isolation requirements to a catalog 

474 

475 Returns whether the source is selected. 

476 

477 Parameters 

478 ---------- 

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

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

481 

482 Returns 

483 ------- 

484 selected : `numpy.ndarray` 

485 Boolean array indicating for each source whether it is selected 

486 (True means selected). 

487 """ 

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

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

490 return selected 

491 

492 

493class RequireFiniteRaDec(pexConfig.Config): 

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

495 

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

497 the column names to check for "coore_ra" and "coord_dec" keys. 

498 

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

500 entries are not numpy.isfinite(). 

501 """ 

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

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

504 

505 def apply(self, catalog): 

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

507 

508 Returns whether the source is selected. 

509 

510 Parameters 

511 ---------- 

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

513 or `astropy.table.Table` 

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

515 

516 Returns 

517 ------- 

518 selected : `numpy.ndarray` 

519 Boolean array indicating for each source whether it is selected 

520 (True means selected). 

521 """ 

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

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

524 return selected 

525 

526 

527class ScienceSourceSelectorConfig(pexConfig.Config): 

528 """Configuration for selecting science sources""" 

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

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

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

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

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

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

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

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

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

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

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

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

541 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec, 

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

543 

544 def setDefaults(self): 

545 pexConfig.Config.setDefaults(self) 

546 self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_saturated", "base_PsfFlux_flags"] 

547 self.signalToNoise.fluxField = "base_PsfFlux_instFlux" 

548 self.signalToNoise.errField = "base_PsfFlux_instFluxErr" 

549 

550 

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

552class ScienceSourceSelectorTask(BaseSourceSelectorTask): 

553 """Science source selector 

554 

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

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

557 

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

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

560 """ 

561 ConfigClass = ScienceSourceSelectorConfig 

562 

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

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

565 

566 Parameters 

567 ---------- 

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

569 Catalog of sources to select from. 

570 This catalog must be contiguous in memory. 

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

572 Ignored in this SourceSelector. 

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

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

575 

576 Returns 

577 ------- 

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

579 The struct contains the following data: 

580 

581 ``selected`` 

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

583 sourceCat. 

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

585 """ 

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

587 if self.config.doFluxLimit: 

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

589 if self.config.doFlags: 

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

591 if self.config.doUnresolved: 

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

593 if self.config.doSignalToNoise: 

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

595 if self.config.doIsolated: 

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

597 if self.config.doRequireFiniteRaDec: 

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

599 

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

601 

602 return pipeBase.Struct(selected=selected) 

603 

604 

605class ReferenceSourceSelectorConfig(pexConfig.Config): 

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

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

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

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

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

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

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

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

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

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

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

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

618 

619 

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

621class ReferenceSourceSelectorTask(BaseSourceSelectorTask): 

622 """Reference source selector 

623 

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

625 magnitude limit, flag requirements and color limits. 

626 """ 

627 ConfigClass = ReferenceSourceSelectorConfig 

628 

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

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

631 

632 Parameters 

633 ---------- 

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

635 Catalog of sources to select from. 

636 This catalog must be contiguous in memory. 

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

638 Ignored in this SourceSelector. 

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

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

641 

642 Returns 

643 ------- 

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

645 The struct contains the following data: 

646 

647 ``selected`` 

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

649 sourceCat. 

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

651 """ 

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

653 if self.config.doMagLimit: 

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

655 if self.config.doFlags: 

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

657 if self.config.doUnresolved: 

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

659 if self.config.doSignalToNoise: 

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

661 if self.config.doMagError: 

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

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

664 selected &= limit.apply(sourceCat) 

665 

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

667 

668 return pipeBase.Struct(selected=selected) 

669 

670 

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

672 """ 

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

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

675 

676 Parameters 

677 ---------- 

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

679 or `astropy.table.Table` 

680 Catalog of sources to extract field array 

681 field : `str` 

682 Name of field 

683 isFlag : `bool`, optional 

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

685 of False. 

686 

687 Returns 

688 ------- 

689 array : `np.ndarray` 

690 Array of field values from the catalog. 

691 """ 

692 found = False 

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

694 if field in catalog.columns: 

695 found = True 

696 # Sequences must be converted to numpy arrays 

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

698 else: 

699 if field in catalog.schema: 

700 found = True 

701 arr = catalog[field] 

702 

703 if isFlag and not found: 

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

705 elif not found: 

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

707 

708 return arr