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

201 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-09 03:05 -0700

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", "RequirePrimary", 

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 sourceCat[sourceSelectedField] = result.selected 

123 

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

125 selected=result.selected) 

126 

127 @abc.abstractmethod 

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

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

130 

131 Parameters 

132 ---------- 

133 sourceCat : Various table formats 

134 Catalog of sources to select from. Supports 

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

136 or `astropy.table.Table` 

137 This catalog must be contiguous in memory. 

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

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

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

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

142 

143 Returns 

144 ------- 

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

146 The struct contains the following data: 

147 

148 ``selected`` 

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

150 sourceCat. 

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

152 """ 

153 raise NotImplementedError("BaseSourceSelectorTask is abstract") 

154 

155 

156sourceSelectorRegistry = pexConfig.makeRegistry( 

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

158 "BaseSourceSelectorTask)", 

159) 

160 

161 

162class BaseLimit(pexConfig.Config): 

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

164 

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

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

167 in the catalog that match the configured limit. 

168 

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

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

171 subclass. 

172 """ 

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

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

175 

176 def apply(self, values): 

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

178 

179 Subclasses should calculate the array of values and then 

180 return the result of calling this method. 

181 

182 Parameters 

183 ---------- 

184 values : `numpy.ndarray` 

185 Array of values to which to apply limits. 

186 

187 Returns 

188 ------- 

189 selected : `numpy.ndarray` 

190 Boolean array indicating for each source whether it is selected 

191 (True means selected). 

192 """ 

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

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

195 if self.minimum is not None: 

196 selected &= values > self.minimum 

197 if self.maximum is not None: 

198 selected &= values < self.maximum 

199 return selected 

200 

201 

202class ColorLimit(BaseLimit): 

203 """Select sources using a color limit 

204 

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

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

207 in the catalog that match the configured limit. 

208 

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

210 two components of the color, which is: 

211 

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

213 """ 

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

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

216 

217 def apply(self, catalog): 

218 """Apply the color limit to a catalog 

219 

220 Parameters 

221 ---------- 

222 catalog : Various table formats 

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

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

225 or `astropy.table.Table` 

226 

227 Returns 

228 ------- 

229 selected : `numpy.ndarray` 

230 Boolean array indicating for each source whether it is selected 

231 (True means selected). 

232 """ 

233 primary = _getFieldFromCatalog(catalog, self.primary) 

234 secondary = _getFieldFromCatalog(catalog, self.secondary) 

235 

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

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

238 color = primary - secondary 

239 return BaseLimit.apply(self, color) 

240 

241 

242class FluxLimit(BaseLimit): 

243 """Select sources using a flux limit 

244 

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

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

247 in the catalog that match the configured limit. 

248 """ 

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

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

251 

252 def apply(self, catalog): 

253 """Apply the flux limits to a catalog 

254 

255 Parameters 

256 ---------- 

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

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

259 

260 Returns 

261 ------- 

262 selected : `numpy.ndarray` 

263 Boolean array indicating for each source whether it is selected 

264 (True means selected). 

265 """ 

266 flagField = self.fluxField + "_flag" 

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

268 flux = _getFieldFromCatalog(catalog, self.fluxField) 

269 

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

271 return selected 

272 

273 

274class MagnitudeLimit(BaseLimit): 

275 """Select sources using a magnitude limit 

276 

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

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

279 intended for reference catalogs rather than catalogs extracted from 

280 science images. 

281 

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

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

284 in the catalog that match the configured limit. 

285 """ 

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

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

288 

289 def apply(self, catalog): 

290 """Apply the magnitude limits to a catalog 

291 

292 Parameters 

293 ---------- 

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

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

296 

297 Returns 

298 ------- 

299 selected : `numpy.ndarray` 

300 Boolean array indicating for each source whether it is selected 

301 (True means selected). 

302 """ 

303 flagField = self.fluxField + "_flag" 

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

305 flux = _getFieldFromCatalog(catalog, self.fluxField) 

306 

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

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

309 return selected 

310 

311 

312class SignalToNoiseLimit(BaseLimit): 

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

314 

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

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

317 in the catalog that match the configured limit. 

318 """ 

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

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

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

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

323 

324 def apply(self, catalog): 

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

326 

327 Parameters 

328 ---------- 

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

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

331 

332 Returns 

333 ------- 

334 selected : `numpy.ndarray` 

335 Boolean array indicating for each source whether it is selected 

336 (True means selected). 

337 """ 

338 flagField = self.fluxField + "_flag" 

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

340 flux = _getFieldFromCatalog(catalog, self.fluxField) 

341 err = _getFieldFromCatalog(catalog, self.errField) 

342 

343 signalToNoise = flux/err 

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

345 return selected 

346 

347 

348class MagnitudeErrorLimit(BaseLimit): 

349 """Select sources using a magnitude error limit 

350 

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

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

353 you only have a magnitude. 

354 

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

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

357 in the catalog that match the configured limit. 

358 """ 

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

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

361 

362 def apply(self, catalog): 

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

364 

365 Parameters 

366 ---------- 

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

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

369 

370 Returns 

371 ------- 

372 selected : `numpy.ndarray` 

373 Boolean array indicating for each source whether it is selected 

374 (True means selected). 

375 """ 

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

377 

378 

379class RequireFlags(pexConfig.Config): 

380 """Select sources using flags 

381 

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

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

384 in the catalog that match the configured limit. 

385 """ 

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

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

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

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

390 

391 def apply(self, catalog): 

392 """Apply the flag requirements to a catalog 

393 

394 Returns whether the source is selected. 

395 

396 Parameters 

397 ---------- 

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

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

400 

401 Returns 

402 ------- 

403 selected : `numpy.ndarray` 

404 Boolean array indicating for each source whether it is selected 

405 (True means selected). 

406 """ 

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

408 for flag in self.good: 

409 selected &= catalog[flag] 

410 for flag in self.bad: 

411 selected &= ~catalog[flag] 

412 return selected 

413 

414 

415class RequireUnresolved(BaseLimit): 

416 """Select sources using star/galaxy separation 

417 

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

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

420 in the catalog that match the configured limit. 

421 """ 

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

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

424 

425 def setDefaults(self): 

426 """Set default 

427 

428 ``base_ClassificationExtendedness_value < 0.5`` means unresolved. 

429 """ 

430 self.maximum = 0.5 

431 

432 def apply(self, catalog): 

433 """Apply the flag requirements to a catalog 

434 

435 Returns whether the source is selected. 

436 

437 Parameters 

438 ---------- 

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

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

441 

442 Returns 

443 ------- 

444 selected : `numpy.ndarray` 

445 Boolean array indicating for each source whether it is selected 

446 (True means selected). 

447 """ 

448 value = catalog[self.name] 

449 return BaseLimit.apply(self, value) 

450 

451 

452class RequireIsolated(pexConfig.Config): 

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

454 

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

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

457 

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

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

460 """ 

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

462 doc="Name of column for parent") 

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

464 doc="Name of column for nChild") 

465 

466 def apply(self, catalog): 

467 """Apply the isolation requirements to a catalog 

468 

469 Returns whether the source is selected. 

470 

471 Parameters 

472 ---------- 

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

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

475 

476 Returns 

477 ------- 

478 selected : `numpy.ndarray` 

479 Boolean array indicating for each source whether it is selected 

480 (True means selected). 

481 """ 

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

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

484 return selected 

485 

486 

487class RequireFiniteRaDec(pexConfig.Config): 

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

489 

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

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

492 

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

494 entries are not numpy.isfinite(). 

495 """ 

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

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

498 

499 def apply(self, catalog): 

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

501 

502 Returns whether the sources were selected. 

503 

504 Parameters 

505 ---------- 

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

507 or `astropy.table.Table` 

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

509 

510 Returns 

511 ------- 

512 selected : `numpy.ndarray` 

513 Boolean array indicating for each source whether it is selected 

514 (True means selected). 

515 """ 

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

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

518 return selected 

519 

520 

521class RequirePrimary(pexConfig.Config): 

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

523 

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

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

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

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

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

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

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

531 """ 

532 primaryColName = pexConfig.Field( 

533 dtype=str, 

534 default="detect_isPrimary", 

535 doc="Name of primary flag column", 

536 ) 

537 

538 def apply(self, catalog): 

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

540 

541 Returns whether the sources were selected. 

542 

543 Parameters 

544 ---------- 

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

546 or `astropy.table.Table` 

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

548 

549 Returns 

550 ------- 

551 selected : `numpy.ndarray` 

552 Boolean array indicating for each source whether it is selected 

553 (True means selected). 

554 """ 

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

556 

557 return selected 

558 

559 

560class ScienceSourceSelectorConfig(pexConfig.Config): 

561 """Configuration for selecting science sources""" 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

576 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec, 

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

578 requirePrimary = pexConfig.ConfigField(dtype=RequirePrimary, 

579 doc="Primary source criteria to apply") 

580 

581 def setDefaults(self): 

582 pexConfig.Config.setDefaults(self) 

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

584 self.signalToNoise.fluxField = "base_PsfFlux_instFlux" 

585 self.signalToNoise.errField = "base_PsfFlux_instFluxErr" 

586 

587 

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

589class ScienceSourceSelectorTask(BaseSourceSelectorTask): 

590 """Science source selector 

591 

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

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

594 

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

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

597 """ 

598 ConfigClass = ScienceSourceSelectorConfig 

599 

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

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

602 

603 Parameters 

604 ---------- 

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

606 Catalog of sources to select from. 

607 This catalog must be contiguous in memory. 

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

609 Ignored in this SourceSelector. 

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

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

612 

613 Returns 

614 ------- 

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

616 The struct contains the following data: 

617 

618 ``selected`` 

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

620 sourceCat. 

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

622 """ 

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

624 if self.config.doFluxLimit: 

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

626 if self.config.doFlags: 

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

628 if self.config.doUnresolved: 

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

630 if self.config.doSignalToNoise: 

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

632 if self.config.doIsolated: 

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

634 if self.config.doRequireFiniteRaDec: 

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

636 if self.config.doRequirePrimary: 

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

638 

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

640 

641 return pipeBase.Struct(selected=selected) 

642 

643 

644class ReferenceSourceSelectorConfig(pexConfig.Config): 

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

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

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

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

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

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

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

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

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

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

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

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

657 

658 

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

660class ReferenceSourceSelectorTask(BaseSourceSelectorTask): 

661 """Reference source selector 

662 

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

664 magnitude limit, flag requirements and color limits. 

665 """ 

666 ConfigClass = ReferenceSourceSelectorConfig 

667 

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

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

670 

671 Parameters 

672 ---------- 

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

674 Catalog of sources to select from. 

675 This catalog must be contiguous in memory. 

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

677 Ignored in this SourceSelector. 

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

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

680 

681 Returns 

682 ------- 

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

684 The struct contains the following data: 

685 

686 ``selected`` 

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

688 sourceCat. 

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

690 """ 

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

692 if self.config.doMagLimit: 

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

694 if self.config.doFlags: 

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

696 if self.config.doUnresolved: 

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

698 if self.config.doSignalToNoise: 

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

700 if self.config.doMagError: 

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

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

703 selected &= limit.apply(sourceCat) 

704 

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

706 

707 return pipeBase.Struct(selected=selected) 

708 

709 

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

711 """ 

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

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

714 

715 Parameters 

716 ---------- 

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

718 or `astropy.table.Table` 

719 Catalog of sources to extract field array 

720 field : `str` 

721 Name of field 

722 isFlag : `bool`, optional 

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

724 of False. 

725 

726 Returns 

727 ------- 

728 array : `np.ndarray` 

729 Array of field values from the catalog. 

730 """ 

731 found = False 

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

733 if field in catalog.columns: 

734 found = True 

735 # Sequences must be converted to numpy arrays 

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

737 else: 

738 if field in catalog.schema: 

739 found = True 

740 arr = catalog[field] 

741 

742 if isFlag and not found: 

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

744 elif not found: 

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

746 

747 return arr